for循环中的变量在每次迭代时都没有重新初始化?

时间:2012-12-14 17:04:20

标签: string c++-cli scope

我有一个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

我是否完全误解了本地作用域变量应该如何初始化?这是托管C ++独有的“功能”吗?如果我转换 对于C#,编译器将警告两个变量,即使在基本警告级别。

2 个答案:

答案 0 :(得分:1)

免责声明:我非常了解C和C ++; C ++ / CLI,不是那么多。但是你所看到的行为与我对C或C ++类似程序的预期基本相同。

String^String句柄,类似于C或C ++中的指针

除非C ++ / CLI为句柄初始化添加了新规则,否则类型为String^且没有显式初始化的块范围变量最初将具有垃圾值,包括发生在该块内存中的任何内容。

循环的每次迭代在概念上创建并销毁在{}之间定义的任何变量。每次迭代可能在同一个内存位置分配它的局部变量(这不是必需的,但没有真正的理由不这样做)。编译器甚至可以生成在进入函数时分配内存的代码。

因此,在循环的第一次迭代中,variable设置为"One"(或者更确切地说,设置为引用"One"的句柄),这是{{1}打印的值}}。没问题。

在第二次迭代中,Console::WriteLine被分配在第一次迭代时用于它的相同内存位置。没有为其分配新值,因此它保留第一次迭代时存储在该内存位置的值。同样的事情发生在variable

您不能指望保留以前的值,并且您的程序行为未定义。如果您的目标是编写正确工作的程序,而不是理解这个不正确的程序的行为,那么解决方案就是确保所有变量在使用之前都已正确初始化。

如果你在第二次迭代而不是第一次迭代时进行了初始赋值,程序可能会在第一次迭代时崩溃 - 尽管即使这样也无法保证。

至于为什么编译器没有对此发出警告,我不知道。我对建议编译器错误犹豫不决,但这可能是一个。

此外,即使启用了高警告级别,关于未初始化变量的警告也需要控制流分析,默认情况下可能无法完成。启用警告进行高级别优化可能会为编译器提供足够的信息来警告xvariable

对于x警告x而不是约variable W4警告似乎很奇怪。

答案 1 :(得分:0)

C ++ / CLI只是标准C ++ 扩展/超集,所以它符合大多数规范,只是扩展它以适应CLI(〜.Net) )要求。

  

本地范围内的变量不应该初始化为null(或   等价)每次通过循环?

AFAIK C ++标准没有定义应该初始化局部循环变量的方式。

因此,为了避免任何开销,编译器通常不会对循环使用特定的本地内存管理:请参阅此SO问题: Is there any overhead to declaring a variable within a loop? (C++)

  

我是否完全误解了应该如何初始化本地范围的变量?

     

这是托管C ++独有的“功能”

所以不,这不是特性或特殊行为:您的C ++ / CLI编译器仅使用标准C ++实践。

  

如果我将其转换为C#,编译器将警告这两个变量,   即使在基本警告水平。

C#和AFAIK Java努力避免任何未定义的行为,因此它们会强制您在使用之前初始化局部变量。

这是编译产生的CIL(我做了一些格式化和评论,使这一堆文本可以理解:)):

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

因此,变量确实永远不会被编译器生成的代码隐式操纵。