C中未初始化的变量

时间:2013-03-07 10:33:27

标签: c variables initialization undefined-behavior

我有点困惑。据我所知,如果你在C中声明一个int而没有初始化它,例如:int x;

所以它的价值是不确定的。因此,如果我们尝试使用它或应该有未定义的行为。

所以,如果我在VS2010中运行以下代码,它会使程序崩溃。

int main(){
    int a;
    printf("%d\n",a);
    return 0;
}

现在让我们来看看下一个代码,它不提供任何警告,不会崩溃(为什么?)

void foo(int *var_handle){
    // do nothing
}

int main(){
    int a;
    foo(&a);
    printf("%d\n",a); // works, prints some big value
    return 0;
}

你能解释一下这种行为吗?我们只添加了对一个什么都不做的函数的调用,但现在程序不会崩溃。

6 个答案:

答案 0 :(得分:8)

读取未初始化变量的值会导致未定义的行为。未定义的行为意味着可以崩溃。这并不意味着 有义务崩溃。

未初始化的变量具有未指定的值 - 它只是未知它的值是什么。所以在实践中,任何理智的实现,这种代码可能永远不会崩溃。有一个有效的内存地址支持变量,它有一些垃圾内容,printf()读取它没有问题,将其解释为整数并打印它,就是这样。

答案 1 :(得分:3)

使用未初始化的值不会直接导致未定义的行为。

Per C 2011(n1570 draft)6.7.9 10,具有自动存储持续时间的未初始化对象具有不确定的值。根据3.19.2 1,不确定值是未指定的值或陷阱表示。类似的文字出现在C 1999中。

如果对象具有陷阱表示,则可能会出现未定义的行为。但是,如果对象具有未指定的值,则如果对象具有某个确定值,则程序必须具有行为;它只是没有指定对象具有哪个值。该程序不允许因为未指定值而崩溃。

令人惊讶的是,您在Visual Studio 2010中报告了显示崩溃的简单程序,因为我不希望int类型在Visual Studio 2010中有任何陷阱表示。它可能是某个源文件而不是您期望编译和崩溃的内容,或者您​​在Visual Studio 2010中启用了特殊的调试功能,尝试跟踪未初始化的对象(但在使用foo的第二种情况下失败)。

我建议您从头开始重复测试,将您在此问题中显示的代码粘贴到新文件中,并使用默认选项编译该新文件。

当然,Visual Studio 2010不符合C标准,甚至不符合旧的1999标准,因此不必遵守上述条款。实际上,有关Visual Studio 2010的所有内容都是与C标准相关的未定义行为。

答案 2 :(得分:1)

未定义的行为,意味着任何事情都可能发生。字面意思。行为尚未定义。

答案 3 :(得分:1)

你可以试试这个。我不知道它是否是严格未定义的行为,但是我想不出编译器实际上以未定义的方式运行并且仍然符合C标准的方法,至少在foo处于不同的编译单元(~source文件),因为那时编译器不知道它会被允许产生未定义的行为;)。

void foo(int *var_handle){
    // do something to var_handle, or maybe nothing, who knows
}

int main(){
    int a[1];
    foo(a);
    printf("%d\n", a[0]);
    return 0;
}

编辑:进一步的想法:

我很确定使用函数初始化未初始化的局部变量是可以的,方法是将非const指针指向函数的局部变量。因此,就编译器而言,仅获取局部变量的地址使得它定义具有未定义值的变量。编译器无法知道函数是否实际设置了值(函数可能在库中)。

但这只是解释了为什么它可以避免崩溃。它仍然可以是,如果允许函数内联,并且什么都不做,则允许优化器移除调用,然后删除未初始化的局部变量的地址,从而使其仍处于“未定义的行为”状态。您可以通过启动优化并从汇编输出验证来测试编译器,调用foo内联(不生成代码),然后查看printf是否崩溃。

答案 4 :(得分:0)

无法保证使用未初始化的变量会导致程序崩溃 - 这取决于垃圾数据恰好位于为变量分配的内存位置。

答案 5 :(得分:0)

在 x86 或 x86-64 上,过早的优化会阻止从变量加载值:

int b;
b -= b; // b = eax - b, not 0

int c;
c ^= c; // c = eax - c, not 0

// eax, whatever happens to be the value in the eax register.
// rax for 64-bit, eax for 32-bit and less using bit mask,
// ax for int is 16 bit compilers.

未定义的行为可能意味着任何事情。对于未初始化的变量,它的行为与其类型相同,但值未知且无法验证。未初始化的变量会自动采用“累加器”中的值(整数:eax、rax;浮点/simd:xmm0、ymm0、zmm0 等)并且不会从随机存取存储器(或缓存)中加载它,除非它是易失性的。考虑到非左值不使用未初始化的变量,您可以通过为其赋值来初始化该变量。