Thread: Understanding redefinitions inside nesting blocks

  1. #1
    Registered User
    Join Date
    May 2013
    Posts
    228

    Understanding redefinitions inside nesting blocks

    I'm having difficulty understanding this concept.

    Consider the following snippet in C:

    Code:
    int main(int argc, char* argv[]) {
    
        int x=5;
    
        { // nested block
            printf("%d\n", x);
        }
    
        printf("%d\n", x);
    
        return 0;
    }
    Output:
    5
    5

    This snippet implies that x is defined inside the nested block.
    So, if it is defined inside the nested block, and we all know this is illegal:

    Code:
    int main(int argc, char* argv[]) {
    
        int x=5;
        int x=6;
    
        printf("%d\n", x);
    
        return 0;
    }

    then how is it that the following is legal?
    Code:
    int main(int argc, char* argv[]) {
    
        int x=5;
    
        { // nested block
            int x=6;
            printf("%d\n", x);
        }
    
        printf("%d\n", x);
    
        return 0;
    }
    Output:
    6
    5

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Compile and run this program and look carefully at the output:
    Code:
    #include <stdio.h>
    
    int main(void)
    {
        int x = 5;
    
        printf("Hi, my name is x. I live at %p and I am worth $%d million.\n", (void*)&x, x);
    
        {
            int x = 6;
           printf("Hi, my name is x. I live at %p and I am worth $%d million.\n", (void*)&x, x);
        }
    
        printf("Hi, my name is x. I live at %p and I am worth $%d million.\n", (void*)&x, x);
    
        return 0;
    }
    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
    May 2013
    Posts
    228
    The above implies that these variables occupy different places in memory...
    So, can I say, as general rule, that "redefinitions are disallowed only if they are at the same scope, and are allowed as long as they are at different scope"?

  4. #4
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Absurd
    So, can I say, as general rule, that "redefinitions are disallowed only if they are at the same scope, and are allowed as long as they are at different scope"?
    Yeah. However, note an exception:
    Code:
    #include <stdio.h>
    
    int x;
    int x;
    int x = 123;
    
    int main(void)
    {
        printf("%p: %d\n", (void*)&x, x);
    
        return 0;
    }
    It looks like you are defining x three times (since these declarations are also definitions), but actually the first two are tentative definitions so these repeated definitions are permitted for identifiers at file scope.
    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
    May 2013
    Posts
    228
    So at global scope I can redefine a variable without the compiler resisting?
    Do these three definitions also occupy different addresses in memory?
    Does every definition hide the previous one?

  6. #6
    Lurking whiteflags's Avatar
    Join Date
    Apr 2006
    Location
    United States
    Posts
    9,612
    Quote Originally Posted by Absurd View Post
    So at global scope I can redefine a variable without the compiler resisting?
    Do these three definitions also occupy different addresses in memory?
    Does every definition hide the previous one?
    There is one definition of x. It's int x = 123; The other x's are declarations, and don't cause x to be allocated any space.

    There is a difference between declaration and definition in C; it's just usually clearer to see with other bits. For example, functions and structures, and the like, can be declared but not defined in the sense that they take up memory or become part of the program.

    Code:
    // These are declarations:
    struct MovieRec
    {
       int _id;
       char _movieTitle[256];
    };
    
    void getFaveMovie(struct MovieRec* myParam);
    
    // These are definitions:
    void getFaveMovie(struct MovieRec* myParam)
    {
        static int id = 0;
    
        puts("Enter your favorite film.");
        if (fgets(myParam->_movieTitle, 256, stdin) != NULL) {
            myParam->_id = ++id;
        }
        else {
           myParam->_movieTitle[0] = '\0';
           myParam->_id = -1;
        } 
    }
    
    int main(void)
    {
        struct MovieRec rec;
        foo(&rec);
        return 0;
    }
    It would be a lot simpler if you were forced to initialize to define a variable, but it isn't like that in C. It's impossible to go somewhere in a C source file and not be in some scope, however, where you could only declare a variable. In fact, because this is the case, extern provides the ability to declare but not define, in one way or another.

    There are a few things about the way C works that complicate things. Ordinarily, in global scope, int x; is enough to define x with the default value 0. You could turn it into a declaration with the extern keyword if you wanted, though.

    I think, but can't recall, that extern is the default for variables in global scope anyway. Which only makes things more confusing.

    What likely happens in the example code is that the compiler is smart enough to find the most complete, well-formed definition of x in the global scope. That definition is actually the one that is initialized to 123, and since it doesn't violate other rules, the other x's are forward declarations only. (You should try to initialize, since it cuts down on undefined behavior stemming from "using a variable with an undetermined value")

    There is a reason the rules are so complicated. Some of it is because programming scope itself is such a useful concept. It's also true that C's build process leaves a lot to be desired, depending on your point of view...

    Headers often declare types and variables, but the preprocessor will paste the code together in a translation unit before the compiler works on it, turning it into machine code one way or another. So, it's impossible for the compiler to know that you mindlessly wrote one x after another. It's equally possible that the x's where introduced ahead of time by #includes. You see, in the normal build process, the time the compiler gets to chew on the code, the preprocessor has included <stdio.h> and whatever else - like, for example, a custom header you made - which may as well be were x came from.
    Last edited by whiteflags; 07-18-2015 at 02:46 PM.

  7. #7
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Absurd
    So at global scope I can redefine a variable without the compiler resisting?
    Do these three definitions also occupy different addresses in memory?
    Does every definition hide the previous one?
    Refer to the standard:
    Quote Originally Posted by C11 Clause 6.9.2 Paragraphs 1,2
    If the declaration of an identifier for an object has file scope and an initializer, the declaration is an external definition for the identifier.

    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.
    So, we see that in my example from post #4, this is a declaration of x that has file scope and an initailizer, so it is an external definition for x:
    Code:
    int x = 123;
    On the other hand, these two declarations of x have file scope without an initializer and without a storage-class specifier, so they are tentative definitions of x:
    Code:
    int x;
    int x;
    Because there is an external definition of x in that translation unit, these tentative definitions are like other declarations of x that are not definitions, i.e., they do not result in storage being allocated for x, but rather merely declare the name x and the associated type, etc. However, if you removed the external definition, i.e., if we ended up with:
    Code:
    #include <stdio.h>
    
    int x;
    int x;
    
    int main(void)
    {
        printf("%p: %d\n", (void*)&x, x);
        return 0;
    }
    Then it would be as if the program were:
    Code:
    #include <stdio.h>
    
    int x;
    
    int main(void)
    {
        printf("%p: %d\n", (void*)&x, x);
        return 0;
    }
    
    int x = 0;
    That is, the tentative definitions still cause x to be defined as if there were a single definition of x. Contrast this with:
    Code:
    #include <stdio.h>
    
    extern int x;
    extern int x;
    
    int main(void)
    {
        printf("%p: %d\n", (void*)&x, x);
        return 0;
    }
    In the above, the declarations of x are not definitions: there is no definition of x at all, i.e., you would end up with a linker error.

    Quote Originally Posted by whiteflags
    I think, but can't recall, that extern is the default for variables in global scope anyway.
    External linkage is the default linkage for variables at file scope.
    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. Structures nesting
    By justine in forum C Programming
    Replies: 3
    Last Post: 11-26-2012, 06:25 AM
  2. Mice Are Nesting!!!
    By Dagger in forum C++ Programming
    Replies: 4
    Last Post: 02-05-2006, 11:59 PM
  3. Nesting switchstatments
    By Da-Nuka in forum C++ Programming
    Replies: 5
    Last Post: 03-04-2005, 03:05 PM
  4. Nesting
    By coo_pal in forum C Programming
    Replies: 1
    Last Post: 01-27-2003, 11:16 PM
  5. Nesting Parentheses
    By XZSNPP in forum C++ Programming
    Replies: 6
    Last Post: 01-18-2003, 10:01 PM