Search code examples
cstandardsstandards-compliancec89

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


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?


Solution

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