我有一个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#,编译器将警告两个变量,即使在基本警告级别。
答案 0 :(得分:1)
免责声明:我非常了解C和C ++; C ++ / CLI,不是那么多。但是你所看到的行为与我对C或C ++类似程序的预期基本相同。
String^
是String
的句柄,类似于C或C ++中的指针。
除非C ++ / CLI为句柄初始化添加了新规则,否则类型为String^
且没有显式初始化的块范围变量最初将具有垃圾值,包括发生在该块内存中的任何内容。
循环的每次迭代在概念上创建并销毁在{
和}
之间定义的任何变量。每次迭代可能在同一个内存位置分配它的局部变量(这不是必需的,但没有真正的理由不这样做)。编译器甚至可以生成在进入函数时分配内存的代码。
因此,在循环的第一次迭代中,variable
设置为"One"
(或者更确切地说,设置为引用"One"
的句柄),这是{{1}打印的值}}。没问题。
在第二次迭代中,Console::WriteLine
被分配在第一次迭代时用于它的相同内存位置。没有为其分配新值,因此它保留第一次迭代时存储在该内存位置的值。同样的事情发生在variable
。
您不能指望保留以前的值,并且您的程序行为未定义。如果您的目标是编写正确工作的程序,而不是理解这个不正确的程序的行为,那么解决方案就是确保所有变量在使用之前都已正确初始化。
如果你在第二次迭代而不是第一次迭代时进行了初始赋值,程序可能会在第一次迭代时崩溃 - 尽管即使这样也无法保证。
至于为什么编译器没有对此发出警告,我不知道。我对建议编译器错误犹豫不决,但这可能是一个。
此外,即使启用了高警告级别,关于未初始化变量的警告也需要控制流分析,默认情况下可能无法完成。启用警告和进行高级别优化可能会为编译器提供足够的信息来警告x
和variable
。
对于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
...
因此,变量确实永远不会被编译器生成的代码隐式操纵。