Can I put constant string arrays in a header file

K

Keith Thompson

Tobias Blass said:
/* error.h */
#ifndef H_ERROR
#define H_ERROR

const char *const *const errstrs;

#endif

/* error.c */
#include "error.h"

static const char *const error_strings[] = {
"first error",
"second error"
};

const char *const *const errstrs = error_strings;
I wanted to do that first, but I think it's quite ugly to have a separate C file
just for the table, so I wanted to ask whether it's possible to do that in the
header file instead.

I believe the answer to your question is no.

It's just one extra file; it shouldn't be that big a deal.

If you define the object in a header, then you have one instance
of the object for each translation unit (.c file) that includes the
header. If you have only one .c file for your program, you can get
away with it, but any sizeable program will have multiple .c files.

Generally, .h files are for declarations, and .c files are for
definitions. (I'm speaking rather loosely here; we've had long
arguments about the meaning of "definition" which I'd rather not
repeat.)
(The const thing is a good hint, I find it rather difficult to remember which
const has which effect */

The "cdecl" program is very handy for this kind of thing; I used
it to figure out how to declare error_strings above.
That's possibly true, but I think it's easier to change an entry in a table
than to change it in a function.

You're just changing an entry in a table either way. The function
can simply return an element of the table. It can also do bounds
checking, and perhaps return an empty string or a null pointer
for error_string(9999).

And the error.h file doesn't change when you add or change
error messages, so anything that depends on it doesn't have to be
recompiled; you can just recompile error.c and relink. (If you're
using reasonable build tools, this just means that rebuilding your
program will be a bit faster.)
 
K

Keith Thompson

Tobias Blass said:
Morris Keesan said:
This seems overly complex. Is there any good reason not to have

/* error.c */
const char *const error_strings[] = { "first error", "second error" };

/* error.h */
extern const char *const error_strings[];

I.e. why not just make error_strings a global array, and expose the
array name as an extern? Why go to the trouble of declaring a separate
pointer?
I actually tried that. The problem is that any code that includes
"error.h" has no way of knowing how big the array is. (More precisely,
the compiler has no way of knowing this when it's compiling a source
file that includes "error.h".)
I thought the Compiler can calculate the size of a constant array by itself?
You can write
int arr[]={1,2,3,4,5};
[...]

It can if it can see the initializer. When you compile something
that includes error.h, there's no initializer for error_strings,
and the compiler has no way of knowing how big it is.
 
K

Keith Thompson

Morris Keesan said:
Morris Keesan said:
[snip]
This seems overly complex. Is there any good reason not to have

/* error.c */
const char *const error_strings[] = { "first error", "second error" };

/* error.h */
extern const char *const error_strings[];

I.e. why not just make error_strings a global array, and expose the
array name as an extern? Why go to the trouble of declaring a separate
pointer?

I actually tried that. The problem is that any code that includes
"error.h" has no way of knowing how big the array is. (More precisely,
the compiler has no way of knowing this when it's compiling a source
file that includes "error.h".)

But hiding the array completely, and only exposing a pointer to it, doesn't
solve that problem.

No, and it wasn't intended to. But it makes it a problem for the
programmer (who can't write sizeof error_strings / sizeof
error_strings[0] to determine the number of elements) rather than for
the compiler (which is going to reject, or at least warn about, the
declaration).

There has to be some mechanism that lets you safely retrieve elements of
the array without going out of bounds. Perhaps you have a function that
returns an error code, and you're only supposed to use the result of
that function to index in to the array; there's no good reason to index
into the array using arbitrary values. Or perhaps the header declares a
constant that tells you how big the array is.

Since this is an array of error messages, you don't usually *care* how
many there are; you just want the error message for a given code.

But the more I consider this, the more I think that just exporting a
function is the best approach. (For one thing, it makes localization
easier; you can have it return a value from a different array
depending on the currently defined language.)
#include "error.h"
...
printf("error_strings has %d elements\n",
(int)sizeof error_strings / sizeof error_strings[0]);

gcc gave me a warning that it was assuming the size of the array is 1.
Other compilers may behave differently.

You can avoid that by declaring the size of the array, but then you have
to recompile whenever you add a new error string.

I don't understand this last comment. You have to recompile whenever you
add a new error string, anyway, but you only have to recompile the module
that has the array definition.

Right, that's what I meant.
Or by not needing to know the size of the array.
const char * const err_strings = {
"error 1",
"error 2",
...
"error n",
NULL
};

That's only useful if you want to iterate over the array for some
reason.
or
/* error.c */
const char * const err_strings = { "err1", "err2", ... "errn" };
const size_t n_errs = sizeof err_strings/sizeof err_strings[0];

/* error.h */
extern const char * const err_strings[];
extern const size_t n_errs;
 

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
474,085
Messages
2,570,597
Members
47,220
Latest member
AugustinaJ

Latest Threads

Top