Can I put constant string arrays in a header file

T

Tobias Blass

Hi,
I'm currently implementing a string lookup table, mapping error numbers to
human readable strings. So my question is: Can I put the strings in a header
file, or will I end up with N exact copies of this array (N being the number
of inclusions of this file). I use gcc on Linux, so if anyone can tell me that
gcc will optimise N-1 copies of the array out I'm happy with that, too.

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif
 
I

Ian Collins

Hi,
I'm currently implementing a string lookup table, mapping error numbers to
human readable strings. So my question is: Can I put the strings in a header
file, or will I end up with N exact copies of this array (N being the number
of inclusions of this file).

There will only be one copy.
 
K

Keith Thompson

Ian Collins said:
There will only be one copy.

The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)

Here's what I came up with. Note that it makes errstrs a pointer
rather than an array, but you can still refer to errstrs[0],
errstrs[1], etc. If you make it an array, it means that client code
can figure out how many elements there are, but it also means that
you have to specify the length in the declaration and recompile if
it changes.

Note that identifiers starting with 'E' are reserved, so I changed
the include guard macro name.

/* 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;

Yes, that's a lot of "const"s. It means that client code
can't modify the top-level pointer, any of the pointers in the
error_strings array, or any of the error strings themselves.

It might be simpler for error.h to declare a function that takes an int
and returns a pointer to the error message.
 
I

Ian Collins

Ian Collins said:
There will only be one copy.

The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)

Bugger, I didn't notice there was one to few consts! As you say, one
good reason not to do this!
 
B

Ben Bacarisse

Ian Collins said:
Ian Collins said:
On 04/ 9/11 10:43 AM, Tobias Blass wrote:
Hi,
I'm currently implementing a string lookup table, mapping error numbers to
human readable strings. So my question is: Can I put the strings in a header
file, or will I end up with N exact copies of this array (N being the number
of inclusions of this file).

There will only be one copy.

The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)

Bugger, I didn't notice there was one to few consts! As you say, one
good reason not to do this!

I think you may be confusing C and C++ here. The presence of a "top
level" const makes all the difference in C++ (well it did that time I
read a C++ reference manual) but it does not make any difference to how
the objects may or may not get merged in a C program.

I can't find any text that describes what it means in C to have more
that one external definition for an object. I thought there was a "one
definition rule" but if so I can't find it at the moment.
 
K

Keith Thompson

Ian Collins said:
Ian Collins said:
On 04/ 9/11 10:43 AM, Tobias Blass wrote:
I'm currently implementing a string lookup table, mapping error
numbers to human readable strings. So my question is: Can I put the
strings in a header file, or will I end up with N exact copies of
this array (N being the number of inclusions of this file).

There will only be one copy.

The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)

Bugger, I didn't notice there was one to few consts! As you say, one
good reason not to do this!

Hmm? I wasn't referring to the consts, just that defining objects
in header files is a bad idea. It's better to *declare* the
object in the header and *define* it in the corresponding .c file.
(Or, perhaps even better, to declare a function that gives you the
same information.)
 
I

Ian Collins

Ian Collins said:
The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)

Bugger, I didn't notice there was one to few consts! As you say, one
good reason not to do this!

I think you may be confusing C and C++ here. The presence of a "top
level" const makes all the difference in C++ (well it did that time I
read a C++ reference manual) but it does not make any difference to how
the objects may or may not get merged in a C program.

Um, right again. I think I should confine myself to something
productive like unclogging more drains this weekend.

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

Will result in one and only one instance of errstrs in C++. I wonder
why this was never added to C? I can't imagine it breaking any existing
code.
 
M

Morris Keesan

Ian Collins said:
There will only be one copy.

The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)

Here's what I came up with. Note that it makes errstrs a pointer
rather than an array, but you can still refer to errstrs[0],
errstrs[1], etc. If you make it an array, it means that client code
can figure out how many elements there are, but it also means that
you have to specify the length in the declaration and recompile if
it changes.

Note that identifiers starting with 'E' are reserved, so I changed
the include guard macro name.

/* 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;

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?

(Or, as was suggested elsewhere in this thread, make the whole thing
static, and provide a function to access the members of the array).
 
K

Keith Thompson

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".)

#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.
(Or, as was suggested elsewhere in this thread, make the whole thing
static, and provide a function to access the members of the array).

Yes.
 
B

Barry Schwarz

Hi,
I'm currently implementing a string lookup table, mapping error numbers to
human readable strings. So my question is: Can I put the strings in a header
file, or will I end up with N exact copies of this array (N being the number
of inclusions of this file). I use gcc on Linux, so if anyone can tell me that
gcc will optimise N-1 copies of the array out I'm happy with that, too.

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

If the header is included at file scope, then per 6.9.2 the
declaration of errstrs is also an "external definition".

If the header is included in multiple translation units, errstrs will
be defined in each object module.

Paragraph 6.9-5 requires that there be only one such definition.

Chances are the linker will complain when you attempt to combine all
the object modules into a single program.
 
T

Tobias Blass

Ian Collins said:
There will only be one copy.

The code in question is:

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

There won't be one copy per inclusion, because of the header guards,
but you don't want a *definition* in a header file. If the header
is included in two or more translation units that are linked into
a single program, you might get multiple copies of the strings,
or you might just get a link error. (I'm not quite sure what the
language permits or requires here, but don't do it.)
That's what I meant by "inclusion". I've got some .c files, each of them
includes error.h, but I don't want to have 5 Instances of the same table.
Note that identifiers starting with 'E' are reserved, so I changed
the include guard macro name.
Thanks for the note, I didn't know that
/* 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.
(The const thing is a good hint, I find it rather difficult to remember which
const has which effect */
It might be simpler for error.h to declare a function that takes an int
and returns a pointer to the error message.
That's possibly true, but I think it's easier to change an entry in a table
than to change it in a function.
 
T

Tobias Blass

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};
And the Compiler knows arr should have 5 elements, so why is it possible with
int's but not with pointers?
(I have to admit that I haven't tested error.h because I just have written this
array so far when this question came up )
#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.
(Or, as was suggested elsewhere in this thread, make the whole thing
static, and provide a function to access the members of the array).

Yes.
 
I

Ian Collins

That's possibly true, but I think it's easier to change an entry in a table
than to change it in a function.

Why?

It's the no less typing and a lot less rebuilding.
 
T

Tobias Blass

Hi,
I'm currently implementing a string lookup table, mapping error numbers to
human readable strings. So my question is: Can I put the strings in a header
file, or will I end up with N exact copies of this array (N being the number
of inclusions of this file). I use gcc on Linux, so if anyone can tell me that
gcc will optimise N-1 copies of the array out I'm happy with that, too.

/* error.h */
#ifndef ERROR_H
#define ERROR_H

const char *errstrs[]={
"first error",
"second error",
...
};
#endif

If the header is included at file scope, then per 6.9.2 the
declaration of errstrs is also an "external definition".

If the header is included in multiple translation units, errstrs will
be defined in each object module.

Paragraph 6.9-5 requires that there be only one such definition.

Chances are the linker will complain when you attempt to combine all
the object modules into a single program.
Ok thanks, that answers my question. I'll put it in a .c file and declare it as
extern
 
B

Ben Bacarisse

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};
And the Compiler knows arr should have 5 elements, so why is it possible with
int's but not with pointers?

There is no difference between ints and pointers in this regard but the
problem is not in error.c but the error.h. Translation units that
include it (other than error.c) will fall fowl of 6.9.2 p2.

The declaration of error_strings in error.h is a "tentative definition"
and will cause the translation unit to have a definition of
error_strings as an array of one element initialised to a null pointer.
(I have to admit that I haven't tested error.h because I just have written this
array so far when this question came up )
#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.

That's mandated by the standard as far as I can see. Conforming
implementations can't behave differently.

<snip>
 
B

Ben Bacarisse

Barry Schwarz said:
Paragraph 6.9-5 requires that there be only one such definition.

Thanks for this. I knew it was there but for some strange reason I could
not find it last night despite much searching (and it's not as if it is
not exactly where one would expect it to be!).

<snip>
 
M

Morris Keesan

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.
#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.

Or by not needing to know the size of the array.
const char * const err_strings = {
"error 1",
"error 2",
...
"error n",
NULL
};

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;
 
M

Morris Keesan

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".)
I thought the Compiler can calculate the size of a constant array by
itself?
You can write
int arr[]={1,2,3,4,5};
And the Compiler knows arr should have 5 elements, so why is it possible
with int's but not with pointers?

The problem is not because of pointers. In error.c, where you define
the array with an initializer, the compiler is calculating the size of
the array. But code that includes "error.h", with just the declaration
"char *error_strings[]", doesn't have access to that size.
 
M

Morris Keesan

....
That's possibly true, but I think it's easier to change an entry in a
table than to change it in a function.

But you can keep your strings in a table and still provide access to them
only through a function:

static const char *const error_strings = {
"error 1",
"error 2",
...
"error n"
};

const char *error_message(unsigned int err_number)
{
if (err_number >= (sizeof error_strings / sizeof error_strings[0]))
return NULL; // error number out of bounds
else
return error_strings[err_number];
}
 
M

Morris Keesan

.
Or by not needing to know the size of the array.
const char * const err_strings = {

Oops. I obviously meant to type err_strings[]
"error 1",
"error 2",
...
"error n",
NULL
};

But I wasn't thinking clearly enough here. For this sort of application,
where you just want to index into the array, having a NULL to flag the
end of the array isn't particularly useful. You really do want to know the
size of the array, so that you can catch out-of-bounds indexes. But hiding
the array and providing just an extern pointer to it doesn't address that
issue. It merely keeps the naive programmer from attempting to find the
size.
 

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,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top