Say I have a compilation unit file1.c, which declares a file-scope variable like so:
int my_variable = 12;
Then, in another compilation unit file2.c, I create an extern declaration for that variable, but declare it as const
:
extern const int my_variable;
This will compile and work fine with gcc , using -Wall -Wextra -ansi -pedantic
. However, the C89 standard says For two qualified types to be compatible, both shall have the identically qualified version of a compatible type. Adding const
to the declaration adds a restriction rather than avoiding one. Is this safe and valid C? What would be the best practice in setting this up with header files?
It's clearly undefined as the declarations don't match. As you noted, const int
and int
aren't compatible types. A diagnostic is required only if they appear in the same scope.
It isn't safe in practice either, consider
$ cat test1.c
#include <stdio.h>
extern const int n;
void foo(void);
int main(void) {
printf("%d\n", n);
foo();
printf("%d\n", n);
}
$ cat test2.c
int n;
void foo(void) { ++n; }
$ gcc -std=c99 -pedantic test1.c test2.c && ./a.out
0
1
$ gcc -O1 -std=c99 -pedantic test1.c test2.c && ./a.out
0
0
Gcc assumes that n
isn't changed by foo()
when optimizing, because it may assume the definition of n
is of a compatible type, thus const
.
Chances are that you get the expected behaviour with also volatile
-qualifying n
in test1.c
, but as far as the C standard is concerned, this is still undefined.
The best way I can think of to prevent the user from accidentally modifying n
is to declare a pointer to const
, something along
int my_real_variable;
const int *const my_variable = &my_real_variable;
or perhaps some macro
#define my_variable (*(const int *)&my_variable)
With C99, my_real_variable
can be avoided via a compound literal:
const int *const my_variable_ptr = &(int){ 12 };
It would be legal to cast away const
here (as the int
object itself isn't const
), but the cast would be required, preventing accidental modification.
In this case the definition and declaration appear in separate translation units, so the compiler cannot perform any type or qualifier checks. The symbols are resolved by the linker and in this case it seems that the linker is not enforcing this qualifier matching.
If the definition and the declaration appeared in the same translation unit; for example if you placed the extern
declaration in a header file and included it in file1.c, then I would imagine that the compiler would complain. By placing them in separate translation units, the compiler never sees both so cannot perform the check.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With