简体   繁体   中英

Hiding name of int variable in c++

Out of curiosity, I've tried this code, resulting from an interview question[*]

int main(int argc, char *argv[])
{
    int a = 1234;
    printf("Outer: %d\n", a);
    {
        int a(a);
        printf("Inner: %d\n", a);
    }
}

When compiled on Linux (both g++ 4.6.3 and clang++ 3.0) it outputs:

Outer: 1234
Inner: -1217375632

However on Windows (VS2010) it prints:

Outer: 1234
Inner: 1234

The rationale would be that, until the copy-constructor of the second 'a' variable has finished, the first 'a' variable is still accessible. However I'm not sure if this is standard behaviour, or just a(nother) Microsoft quirk.

Any idea?

[*] The actual question was:

How you'd initialise a variable within a scope with the value of an identically named variable in the containing scope without using a temporary or global variable?

{
    // Not at global scope here
    int a = 1234;
    {
        int a;
        // how do you set this a to the value of the containing scope a ?
    }
}

How you'd initialise a variable within a scope with the value of an identically named variable in the containing scope without using a temporary or global variable?

Unless the outer scope can be explicitly named you cannot do this. You can explicitly name the global scope, namespace scopes, and class scopes, but not function or block statement scopes.


C++11 [basic.scope.pdecl 3.3.2 p1 states:

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any), except as noted below. [ Example:

 int x = 12; { int x = x; } 

Here the second x is initialized with its own (indeterminate) value. —end example ]

MSVC correctly implements this example, however it does not correctly implement this when the initializer uses parentheses instead of assignment syntax. There's a bug filed about this on microsoft connect.

Here's an example program with incorrect behavior in VS as a result of this bug.

#include <iostream>

int foo(char) { return 0; }
int foo(int) { return 1; } 

int main()
{
    char x = 'a';
    {
        int x = foo(static_cast<decltype(x)>(0));
        std::cout << "'=' initialization has correct behavior? " << (x?"Yes":"No") << ".\n";
    }
    {
        int x(foo(static_cast<decltype(x)>(0)));
        std::cout << "'()' initialization has correct behavior? " << (x?"Yes":"No") << ".\n";
    }
}

C++ includes the following note.

[ Note: Operations involving indeterminate values may cause undefined behavior. —end note ]

However, this note indicates that operations may cause undefined behavior, not that they necessarily do . The above linked bug report includes an acknowledgement from Microsoft that this is a bug and not that the program triggers undefined behavior.

Edit: And now I've changed the example so that the object with indeterminate value is only 'used' in an unevaluated context, and I believe that this absolutely rules out the possibility of undefined behavior on any platform, while still demonstrating the bug in Visual Studio.

How you'd initialise a variable within a scope with the value of an identically named variable in the containing scope without using a temporary or global variable?

If you want to get technical about the wording, it's pretty easy. A "temporary" has a specific meaning in C++ (see §12.2); any named variable you create is not a temporary. As such, you can just create a local variable (which is not a temporary) initialized with the correct value:

int a = 1234;
{ 
   int b = a;
   int a = b;
}

An even more defensible possibility would be to use a reference to the variable in the outer scope:

int a = 1234;
{ 
    int &ref_a = a;
    int a = ref_a;
}

This doesn't create an extra variable at all -- it just creates an alias to the variable at the outer scope. Since the alias has a different name, we retain access to the variable at the outer scope, without defining a variable (temporary or otherwise) to do so. Many references are implemented as pointers internally, but in this case (at least with a modern compiler and optimization turned on) I'd expect it not to be -- that the alias really would just be treated as a different name referring to the variable at the outer scope (and a quick test with VC++ shows that it works this way -- the generated assembly language doesn't use ref_a at all).

Another possibility along the same lines would be like this:

const int a = 10;
{ 
    enum { a_val = a };
    int a = a_val;
}

This is somewhat similar to the reference, except that in this case there's not even room for argument about whether a_val could be called a variable -- it absolutely is not a variable. The problem is that an enumeration can only be initialized with a constant expression, so we have to define the outer variable as const for it to work.

I doubt any of these is what the interviewer really intended, but all of them answer the question as stated. The first is (admittedly) a pure technicality about definitions of terms. The second might still be open to some argument (many people think of references as variables). Though it restricts the scope, there's no room for question or argument about the third.

What you are doing, initializing a variable with itself, is undefined behavior. All your test cases got it right, this is not a quirk. An implementation could also initialize a to 123456789 and it would still be standard.

Update: The comments on this answer point that initializing a variable with itself is not undefined behavior, but trying to read such variable is.

How you'd initialise a variable within a scope with the value of an identically named variable in the containing scope without using a temporary or global variable?

You can't. As soon as the identical name is declared, the outer name is inaccessible for the rest of the scope. You'd need a copy or an alias of the outer variable, which means you'd need a temporary variable.

I'm surprised that, even with the warning level cranked up, VC++ doesn't complain on this line:

int a(a);

Visual C++ will sometimes warn you about hiding a variable (maybe that's only for members of derived classes). It's also usually pretty good about telling you you're using a value before it has been initialized, which is the case here.

Looking at the code generated, it happens to initialize the inner a to the same value of the outer a because that's what's left behind in a register.

I had a look at the standard, it's actually a grey area but here's my 2 cents...

3.1 Declarations and definitions [basic.def]

  1. A declaration introduces names into a translation unit or redeclares names introduced by previous declarations.

  2. A declaration is a definition unless... [non relevant cases follow]

3.3.1 Point of declaration

  1. The point of declaration for a name is immediately after its complete declarator and before its initializer (if any), except as noted below [self-assignment example].

  2. A nonlocal name remains visible up to the point of declaration of the local name that hides it.

Now, if we assume that this is the point of declaration of the inner 'a' (3.3.1/1)

int a (a);
     ^

then the outer 'a' should be visible up to that point (3.3.1/2), where the inner 'a' is defined.

Problem is that in this case, according to 3.1/2, a declaration IS a definition. This means the inner 'a' should be created. Until then, I can't understand from the standard whether the outer 'a' is still visible or not. VS2010 assumes that it is, and all that falls within the parentheses refers to the outer scope. However clang++ and g++ treat that line as a case of self-assignment, which results in undefined behaviour.

I'm not sure which approach is correct, but I find VS2010 to be more consistent: the outer scope is still visible until the inner 'a' is fully created.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM