Conditional declaration (A matter of style)

  • Thread starter Anders Wegge Keller
  • Start date
A

Anders Wegge Keller

When writing modular code, I sometimes want to make a particular data
structure read-only outside the module that need to write to it. The
most obvious example is global options, that is only written to by the
command-line parsing module, and should be const elsewhe. However,
getting this declaration to change depending on where it's seen
becomes a bit unsightly, so I wonder if there is a better way of doing
this:


<<options.h>>

....

typedef struct {

int option_1;
int option_2;
...
int option_N;
} Options_t;

#ifdef OPTION_RW
extern Options_t *options;
#else
extern const Options_t * const options;
#endif



<<options.c>>

#define OPTIONS_RW
#include <options.h>
#undefine OPTIONS_RW

....

options = malloc (sizeof Options_t);

....



What I'd like to do is getting rid of the OPTIONS_RW definition in
options.c. Creating two extra header files (options_public.h and
options_private.h), and keeping the declaration in those two isn't an
improvement - in my opinion - on the definition above.
 
B

Ben Bacarisse

Anders Wegge Keller said:
When writing modular code, I sometimes want to make a particular data
structure read-only outside the module that need to write to it. The
most obvious example is global options, that is only written to by the
command-line parsing module, and should be const elsewhe. However,
getting this declaration to change depending on where it's seen
becomes a bit unsightly, so I wonder if there is a better way of doing
this:


<<options.h>>

...

typedef struct {

int option_1;
int option_2;
...
int option_N;
} Options_t;

#ifdef OPTION_RW
extern Options_t *options;
#else
extern const Options_t * const options;
#endif



<<options.c>>

#define OPTIONS_RW
#include <options.h>
#undefine OPTIONS_RW

...

options = malloc (sizeof Options_t);

...



What I'd like to do is getting rid of the OPTIONS_RW definition in
options.c. Creating two extra header files (options_public.h and
options_private.h), and keeping the declaration in those two isn't an
improvement - in my opinion - on the definition above.

You might make the pointer static:

static Options_t *options;

and provide a one-line function

const Options_t *get_options_ptr(void) { return options; }

You don't get two header files, you get just one but that's an advantage
isn't it?
 
A

Anders Wegge Keller

Ben Bacarisse said:
You might make the pointer static:

static Options_t *options;

and provide a one-line function

const Options_t *get_options_ptr(void) { return options; }

You don't get two header files, you get just one but that's an advantage
isn't it?

Much better. Thank you for that suggestion.
 
K

Kaz Kylheku

When writing modular code, I sometimes want to make a particular data
structure read-only outside the module that need to write to it. The
most obvious example is global options, that is only written to by the
command-line parsing module, and should be const elsewhe. However,
getting this declaration to change depending on where it's seen
becomes a bit unsightly, so I wonder if there is a better way of doing
this:

If you define an external object unqualified, and in other translation units
use const declarations to refer to it, it is, formally, undefined behavior.

Paragraph 2 in "6.2.7 Compatible type and composite type" [ISO/IEC 9899:1999]
says "All declarations that refer to the same object or function shall have
compatible type; otherwise, the behavior is undefined."

A const-qualified type is not compatible with a qualified type:

Paragraph 9 in "6.3.7 Type Qualifiers" [ISO/IEC 9899:1999] says "For two
qualified types to be compatible, both shall have the identically qualified
version of a compatible type."

You can access an unqualified object through a const-qualified lvalue, but that
is not quite the same thing because you're not declaring that the underlying
object is const.

Your compiler and linker may let you get away with this, but one has to
question the wisdom of invoking undefined behavior only to get better error
checking for some convention in your program.

I think this can work if you use it for checking only during compilation,
and not for actually building the program. Lying to the compiler about the
type of an external object seems harmless if the translation units are never
combined into a program. The rule in 6.2.7 cannot possibly apply to
translation units before it is indicated that they are to be one program.
#ifdef OPTION_RW
extern Options_t *options;
#else
extern const Options_t * const options;
#endif

So you're invoking undefined behavior here by qualifying the pointer.

(But the const on the Options_t type is fine because that just determines
the type used for accessing the options structure without declaring
it to be that type.)

But if you're willing to undergo the inconvenience of the pointer, you might as
well have two pointers:

extern const Options_t *const popt; /* public, visible to all */

#ifdef OPTION_MODULE /* present if option-related module is being compiled */
extern Options_t *wpopt;
#endif

Then in the main options module:

#define OPTION_MODULE
#include "options.h"

const Options_t *const popt = &g_options;
Options_t *wpopt = &g_options;

Use wpopt in the implementation to write to the options. If the options
module is just one source file, wr_options can just be static (internal
linkage) inside it and so not mentioned in the header.

The point is that the options module does not have to use the same interface to
manipulate the options.
#define OPTIONS_RW
#include <options.h>
#undefine OPTIONS_RW

What, angle brackets to refer to your own header? That is foolery.
options = malloc (sizeof Options_t);

See, now you're lying to the translation units where options is declared const.
It's possible that the const declaration is believed and the compiler emits
code which caches the original null value.

An external, file scope const is really a constant. Not in the sense that
evaluating it is a constant expression (that is in C++ only) but in the
sense that it can be relied upon never to have two or more values over the
lifetime of the program.

There is no need to malloc global options. A static pointer can be initialized
to hold the address of a static structure.
You can build entire linked lists and trees that way, even ones with cycles.

Years ago I build a hyperlinked help system for a program written in C (which
had a GUI). The graph of linked help screens was assembled with static
definitions containing pointers to each other.

/* circular list */
struct node { struct node *next; } node_b; /* forward decl */
struct node node_a = { &node_b };
struct node node_b = { &node_a };
 

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,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top