Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it valid to treat an extern global as const when the definition is not const? [duplicate]

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?

like image 256
Tom Galvin Avatar asked Dec 29 '14 12:12

Tom Galvin


2 Answers

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.

like image 114
mafso Avatar answered Sep 20 '22 05:09

mafso


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.

like image 44
Clifford Avatar answered Sep 22 '22 05:09

Clifford