为什么GCC seg会在clang没有在教科书上练习的地方出错?
使用Adam Hoover的“使用C和Unix进行系统编程”学习C。我遇到了第四章的问题,这让我非常困惑。问题如下: 在下面的代码中,到达了第一个printf() 生成输出“14”,但生成第二个printf() 可能导致总线错误或分段故障。为什么? 书中的原始代码:为什么GCC seg会在clang没有在教科书上练习的地方出错?,c,gcc,clang,C,Gcc,Clang,使用Adam Hoover的“使用C和Unix进行系统编程”学习C。我遇到了第四章的问题,这让我非常困惑。问题如下: 在下面的代码中,到达了第一个printf() 生成输出“14”,但生成第二个printf() 可能导致总线错误或分段故障。为什么? 书中的原始代码: main() { int *p; funct(p); printf("%d\n",*p); } funct(int *p2) { p2=(int *)malloc(4); *p2=14; printf("%
main()
{
int *p;
funct(p);
printf("%d\n",*p);
}
funct(int *p2)
{
p2=(int *)malloc(4);
*p2=14;
printf("%d\n",*p2);
}
我稍微修改过的“调试”(printf all things)版本:
#包括
#包括
无效函数(int*p2);
int main(){
int*p;
printf(“主p-地址:%p\n”,p);
函数(p);
printf(“主p-地址:%p\n”,p);
printf(“主p值:%d\n”,*p);
}
无效函数(int*p2){
printf(“函数(前malloc)p2-地址:%p\n”,p2);
p2=(int*)malloc(4);
printf(“函数(后malloc)p2-地址:%p\n”,p2);
*p2=14;
printf(“函数p2值:%d\n”,*p2);
}
我已经使用gcc和clang(在ubuntulinux上)编译了这个示例,clang不会为应该这样做的代码产生seg错误。我对此困惑了一段时间,无法想象这是为什么或如何。欢迎有任何见解
谢谢
int *p;
funct(p);
printf("%d\n",*p);
这是错误的<代码>p是通过值传递的。因此,对函数所做的修改不会影响main
中的p
。取消引用未初始化的指针行为是未定义的
你真正需要做的是-
funct(&p) ; // in main
void funct( int **p ){
*p = malloc(sizeof(int));
// ...
}
这是未定义的行为,不一定会导致崩溃(或任何其他特定行为)。编译器可以自由地为这种情况生成它喜欢的任何代码。既然您问了为什么由clang生成的代码不会崩溃,我们就需要深入研究该代码。以下是在x86_64上使用
-O3
编译时产生的叮当声:
main: # @main
pushq %rbp
movq %rsp, %rbp # Build stack frame
movl $.L.str, %edi
movl $14, %esi
xorb %al, %al # no XMM registers used by varargs call
callq printf # printf(%edi = "%d\n", %esi = 14)
movl $.L.str, %edi
xorb %al, %al # no XMM registers used by varargs call
callq printf # printf(%edi = "%d\n", %esi = ?)
xorl %eax, %eax
popq %rbp
ret # return %eax = 0
由于
p
未初始化,clang今天选择将表达式*p
编译为零。这是一种合法的转换,因为clang可以证明表达式具有未定义的行为。然后,正在打印的值就是在调用printf
时%esi
寄存器中结束的值(在我的机器上,恰好是-1
)。这可能不是您所期望的,但这是未定义行为的本质 如果您在x64上编译,p2=(int*)malloc(4)代码>不够大。您正在将内存分配给传递给函数的指针的副本,而不是main()
中的指针。取消对未初始化指针的引用是一个错误。UB表示它可能会崩溃,也可能不会。关于堆栈溢出的C问题中有多少部分涉及未定义的行为?这是一个实际的练习?哇!如果要在funct()
中修改地址,需要传递p
(&p
)的地址。我知道代码有点可怕。我相信这次演习的目的是找出问题所在。我不明白的是,为什么这段可怕的代码在使用clang编译时不会出现seg错误。为了清楚起见,我认为它应该像使用gcc编译时那样对fault进行seg。
main: # @main
pushq %rbp
movq %rsp, %rbp # Build stack frame
movl $.L.str, %edi
movl $14, %esi
xorb %al, %al # no XMM registers used by varargs call
callq printf # printf(%edi = "%d\n", %esi = 14)
movl $.L.str, %edi
xorb %al, %al # no XMM registers used by varargs call
callq printf # printf(%edi = "%d\n", %esi = ?)
xorl %eax, %eax
popq %rbp
ret # return %eax = 0