试图理解C指针、块和go to语句的问题
我正在努力通过Jim Trevor的“PL课程”学习C和Cyclone。Trevor给出了一个不安全的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标准的强制要求)。 如果示例是以这种方式编译的,那么 程序进入第一个
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
:在