4

我有点困惑。据我所知,如果您在 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;
}

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

4

6 回答 6

8

读取未初始化变量的值会导致未定义的行为。未定义的行为意味着它可能会崩溃。这并不意味着它或它必须崩溃。

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

于 2013-03-07T10:36:04.960 回答
4

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

根据 C 2011(n1570 草案)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 标准的未定义行为。

于 2013-03-07T12:27:10.150 回答
1

这是未定义的行为,这意味着任何事情都可能发生。从字面上看任何东西。只是没有定义行为。

于 2013-03-07T10:35:25.307 回答
1

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

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;
}

编辑:进一步的想法:

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

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

于 2013-03-07T12:10:51.233 回答
0

无法保证使用未初始化的变量会导致程序崩溃——这取决于为变量分配的内存位置中碰巧有哪些垃圾数据。

于 2013-03-07T10:36:14.030 回答
0

On an x86 or x86-64, premature optimization prevent the load of value from the variable:

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.

Undefined behavior could mean anything. For uninitialized variable, it would behave as its type, but the value is unknown and cannot be verified. An uninitialized variable automatically take the value in the "accumulator" (integral: eax, rax; floating point/simd: xmm0, ymm0, zmm0, etc.) and not load it from random access memory (or cache) unless it is volatile. You can initialize the variable by assigning a value to it, given the non-lvalue does not use an uninitialized variable.

于 2021-02-12T20:46:29.897 回答