试图理解C指针、块和go to语句的问题

试图理解C指针、块和go to语句的问题,c,C,我正在努力通过Jim Trevor的“PL课程”学习C和Cyclone。Trevor给出了一个不安全的go-to语句示例: int z; { int x = 0xBAD; goto L; } { int *y = &z; L: *y = 3; // Possible segfault } 特雷弗对上述代码中的安全问题解释如下: 许多编译器堆栈分配 输入块时块的局部变量,以及 当块退出时释放(pop)存储 (尽管这不是C标准的强制要求)。 如果示例是以这种方式编译的,那么 程序进入第一个

我正在努力通过Jim Trevor的“PL课程”学习C和Cyclone。Trevor给出了一个不安全的go-to语句示例:

int z;
{ int x = 0xBAD; goto L; }
{ int *y = &z;
L: *y = 3; // Possible segfault
}
特雷弗对上述代码中的安全问题解释如下:

许多编译器堆栈分配 输入块时块的局部变量,以及 当块退出时释放(pop)存储 (尽管这不是C标准的强制要求)。 如果示例是以这种方式编译的,那么 程序进入第一个块,在堆栈上分配x的空间,并用值初始化 很糟糕。转到跳到第二个块的中间,直接到内容的赋值 指针y的方向。因为y是第一个(唯一)变量 在第二个块中声明,赋值应为 y将位于堆栈的顶部。不幸的是,这是错误的 正是x被分配的位置,所以程序尝试 要写入位置0xBAD,可能会触发 分段错误

我不明白为什么
转到
语句在这里是个问题。问题似乎是未初始化指针Z的不可预测行为。在第二个块的开头,
int*y
用Z的地址填充。Z未初始化,因此它将用Z引用的内存区域中堆栈上的位模式填充
int*y
。我不理解为什么特雷弗的论文暗示Z和X都引用0xBAD。C是否会为第一个块创建一个新的堆栈帧(正如Trevor所描述的):从而将0xBAD写入内存中的一个新帧(而不是Z引用的内存中的位置)

我不明白为什么go-to声明在这里是个问题

goto L
绕过
y
的初始化(
y
将不会设置为
&z
),因此在分配给谁知道它指向哪里时出现问题
*y

问题似乎是来自于不可预测的行为 未初始化的指针Z

否。指针
&z
实际上是有效的。
int
z
未初始化,但这并不重要,因为您从未尝试读取它;你实际上是在试图覆盖它

在第二个块的开头,int*y可以用Z的地址填充

这就是重点<代码>转到L绕过该选项

我不明白为什么特雷弗的论文暗示Z和X都引用0xBAD


我认为Trevor暗示了第二个潜在问题,尽管我不确定有多少编译器(如果有的话)会真正展示它。当使用
goto
离开块时,堆栈指针(例如x86上的
ESP
)理论上可能不会递减。通过跳过
y
的初始化,堆栈指针也可能不会增加。因此,如果编译器引用使用堆栈指针的局部变量(而不是帧指针,例如x86上的
EBP
),这样的编译器理论上可能会将
x
误认为
y
,就好像发生了
int*y=0xBAD

如您所说,问题是变量
y
可能在初始化之前就被访问了。您给出的代码片段只是演示问题的一种方法

当我使用带有
-Wall
选项的GCC编译此函数时,它会警告
警告“y”在该函数中未初始化时使用。如果我用C++编译它为C++代码,那实际上是个错误:

test.cc:8:3: error: jump to label ‘L’
test.cc:6:25: error:   from here
test.cc:7:10: error:   crosses initialization of ‘int* y’

尽管在本例中,
y
是一个POD类型,但如果它是一个具有构造函数的类,则
goto
将跳过构造函数。C++语言规范说,在所有情况下这是非法的。

如果删除块并分离出值的初始化和声明,则更容易理解问题。

int z;
int *y;
goto L;
y = &z;
L: 
*y = 42;

这基本上就是原始样本中发生的情况,但更清楚一点。这里的行
y=&z
从未执行,因此
y
指向一个未定义的位置,因此它的设置是不安全的

就语言而言,程序的行为根本没有定义。
goto
跳过
y
的初始化;指针对象存在,但它不指向任何已定义的位置。取消引用
y
具有未定义的行为

但更详细地看一下代码,并对其行为做出一些(无根据的)假设:

int z;
{ 
    int x = 0xBAD; goto L; 
}   
{ 
    int *y = &z;
    L: *y = 3; // Possible segfault
}
局部变量(通常)在堆栈上分配。当控件到达包含其定义的块的末尾时,每个局部变量都不再存在

我认为,第一个块创建一个
int
对象
x
,并将值
0xBAD
赋给它<当
goto
将控制权移出该块时,code>x
将不再存在,但
0xBAD
值可能仍然存在于堆栈顶部的正上方

goto
将控制权转移到第二个块中。它跳过
y
的初始化,但不跳过其分配;指针对象
y
仍然分配在堆栈上,无论控件是直接进入块还是通过
goto
语句进入块。如果
0xBAD
值留在堆栈顶部上方,则
y
可以轻松地分配到同一位置;由于跳过初始化,
0xBAD
值可以保留在
y
中(或者更确切地说,构成
0xBAD
int
表示的位保留在
y
中,并被解释为指针值)

因此赋值
*y=3尝试将值
3
存储在内存位置
0xBAD

这可能就是定义、初始化和使用变量
x
:在