Thread: Real example of extern variable

  1. #1
    Registered User
    Join Date
    Nov 2019
    Posts
    54

    Real example of extern variable

    I want to understand the external variable by doing an experiment I searched on the internet and found this link External variable - Wikipedia

    Now I made two files, file1.c and file2.c

    Code:
    //GCC compiler 
    #include<stdio.h>
    
      int Global_Variable;
    
    
      // Function prototype   
      void SomeFunction(void);        
    
    
      int main(void) 
      {
        Global_Variable = 1;
        
        SomeFunction();
       printf ("%d", Global_Variable);
        
        return 0;
      }
    Code:
      extern int Global_Variable;  
    
      // Function header (definition)
      void SomeFunction(void) {       
        ++Global_Variable;
      }
    if I use extern keyword result would be 2 and If I remove extern result would be 1
    I do not understand how this work
    Last edited by Player777; 02-07-2020 at 12:28 AM.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    If you omit the extern keyword, then I would expect a link error: the identifier named Global_Variable that is declared at file scope has external linkage with or without the extern keyword, but without the extern keyword the declaration of Global_Variable is also a definition, and consequently your program defines the same Global_Variable object twice, which is an error. With the extern keyword used in one of the two declarations, that declaration ceases to be a definition, so all is well as there is only one definition of Global_Variable.

    If you had turned file2.c into a header that was included in file1.c, then omitting the extern keyword still makes that a definition, but it would become a tentative definition in that context, and hence permitted.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Nov 2019
    Posts
    54
    Quote Originally Posted by laserlight View Post
    If you omit the extern keyword, then I would expect a link errorermitted.
    I have mingw and windows

    gcc -o example file1.c file2.c
    example
    2

    Code:
       int Global_Variable;  
    
      // Function header (definition)
      void SomeFunction(void) {       
        ++Global_Variable;
      }
    I don't understand how it work

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    I just checked with my copy of gcc, and I'm surprised: gcc does permit this, even though it goes against the standard. My guess is that it treats the repeated definition as only a declaration, i.e., so it is equivalent to using the extern keyword for one of the declarations... or perhaps it interprets the tentative definition more loosely, e.g., as applying to the program as a whole rather than to each translation unit.

    Having said that, I get the output of 2 in both cases. I do not see how you can get the output of 1 unless you replaced the extern with static.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  5. #5
    Registered User
    Join Date
    Dec 2017
    Posts
    864
    Neither gcc nor clang (with -std=c99 -Wall -W -pedantic) gives an error or warning.
    Code:
    // file1.c ///////////////////////////////
    #include <stdio.h>
     
    int Global_Variable;
     
    void SomeFunction(void);
     
    int main(void)
    {
        printf("%d\n", Global_Variable);  // prints 0
        SomeFunction();
        printf("%d\n", Global_Variable);  // prints 99
        return 0;
    }
    
    
    // file2.c ///////////////////////////////
    int Global_Variable;  
     
    void SomeFunction(void) {       
        Global_Variable = 99;
    }

    The only way to get them to complain is if you add an initializer to both of them.
    The world hangs on a thin thread, and that is the psyche of man. - Carl Jung

  6. #6
    misoturbutc Hodor's Avatar
    Join Date
    Nov 2013
    Posts
    1,742
    I get 2 in both cases as well. I'm also quite surprised that it's not an error. But... I think it's something to do with 6.9#4 of the standard

    As discussed in 5.1.1.1, the unit of program text after preprocessing is a translation unit, which consists of a sequence of external declarations. These are described as ''external'' because they appear outside any function (and hence have file scope). As discussed in 6.7, a declaration that also causes storage to be reserved for an object or a function named by the identifier is a definition
    and 6.9#5:

    An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof or _Alignof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.(161)
    The part I've made red is the part that I think is applicable but I'm not sure because I expected an error, but... so it's both a definition and declaration? The wording is confusing

  7. #7
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    Quote Originally Posted by john.c
    The only way to get them to complain is if you add an initializer to both of them.
    That aligns with my thinking that they are interpreting the tentative definition more loosely: with the initialiser the declaration ceases to be a tentative definition.

    EDIT:
    Quote Originally Posted by Hodor
    so it's both a definition and declaration?
    All definitions are also declarations, so that isn't surprising.

    In this case the identifier is used in an expression in both translation units, so the applicable part is: "somewhere in the entire program there shall be exactly one external definition for the identifier".
    Last edited by laserlight; 02-07-2020 at 01:47 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  8. #8
    misoturbutc Hodor's Avatar
    Join Date
    Nov 2013
    Posts
    1,742
    Quote Originally Posted by laserlight View Post
    That aligns with my thinking that they are interpreting the tentative definition more loosely: with the initialiser the declaration ceases to be a tentative definition.

    EDIT:

    All definitions are also declarations, so that isn't surprising.

    In this case the identifier is used in an expression in both translation units, so the applicable part is: "somewhere in the entire program there shall be exactly one external definition for the identifier".
    Yeah, that's true. I guess what's confusing me is the interpretation of 6.9#5 in combination with 5.1.1.1. I guess as long as the extern object is not used or initialised it's up to the compiler to ensure there is only one, not me (I thought it was up to me but apparently not). Dunno. But even compiling and linking the .o files separately (using ld) works

  9. #9
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    If the object is not used, then the other part of what you quoted comes into play: "there shall be no more than one", i.e., you don't even have to define the object despite having declared it.

    Quote Originally Posted by Hodor
    it's up to the compiler to ensure there is only one, not me (I thought it was up to me but apparently not).
    It is up to you to ensure that there is only one definition: but then if you don't do it, we're technically in the realm of undefined behaviour. So while I'd like to argue that the compiler/linker is standard non-conforming as there was no diagnostic, in truth it is the program that is non-conforming, resulting in undefined behaviour by violating a "shall" clause, and in the face of undefined behaviour the compiler/linker is not required to issue a diagnostic.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  10. #10
    misoturbutc Hodor's Avatar
    Join Date
    Nov 2013
    Posts
    1,742
    Quote Originally Posted by laserlight View Post
    It is up to you to ensure that there is only one definition: but then if you don't do it, we're technically in the realm of undefined behaviour. So while I'd like to argue that the compiler/linker is standard non-conforming as there was no diagnostic, in truth it is the program that is non-conforming, resulting in undefined behaviour by violating a "shall" clause, and in the face of undefined behaviour the compiler/linker is not required to issue a diagnostic.
    You won't get an argument from me because I thought it should be either an error or UB as well, but even Turbo C allows it. clang allows it. gcc allows it. splint static analysis shows no warning. clang static analysis shows nothing. cppcheck static analysis shows nothing. Either all these compilers and tools have misinterpreted the C standard or I have. I'd say it's more likely me.

    So I guess the question is who is responsible for not violating the shall clause? It seems to me that it's the compiler's responsibility for the two files above. Otherwise surely something would at least give a warning.

  11. #11
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    Quote Originally Posted by Hodor
    So I guess the question is who is responsible for not violating the shall clause? It seems to me that it's the compiler's responsibility for the two files above. Otherwise surely something would at least give a warning.
    A "shall" clause is a requirement on a program, the violation of which results in undefined behaviour. A conforming compiler must accept conforming programs, but isn't required to accept non-conforming programs, but can do so.

    Hence, that you get no diagnostic, not even a warning, doesn't mean that you misinterpreted the standard. The standard is clear that there shall be only one definition in the entire program. My guess as stated above is that there's no cost to treating this like a tentative definition that applies to the entire program rather than per translation unit, so compiler authors have chosen to go this route rather than worry about getting programmers to fix non-conforming edge-case programs.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  12. #12
    misoturbutc Hodor's Avatar
    Join Date
    Nov 2013
    Posts
    1,742
    Quote Originally Posted by laserlight View Post
    A "shall" clause is a requirement on a program, the violation of which results in undefined behaviour. A conforming compiler must accept conforming programs, but isn't required to accept non-conforming programs, but can do so.

    Hence, that you get no diagnostic, not even a warning, doesn't mean that you misinterpreted the standard. The standard is clear that there shall be only one definition in the entire program. My guess as stated above is that there's no cost to treating this like a tentative definition that applies to the entire program rather than per translation unit, so compiler authors have chosen to go this route rather than worry about getting programmers to fix non-conforming edge-case programs.
    Ok, yes, it's treated as a tentative a definition.

    6.9.2

    1 If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.

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

    3 If the declaration of an identifier for an object is a tentative definition and has internal linkage, the declared type shall not be an incomplete type.
    Based on that I would say there is no UB because it says right there how the compiler should behave when there is no initializer etc. But anyway, I wouldn't write code like that because it's confusing and bound to cause problems, because of programmer error, even if there is no UB.

  13. #13
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    Quote Originally Posted by Hodor
    Based on that I would say there is no UB because it says right there how the compiler should behave when there is no initializer etc.
    Notice that tentative definitions are coalesced into a single definition per translation unit, not across the entire program.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  14. #14
    misoturbutc Hodor's Avatar
    Join Date
    Nov 2013
    Posts
    1,742
    Quote Originally Posted by laserlight View Post
    Notice that tentative definitions are coalesced into a single definition per translation unit, not across the entire program.
    Yeah, you're right.

    The crux of the "problem" seems to be 6.9.2p2

    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.
    In particular this means that 6.9.2p1 is then invoked (because 6.9.2p2 says there is an implicit initializer in this circumstance)

    6.9.2p1
    If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.
    6.9.2p1 is, because of 6.9.2p2, true (it has an implied initializer).

    Your note about per translation unit is of course applicable as well and I think I referenced that earlier. In summary I still don't think there is any UB occurring, but the example is convoluted and kind of confusing (to me). There might be implementation defined behaviour though; kind of hard to tell. The answer is, of course, to do what is normally expected and explicitly write extern int blah;

    Edit: It's been pointed out to me that J.5.11p1 is probably more applicable (and in my opinion more readable)

    J.5.11 Multiple external definitions

    1 There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).
    Last edited by Hodor; 02-07-2020 at 05:14 AM.

  15. #15
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    27,550
    Quote Originally Posted by Hodor
    The crux of the "problem" seems to be 6.9.2p2
    That's not a problem; it's a technical description of when and how one or more tentative definitions in a translation unit are coalesced into a single definition.

    Quote Originally Posted by Hodor
    In summary I still don't think there is any UB occurring
    The program has two translation units. In each translation unit, there is a tentative definition for the same identifier with external linkage, resulting in a definition each. An identifier with external linkage shall only be defined exactly once (or at most once, if unused) across the entire program. Consequently, it has been defined twice. So, why do you think that there's no undefined behaviour?
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 9
    Last Post: 05-07-2017, 12:29 PM
  2. Replies: 7
    Last Post: 04-10-2017, 01:58 AM
  3. extern variable
    By edesign in forum C Programming
    Replies: 4
    Last Post: 06-10-2009, 05:51 AM
  4. using extern to have a global variable
    By steve1_rm in forum C Programming
    Replies: 3
    Last Post: 01-29-2009, 06:44 AM
  5. extern variable assignment
    By George2 in forum C++ Programming
    Replies: 4
    Last Post: 02-12-2008, 07:41 AM

Tags for this Thread