3

我有一个 C++/CLI 项目,它在 for 循环中声明了一个 String^ 变量,但没有对其进行初始化。在第一次迭代中,变量被设置为某个值。在每次后续迭代中,它似乎都保留了先前的值。每次通过循环时,本地范围内的变量不应该初始化为 null (或等效)吗?这也发生在 int 上。此外,除非我将警告级别设置为 W4,否则编译器不会警告可能未初始化的值,即使那样它也只会警告 int 而不是 String^。

这是显示行为的示例代码。

#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
    for(int n = 0; n < 10; n++)
    {
        String^ variable;
        int x;

        switch(n)
        {
        case 1:
            variable = "One";
            x = 1;
            break;
        case 5:
            variable = "Five";
            x = 5;
            break;
        }

        Console::WriteLine("{0}{1}", variable, x);
    }
}

这个的输出将是

One, 1
One, 1
One, 1
One, 1
Five, 5
Five, 5
Five, 5
Five, 5
Five, 5

Am I completely misunderstanding how locally scoped variables are supposed to be initialized? Is this a "feature" unique to managed C++? If I convert this to C# the compiler will warn about both variables, even at the base warning level.

4

2 回答 2

1

Disclaimer: I know C and C++ pretty well; C++/CLI, not so much. But the behavior you're seeing is essentially the same that I'd expect for a similar program in C or C++.

String^ is a handle to a String, similar to a pointer in C or C++.

Unless C++/CLI adds new rules for initialization of handles, a block-scope variable of type String^ with no explicit initialization will initially have a garbage value, consisting of whatever happened to be in that chunk of memory.

Each iteration of the loop conceptually creates and destroys any variables defined between the { and }. And each iteration probably allocates its local variables in the same memory location (this isn't required, but there's no real reason for it not to do so). The compiler could even generate code that allocates the memory on entry to the function.

So on the first iteration of your loop, variable is set to "One" (or rather, to a handle that refers to "One"), that's the value printed by Console::WriteLine. No problem there.

On the second iteration, variable is allocated in the same memory location that was used for it on the first iteration. No new value is assigned to it, so it retains the value that was stored in that memory location on the first iteration. The same thing happens with x.

You cannot count on the previous value being retained, and your program's behavior is undefined. If your goal were to write a correctly working program, rather than to understand how this incorrect program behaves, the solution would be to ensure that all your variables are properly initialized before they're used.

If you did the initial assignment on the second iteration rather than the first, the program would likely crash on the first iteration -- though even that's not guaranteed.

As for why the compiler doesn't warn about this, I don't know. I hesitate to suggest a compiler bug, but this could be one.

Also, even with high warning levels enabled, warning about uninitialized variables requires control flow analysis that may not be done by default. Enabling both warnings and a high level of optimization might give the compiler enough information to warn about both variable and x.

It still seems odd that it warns about x and not about variable with W4.

于 2012-12-14T18:17:57.870 回答
0

C++/CLI is only an extension/superset of standard C++ so it complies with most of its specifications, extending it only to fit with the CLI (~.Net) requirements.

Shouldn't a variable in local scope be initialized to null (or equivalent) each time thru the loop?

AFAIK the C++ standard does not define the way local loop variables should be initialized.

So, to avoid any overhead, compilers usually don't use specific local memory management for loops : see this SO question : Is there any overhead to declaring a variable within a loop? (C++)

Am I completely misunderstanding how locally scoped variables are supposed to be initialized?

Is this a "feature" unique to managed C++

So no, this is not a feature or special behavior : your C++/CLI compiler is only using standard C++ practices.

If I convert this to C# the compiler will warn about both variables, even at the base warning level.

C# and AFAIK Java try hard to avoid any undefined behaviors, so they force you to initialize local variables before they are used.

Here is the CIL resulting from the compilation (I've done some formatting and commenting to make this bunch of text understandable :)) :

.locals init (int32 V_0, int32 V_1, string V_2, int32 V_3)
//                   ^          ^           ^          ^
//                   n          x        variable     tmp

// initialization of "n"
IL_0000:  ldc.i4.0
IL_0001:  stloc.0
IL_0002:  br.s       IL_0008

// loop starts here

// post iteration processing
IL_0004:  ldloc.0
IL_0005:  ldc.i4.1
IL_0006:  add
IL_0007:  stloc.0

// stop condition check
IL_0008:  ldloc.0
IL_0009:  ldc.i4.s   10
IL_000b:  bge.s      IL_003e

// initialization of temporary "tmp" variable for switch
IL_000d:  ldloc.0
IL_000e:  stloc.3

// check if "tmp" is 3
IL_000f:  ldloc.3
IL_0010:  ldc.i4.1
// if so go to "variable" intialization
IL_0011:  beq.s      IL_0019

// check if "tmp" is 5
IL_0013:  ldloc.3
IL_0014:  ldc.i4.5
IL_0015:  beq.s      IL_0023

// go to display
IL_0017:  br.s       IL_002b

// initialization of "variable"
IL_0019:  ldstr      "One"
IL_001e:  stloc.2
...

So the variable is indeed never implicitly manipulated by the code generated by the compiler.

于 2012-12-15T12:33:58.233 回答