dEm*_*gOd 22 c c++ duffs-device
在玩代码时,我注意到一个奇怪的行为,我不知道解释背后的逻辑
void foo(int n)
{
int m = n;
while (--n > 0)
{
switch (n)
{
case -1:
case 0:
for (int j = 0; j < m; ++j)
default:
printf(":-)");
break;
}
}
}
int main()
{
foo(10);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我希望printf执行比方说的10时间。然后,我看到它继续运行(想象一下100000,而不是10),并假定开发(VS)解释了printf 内部的for(漂亮的预期),因此输出由n次,每次入口switch。
但后来证明j从未初始化。
所以我的问题是为什么?这是未定义的行为吗?这不是一个,据说,标准代码?
Ale*_*op. 27
default只是一个标签(如果n不是 -1 或 0 ,则代码跳转的地址)。因此,当n不是 -1 或 0 时,流程进入for循环体,跳过 的初始化j。您也可以编写与此相同的代码,因此这里发生的事情会更清楚:
int m = n;
while (--n > 0)
{
switch (n)
{
case -1:
case 0:
for (int j = 0; j < m; ++j)
{
default: printf(":-)");
}
break;
}
}
Run Code Online (Sandbox Code Playgroud)
(注意,正如@alagner 在评论中提到的,它不会用 C++ 编译器编译,但可以用 C 编译器完美编译,所以这足以说明我的观点并解释代码的外观)。
所以是的,由于j未初始化,它是未定义的行为。如果您启用编译器警告,它会警告您(https://godbolt.org/z/rzGraP):
warning: 'j' may be used uninitialized in this function [-Wmaybe-uninitialized]
12 | for (int j = 0; j < m; ++j)
| ^
Run Code Online (Sandbox Code Playgroud)
Joh*_*ica 11
一个switch块实际上是一组美化的goto语句。不同的情况不会向代码引入范围或任何逻辑结构。它们实际上只是switch语句跳转到的目标。
在此程序中,default:标签位于嵌套for循环内。当default遇到 case 时,程序会跳转到循环内,就好像那里有一条goto语句一样。该switch块相当于:
if (n == -1 || n == 0) {
goto before_loop;
}
else {
goto inside_loop;
}
before_loop:
for (int j = 0; j < m; ++j) {
inside_loop:
printf(":-)");
}
Run Code Online (Sandbox Code Playgroud)
这是危险的,因为跳转到inside_loop:跳过j = 0。正如您所观察到的,j仍然是声明的,但没有初始化,访问它会导致未定义的行为。
正如发布的那样,代码具有未定义的行为,因为当switch跳转到语句体default:内部的标签时,for它会跳过j内部循环中的初始化,在j测试时导致未定义行为,并在循环迭代时递减。
在 C++ 中不允许通过直接跳转到新作用域来跳过初始化。在 C 语言中不存在这样的约束,因为与历史代码的兼容性不一定会引起问题,但现代编译器会检测到此错误并抱怨。我建议使用-Wall -Wextra -Werror以避免愚蠢的错误。
请注意,修改如下,它变得完全定义,打印:)90 次(外循环的 9 次迭代,内循环的 10 次迭代)并成功完成:
#include <stdio.h>
void foo(int n) {
int m = n;
while (--n > 0) {
int j = 0;
switch (n) {
case -1:
case 0:
for (j = 0; j < m; ++j)
default:
printf(":-)");
break;
}
}
}
int main() {
foo(10);
printf("\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
对于初学者来说,foo具有返回类型的函数不int返回任何内容。
该while循环:
while (--n > 0)
{
//..
}
Run Code Online (Sandbox Code Playgroud)
仅在 pre-decrement 表达式的--n值大于的情况下获得控制权0。
也就是说,在while循环中,变量n既不等于0也不到-1。
因此,控制将立即向标签传递default的范围内switch声明。
switch (n)
{
case -1:
case 0:
for (int j = 0; j < m; ++j)
default:
printf(":-)");
break;
}
Run Code Online (Sandbox Code Playgroud)
您可以通过以下方式等效地重写while没有switch语句的循环,以使其更清晰:
while (--n > 0)
{
goto Default;
for (int j = 0; j < m; ++j)
{
Default: printf(":-)");
}
}
Run Code Online (Sandbox Code Playgroud)
也就是说,控制立即在for循环内部传递。根据 C 标准(6.8.5 迭代语句)
4 迭代语句使称为循环体的语句重复执行,直到控制表达式比较等于 0。无论循环体是从迭代语句输入还是通过跳转,都会发生重复。
这意味着for循环将包含一个语句:
printf(":-)");
Run Code Online (Sandbox Code Playgroud)
将被执行。
然而,该变量的初始初始化j在for环路被旁路。来自 C 标准(6.2.4 对象的存储持续时间)
6 对于这种没有可变长度数组类型的对象,其生命周期从进入与其关联的块开始,直到该块的执行以任何方式结束。(进入封闭块或调用函数会挂起,但不会结束当前块的执行。)如果递归进入块,则每次都会创建对象的一个新实例。对象的初始值是不确定的。如果为对象指定了初始化,则在块的执行中每次到达声明或复合文字时都会执行它;否则,每次到达声明时,该值都会变得不确定。
因此,该变量j 具有不确定的值。这意味着for循环以及函数本身具有未定义的行为。