Search code examples
cexternstorage-class-specifier

Why 'extern' storage class works differently in functions?


The following snippet works fine

extern int i;
int i;

int main(){
    return 0;
}

Here what I got is, 'i' is declared and then defined. Since there is only one definition so thats perfectly fine.

int main(){
    extern int i;
    int i;
    return 0;
}

Now, the above one gives the following error

new.cpp: In function ‘int main()’:
new.cpp:5:6: error: redeclaration of ‘int i’
  int i;
      ^
new.cpp:4:13: note: previous declaration ‘int i’
  extern int i;

Whats the problem here? Here also there is single definition of 'i'.


Solution

  • To understand the difference, you need to get familiar with a concept called tentative definition in C. To quote the C standard:

    C11, draft, §6.9.2, External object definitions

    A declaration of an identifier for an object that has file scope without an initializer, and without a storage-class specifier or with the storage-class specifier static, constitutes a tentative definition. If a translation unit contains one or more tentative definitions for an identifier, and the translation unit contains no external definition for that identifier, then the behavior is exactly as if the translation unit contains a file scope declaration of that identifier, with the composite type as of the end of the translation unit, with an initializer equal to 0.

    What you have in the first snippet is only a tentative definition for i. You can have as many tentative definitions for an object as you want (but only one definition is allowed):

    int i; // tentative definition
    int i; // tentative definition
    int i; // tentative definition
    
    int main(void) {
       return 0;
    }
    

    is valid.

    Here, i has external linkage and tentatively defined. If i is defined in somewhere in the same translation unit, then that'll be the actual definition of i. If there's no other definition of i is found in the translation unit, then this becomes the full definition as if it was defined like:

    int i = 0;
    
    int main(void) {
       return 0;
    }
    

    But the second snippet int i; is not a tentative definition. Only objects with external linkage can be defined tentatively. In second snippet, The declaration extern int i; says i is defined elsewhere with external linkage. But the next line int i; says i is defined with no linkage (local automatic variables do not have any linkage -- this is not a tentative definition). So there's a conflict of definition for i. Hence, the first one snippet is fine but second isn't.