Looking for opinions on a C technique

J

Jack Klein

I'm looking for opinions on a C technique I, and others, have used
successfully in the past. While some people swear by, apparently
others swear at it.

Assume a part of a program too large to fit comfortably in a single
source file, call it a "module". Let's call it "module A".

Also assume for various reasons module A needs a private data store
with static storage duration, accessible from files in more than one
translation unit. For a number of reasons it is necessary to have the
stored objects directly accessible where they are needed, so accessor
functions are ruled out.

Which leaves objects with external linkage.

To about name conflicts, all of module A's objects have names starting
with "moda_". Since they are referenced by name in several source
files, they must be defined in one, and declared as external in the
others.

Possibility 1:

They are just defined in one source file and matching extern
declarations are added to the others.

The big downside of this the files getting out of sync as
modifications are made. Maybe caught by the linker, maybe undefined
behavior at run time.

Possibility 2:

They are declared in a header file, and defined in a separate source
file that contains nothing else but the definitions.

Easier to keep in sync, anyone modifying such an object changes the
header file, then copies and pastes it into the C file, finally does a
search and replace of "extern" with nothing in the C file.

Less chance of getting out of sync, but still possible.

Possibility 3, which is possibility 2 automated with a little help
from the preprocessor:

File moda_vars.h:

#ifndef MODA_VARS_H
#define MODA_VARS_H

#ifdef DEFINE_MODA_VARS
#define MODULE
#else
#define MODULE extern
#endif

MODULE int moda_x;
MODULE int moda_y;

/* etcetera */

#endif /* MODA_VARS_H */
<eof>

File moda_vars.c:

#define DEFINE_MODA_VARS
#include "moda_vars.h"
<eof>

Files moda_code_1.c, moda_code_2.c, etc...
#include "moda_vars.h"
/* no definition of DEFINE_MODA_VARS */

The major point of option 3 is that there is no way that the
definitions of the objects and the external declarations can ever get
out of sync. Assuming a typical (and off-topic) build tool that
handles dependencies, any change to the header file forces a fresh
build of all the source files, definer and users.

In the situations where I've used this technique there is generally no
need to provide other than the default 0 initialization of static
objects, although there are somewhat more elaborate macros that allow
initializer expressions to be visible to the compiler in the defining
file and invisible in those that see the extern declarations.

I am not asking for opinions on whether or not the use of such shared
objects is a good idea, although this being comp.lang.c I'm sure I'll
get some.

What I am interested in is opinions on using a technique like
possibility 3, or any experiences anyone might care to share with
using something like this.

--
Jack Klein
Home: http://JK-Technology.Com
FAQs for
comp.lang.c http://www.eskimo.com/~scs/C-faq/top.html
comp.lang.c++ http://www.parashift.com/c++-faq-lite/
alt.comp.lang.learn.c-c++ ftp://snurse-l.org/pub/acllc-c++/faq
 
M

Mark Gordon

I'm looking for opinions on a C technique I, and others, have used
successfully in the past. While some people swear by, apparently
others swear at it.

Assume a part of a program too large to fit comfortably in a single
source file, call it a "module". Let's call it "module A".

Also assume for various reasons module A needs a private data store
with static storage duration, accessible from files in more than one
translation unit. For a number of reasons it is necessary to have the
stored objects directly accessible where they are needed, so accessor
functions are ruled out.

Which leaves objects with external linkage.

To about name conflicts, all of module A's objects have names starting
with "moda_". Since they are referenced by name in several source
files, they must be defined in one, and declared as external in the
others.

Possibility 1:

They are just defined in one source file and matching extern
declarations are added to the others.

The big downside of this the files getting out of sync as
modifications are made. Maybe caught by the linker, maybe undefined
behavior at run time.

I would never do this.
Possibility 2:

They are declared in a header file, and defined in a separate source
file that contains nothing else but the definitions.

Easier to keep in sync, anyone modifying such an object changes the
header file, then copies and pastes it into the C file, finally does a
search and replace of "extern" with nothing in the C file.

Less chance of getting out of sync, but still possible.

If you include the header file in the file that defines the variables,
the compiler will tell you about type miss-matches and the linker will
tell you if a variable is used but not defined.
Possibility 3, which is possibility 2 automated with a little help
from the preprocessor:

File moda_vars.h:

#ifndef MODA_VARS_H
#define MODA_VARS_H

#ifdef DEFINE_MODA_VARS
#define MODULE
#else
#define MODULE extern
#endif

MODULE int moda_x;
MODULE int moda_y;

/* etcetera */

#endif /* MODA_VARS_H */
<eof>

File moda_vars.c:

#define DEFINE_MODA_VARS
#include "moda_vars.h"
<eof>

Files moda_code_1.c, moda_code_2.c, etc...
#include "moda_vars.h"
/* no definition of DEFINE_MODA_VARS */

The major point of option 3 is that there is no way that the
definitions of the objects and the external declarations can ever get
out of sync. Assuming a typical (and off-topic) build tool that
handles dependencies, any change to the header file forces a fresh
build of all the source files, definer and users.

Used (by the original author) on code I am maintaining. Of course, you
have to make sure DEFINE_MODA_VARS is only defined in out source file.
In the situations where I've used this technique there is generally no
need to provide other than the default 0 initialization of static
objects, although there are somewhat more elaborate macros that allow
initializer expressions to be visible to the compiler in the defining
file and invisible in those that see the extern declarations.

I am not asking for opinions on whether or not the use of such shared
objects is a good idea, although this being comp.lang.c I'm sure I'll
get some.

What I am interested in is opinions on using a technique like
possibility 3, or any experiences anyone might care to share with
using something like this.

Not done anything clever for non-0 initialisations, but option 3 has
worked for a long time on the project I'm now involved with and we have
not had any problems with it.

<OT>
You may also want to look at "partial linking" of the group of files
done such that only the variables you want to expose have external
linkage beyond the library. This is mainly useful if the library is to
be distributed as a binary and you don't trust the users of the library
not to mess with your internal state.
</OT>
 
D

Dan Pop

In said:
Possibility 2:

They are declared in a header file, and defined in a separate source
file that contains nothing else but the definitions.

Easier to keep in sync, anyone modifying such an object changes the
header file, then copies and pastes it into the C file, finally does a
search and replace of "extern" with nothing in the C file.

Less chance of getting out of sync, but still possible.

Possibility 2 improved:

They are declared in a header file, and defined in a separate source
file that includes the header file and contains nothing else but the
definitions.

No chance of getting out of sync without compiler complaints.

It's the same basic rule that says that the implementation of strcpy()
should also include <string.h>.

Dan
 
D

Dann Corbit

If I have a bunch of stuff that is both static and needed by a small
collection of files, I will usually store it in a struct. I think it makes
usage much more obvious.

E.g.

struct graphics_data {
struct location loc;
struct viewport wind;
struct palette pal;
....
} my_shared_graphics_data;

struct mapping_data {
enum transformation transform;
enum ellipse_of_reference ellipse;
double scale;
double phi;
double rho;
....
} my_shared_mapping_data;

something like that. It's not very often that I do something like that
because INEVITABLY[*] I need to multithread the program at some point and
then I'm really, really sorry I made static variables.

[*] reminds me of the public television child's program "Zoom" which had a
running skit called "Love of Chair."
 

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

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top