Search code examples
cstaticlanguage-lawyerexternlinkage

Allowing "overriding" zero-initialized object with internal linkage


I'm designing a micro-framework for unit testing and want to be able to provide an ability for client to define a "test suite name". So I have the following header file called test_suite.h:

static const char *const test_suite_name;

static inline void run_all_tests(void){
    printf("Running ");
    if(!test_suite_name){
        printf("unnamed suite");
    } else {
        printf("%s suite", test_suite_name);
    }
    //run tests
}

The intention of this is to allow clients to "override" the test_suite_name as follows:

#include "test_suite.h"

extern const char *const test_suite_name = "suite1";

I think the behavior of such usage is well-defined since static const char *const test_suite_name; constitutes a tentative-definition and then extern const char *const test_suite_name = "suite1"; constitutes an external definition. There is no linkage disagreement since 6.2.2(p4):

For an identifier declared with the storage-class specifier extern in a scope in which a prior declaration of that identifier is visible,31) if the prior declaration specifies internal or external linkage, the linkage of the identifier at the later declaration is the same as the linkage specified at the prior declaration.

I ran some experiments:

  1. https://coliru.stacked-crooked.com:

Prints the following error message:

error: redefinition of 'const char* const suite_name'
 extern const char *const suite_name = "some suite";

DEMO

  1. https://ideone.com/:

Works completely fine with no warnings produced

DEMO

  1. gcc7.4.0 on my machine.

Produces warning:

warning: ‘test_suite_name’ initialized and declared ‘extern’

Question: Is the behavior of the code shown above well-defined?

I'm pretty sure that the behavior would be undefined if write the following:

#include "test_suite.h"

const char *const test_suite_name = "suite1"; //without extern

because of 6.2.2(p5)(emphasize mine):

If the declaration of an identifier for a function has no storage-class specifier, its linkage is determined exactly as if it were declared with the storage-class specifier extern. If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external.

So we would have linkage disagreement between static const char *const test_suite_name; with internal linkage and const char *const test_suite_name = "suite1"; with external linkage.


Solution

  • Use of static

    You may actually be able to use static instead of external. Making a quick test with gcc under Ubuntu:

    #include "test_suite.h"
    
    static const char *const test_suite_name = "huhu";
    
    int main() {
          run_all_tests();
          return 0;
    }
    

    If I compile with:

    gcc -Wall -Wpedantic -Wextra mytest.c -o mytest
    

    it gives as output:

    Running huhu suite
    

    Omitting static

    If you accidentally forget to specify static it should give a compile time error then. So if I change this line to:

    const char *const test_suite_name = "huhu";
    

    and try to compile it like this:

    gcc -Wall -Wpedantic -Wextra mytest2.c -o mytest2
    

    this error message will be displayed:

    mytest2.c:3:19: error: non-static declaration of ‘test_suite_name’ follows static declaration
     const char *const test_suite_name = "huhu";
                       ^~~~~~~~~~~~~~~
    In file included from mytest2.c:1:
    test_suite.h:3:26: note: previous declaration of ‘test_suite_name’ was here
     static const char *const test_suite_name;
    

    Since it is an error it is also output if you compile with:

    gcc mytest2.c -o mytest2
    

    Screenshot of Error Message

    Screenshot with Error Message