#define question

A

Adam L.

Howdy all.

Quick background ? Pascal/Delphi programmer writing a software program
in Pascal, coding the same program in parallel, in C. This is my long
overdue goal to learn the C language. Been putting it off for about 6
years. :)

So far, so good. Just in the beginnings, but it's coming along. There
is one thing that I have seen a few times that I can't seem to make sense of:

#ifdef SOME_VAR // Conditional seems to vary
#define EXTERN
#else
#define EXTERN extern
#endif


Now, I've read a few posts and visited several web pages
about the above code and the explanations are more confusing than the code.
Would someone happen to have a good ?in Pascal it would work like this? or
?if it were in BASIC, it would work like this? explanation? I'd even settle
for some ?clueless about C? plain English.

Thanks in advance!

-Adam
 
A

Ark Khasin

Adam said:
Howdy all.

Quick background ? Pascal/Delphi programmer writing a software program
in Pascal, coding the same program in parallel, in C. This is my long
overdue goal to learn the C language. Been putting it off for about 6
years. :)

So far, so good. Just in the beginnings, but it's coming along. There
is one thing that I have seen a few times that I can't seem to make sense of:

#ifdef SOME_VAR // Conditional seems to vary
#define EXTERN
#else
#define EXTERN extern
#endif


Now, I've read a few posts and visited several web pages
about the above code and the explanations are more confusing than the code.
Would someone happen to have a good ?in Pascal it would work like this? or
?if it were in BASIC, it would work like this? explanation? I'd even settle
for some ?clueless about C? plain English.

Thanks in advance!

-Adam
You've seen it in VERY BAD code (or perhaps very old code gone bad,
which case I won't cover). Never do it in yours!

The intended meaning is this:
foo.c owns, say, int foo. It is of external linkage; other translation
units need to say
extern int foo;
in order to see the foo.

Mere mortals do it like that: we say someplace in foo.c
int foo;
Then we invent a header file, foo.h, where we place external interfaces
of foo.c. There, we say
extern int foo;
Every source that does
#include "foo.h"
now has access to foo. foo.c also includes "foo.h", just to make sure
that we don't accidentally have a declaration (in .h) different from
definition (in .c). But you knew that.

Now, artistic types might write, in foo.h
EXTERN int foo;
and in foo.h write
#define SOME_VAR
#include "header-where-you-saw-that-passage.h"
#include "foo.h"

The effect is that foo.h included in foo.c produces a /definition/ of
foo, and if included in other source files, produces /declarations/
provided that SOME_VAR is not #define'd anywhere else.

This clever trick allows you to place all globals marked as EXTERN in
one translation unit; it is not good for anything else.

The drawback is that one develops a bad habit of defining a macro (in
this case, EXTERN) differently in different translation units; that's a
demonstrated recipe for disaster. Oh yeah, you could convince yourself
it's OK to sin "just once" if there is a tangible benefit. But in this
case the benefit is negative; the trick is designed to destroy any code
modularity. If SOME_VAR is defined in several files, the behavior is
undefined (but realistically, probably a link error).

-- Ark
 
R

Richard Bos

Adam L. said:
So far, so good. Just in the beginnings, but it's coming along. There
is one thing that I have seen a few times that I can't seem to make sense of:

#ifdef SOME_VAR // Conditional seems to vary
#define EXTERN
#else
#define EXTERN extern
#endif

This is for when you want to #define a macro to one of two (or more)
values, depending on whether you have or have not set another macro
earlier on. SOME_VAR (which is _not_ a variable, but must also be a
macro) will usually be a constant set by a specific compiler, but not by
others; or a constant set in one header and/or .c file, but not in
others.
In this case, it is specifically so that you can have one single header
file to #include to _define_ certain objects in one place, but only
_declare_ them elsewhere. This is important, because you're not allowed
to define objects more than once, but you can declare them as often as
you use them. It is perhaps more easy to grasp with a practical example.

Let's say that you have a big program, which you split into several .c
files. Some of these .c files define objects which are useful for the
other parts of the program, and those other parts want to declare the
existence of these objects so they can use them. Let us say, for
example, that you have a fast-file-reading unit, which contains, as part
of the objects it wants to share, a file contents buffer. Now normally,
in fastfile.c, you'd _define_

unsigned char ffr_buffer[FFR_BUFFERSIZE];

and everywhere else you'd want to use that buffer, you would _declare_

extern unsigned char ffr_buffer[FFR_BUFFERSIZE];

See the difference? Only that extern. In object declarations, extern
means "this object should not be created here and now; some other part
of the program will create this object, but I want to know what it is
called and what type it has, so I can use it here". By contrast, without
the extern that definition simply means "create an object with this name
and give it this type".

Now you want to simplify this. For that reason, you create a header
called fastfile.h. This header contains all declarations which any .c
file which uses functionality from fastfile.c would want to declare. In
particular, it contains

extern unsigned char ffr_buffer[FFR_BUFFERSIZE];

(and of course, it also contains #define FFR_BUFFERSIZE 32768, or
whatever). Now you can #include fastfile.h in any .c file which uses
fast file functionality, and you will automatically have the right
declarations without having to retype them.
In fact, you will even be able to #include fastfile.h into fastfile.c -
declaring _and_ defining an object in the same file is allowed, it's
only defining it more than once (anywhere) which is a problem - and that
will make sure that the compiler checks whether the declaration in the
header is the same as the definition in the .c file. That way, if you
change the definition of ffr_buffer (say, to an unsigned int array), you
will be warned to change the header file so that the declaration other
files have of it remains the same as the definition in fastfile.c.

So far, so good. But now you want to introduce another timesaver. Notice
how the declaration and the definition are almost the same? What if you
could get the compiler to accept the declaration as a definition only in
fastfile.c, and as a normal declaration elsewhere? Then you'd _only_
have to maintain the header file, and you wouldn't have to write a
separate definition in fastfile.c. This would save typing, but more
importantly, it would ensure that you wouldn't _need_ to change the
declaration when the definition changes, because they'd be one and the
same line of code. Changing one would automatically change the other,
and hey presto: one less point of breakage.
Well, you can. Using the preprocessor. Here's what you do. You remove
the separate definition from fastfile.c, and change the declaration in
fastfile.h to

EXTERN unsigned char ffr_buffer[FFR_BUFFERSIZE];

Note: capital EXTERN. Unlike Pascal, C is case-sensitive; EXTERN is not
extern. What you do now is change the definition of EXTERN, which as yet
has none, to be _either_ the keyword extern - making this line a normal
declaration - _or_ nothing at all - turning it into a definition. And
which of these you do, you let depend on whether you're in fastfile.c,
where you want the definition, or elsewhere, where you do not.
So now you have, in fastfile.h,

#if <something we'll come to next>
#define EXTERN
#else
#define EXTERN extern
#endif

#define FFR_BUFFERSIZE 32768
EXTERN unsigned char ffr_buffer[FFR_BUFFERSIZE];

If the <something> holds, this results in FFR_BUFFERSIZE being #defined,
and ffr_buffer being defined as well, because EXTERN is blank. If
<something> does not hold, it results in FFR_BUFFERSIZE being #defined
as well (which, unlike for objects, is legal for macros, because macros
are only a bit of text substitution in the compiler, not a real object
in the resulting program), but in ffr_buffer only being declared,
because now EXTERN is extern. Clearly, we want <something> to hold only
when fastfile.h is #included in fastfile.c, and not anywhere else.

But how do you do that? Can the preprocessor automatically detect in
which source file it is? Not really. There's __FILE__, but that isn't
suitable here. It's much better to #define your own constant that
explicitly tells the header where it is. For example, let's assume a
macro called FFR_MAIN_FILE, which is either defined or not. We can then
change the above to

#ifdef FFR_MAIN_FILE
#define EXTERN
#else
#define EXTERN extern
#endif

#define FFR_BUFFERSIZE 32768
EXTERN unsigned char ffr_buffer[FFR_BUFFERSIZE];

Now we'll get the definition where FFR_MAIN_FILE has been #defined, and
the declaration where it has not. If you prefer, you can spell #ifdef
FFR_MAIN_FILE as #if defined FFR_MAIN_FILE (or even as #if defined
(FFR_MAIN_FILE) ); they're equivalent. All of these check whether
FFR_MAIN_FILE has previously been #defined in that source file, or in
the one which currently #includes it, or the one which #includes that,
and so on. It does not, though, check whether FFR_MAIN_FILE is #defined
_anywhere_; only if it is #defined _here and now_.
Using this knowledge, you can now put the following in fastfile.c:

#define FFR_MAIN_FILE
#include "fastfile.h"

and that will be equivalent to

#define FFR_BUFFERSIZE 32768
unsigned char ffr_buffer[FFR_BUFFERSIZE];

and in database.c, which uses the fast file capabilities, you can do

#include "fastfile.h"

_without_ the #define FFR_MAIN_FILE first, and that will result in

#define FFR_BUFFERSIZE 32768
extern unsigned char ffr_buffer[FFR_BUFFERSIZE];

A definition in one file only, a declaration everywhere else. Precisely
what you should have. And all that using only a single line to
define/declare the object, plus a single macro definition to make the
difference.

If that sounds like a lot of work to avoid just a single retyped line,
it is. But it can easily be worth it if a library defines a couple more
objects; the advantages add up, but the extra trouble stays the same.

Richard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top