未初始化变量作为自身初始化器的行为是什么?

23
我刚刚注意到,以下代码可以使用clang/gcc/clang++/g++编译,使用c99、c11、c++11标准。
int main(void) {
    int i = i;
}

即使使用了-Wall -Wextra,编译器也没有报告任何警告。
通过将代码修改为int i = i + 1;并使用-Wall,它们可能会报告以下警告:
why.c:2:13: warning: variable 'i' is uninitialized when used within its own initialization [-Wuninitialized]
    int i = i + 1;
        ~   ^
1 warning generated.

我的问题:
- 编译器为什么允许这样做? - C/C++标准对此有何规定?具体来说,这种行为是未定义的还是依赖于实现?

2
-Wall -Wextra 中没有任何“even”的东西。这是警告的最低要求。请参见我对旧问题关于 -Wall这个答案 - DevSolar
1
“-Wall” 对于我来说足以让gcc发出警告。 - Kevin
2
重复:为什么编译器允许使用自身初始化变量?。然而,那里的答案并不是很好。所以现在让我们保持开放状态,看看是否有更好的解决方案。然后我们可以关闭链接的帖子。 - Lundin
2
我改进了标题,并将清理一些旧的、糟糕的重复问题,因为到目前为止在这里发布的答案已经比那些重复问题的答案更好了。 - Lundin
1
C++部分是https://dev59.com/E2Up5IYBdhLWcg3w8LB4的副本。 - xskxzr
显示剩余5条评论
3个回答

17
因为当使用未初始化的变量 i 来初始化它自身时,它在那个时候具有不确定的值。一个不确定的值可以是一个未指定的值或一个陷阱表示。
如果您的实现支持整数类型中的填充位,并且所讨论的不确定值恰好是一个陷阱表示,那么使用它将导致未定义行为。
如果您的实现中整数没有填充位,则该值仅为未指定,没有未定义的行为。
编辑:
进一步地,如果在某一时刻从未对 i 取地址,则行为仍然可能是未定义的。这在 C11 标准的 6.3.2.1p2 节中详细说明:
如果 lvalue 指定了具有自动存储期的对象(可以用寄存器存储类声明,即从未取其地址),并且该对象未初始化(未声明初始化程序且在使用之前未执行任何赋值),则行为是未定义的。
因此,如果您从未获取 i 的地址,则会导致未定义的行为。否则,上述语句适用。

也许有必要提到,标识符i的范围始于其声明符的末尾,其中初始化程序不是其中的一部分。因此,即使使用自身对其进行初始化没有用处,在其自己的初始化程序中,i仍然在范围内。 - John Bollinger
此外,关于C11 6.3.2.1/2存在一些微妙的问题,但我认为这并不会使您的分析不正确。 - John Bollinger
你能详细说明一下吗?最好参考标准。 - Hongxu Chen
@JohnBollinger 这个回答基本上是正确的。包括6.3.2.1特殊情况在内的完整故事可以在这里找到:(为什么)使用未初始化的变量是未定义行为? - Lundin
1
@JohnBollinger 对于 i 是否被取地址进行了进一步的详细说明。 - dbush
显示剩余7条评论

11

这是一个警告,与标准无关。

警告是一种具有“乐观”方法的启发式方法。只有当编译器确信会出现问题时才会发出警告。在这种情况下,可以尝试使用clang或最新版本的gcc,正如评论中所述(请参见我的另一个相关问题:为什么在这个微不足道的示例中,我没有从gcc获得“未使用初始化”的警告?)。

无论如何,在第一种情况下:

int i = i;

什么也不做,因为 i==i 已经成立。可能由于无用性而完全优化掉了该赋值操作。使用不将自初始化视为问题的编译器时,您可以在没有警告的情况下执行此操作:

int i = i;
printf("%d\n",i);

虽然这会触发一个警告:

int i;
printf("%d\n",i);

尽管如此,没有对此进行警告已经够糟糕了,从现在开始 i 被视为 初始化

在第二种情况下:

int i = i + 1;

必须进行一个未初始化值和 1 之间的计算。 这可能会导致未定义行为。


1
好的回答;然而,我不确定关于“编译器在确定会出现问题时发出警告”的说法。有很多警告,并非都是这种情况,这高度取决于启用的警告类型。 - Ctx
3
int i = i + 1 的风险在于 UB 就是 UB,没有别的。此外还有有符号溢出的问题。另外,当其他程序员在以后的某个时间点需要理解该代码时,会感到非常困惑。 - DevSolar
我忘记了“下划线符号”。为了简单起见已经编辑掉了。 - Jean-François Fabre
正如@Ctx所说。例如,我经常发现自己构建第三方代码时会引起大量关于可能未初始化变量使用的警告。在这些情况下,控制流分析通常显示程序员包含了适当的逻辑来确保在使用变量之前为其分配一个值。但编译器明确地不确定是否存在问题。 - John Bollinger
“-Wparentheses” 就是另一个例子。 - Osiris
显示剩余3条评论

4

我相信你能够理解在情况出现警告的情况下的操作

int i = i + 1; 

正如预期的那样,但是,即使在某些情况下,您也希望显示警告。
int i = i;

此语句本质上并没有什么问题。请参考以下相关讨论:

关于此事,C/C++标准有何规定?具体而言,这种情况的行为是未定义还是与实现有关?

由于类型int可能具有陷阱表示方式,并且您从未获取过所讨论变量的地址,因此这是未定义行为。因此,一旦尝试使用存储在变量i中的(不确定的)值,就会面临UB。

您应该打开编译器警告。在gcc中,

  • 在C语言中,使用-Winit-self参数进行编译可以得到一个警告。了解更多
  • 对于C++语言,-Wall参数已经包含了-Winit-self的功能。

这里使用gcc 7.3.1,也无法使用-Winit-self - Jean-François Fabre
你有一个新版本的gcc。我的版本比较旧。当你说“未定义行为的可能源(如果变量的值稍后被使用)”时,这与使用未初始化的变量是相同的。 - Jean-François Fabre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接