使用Adam Hoover的“使用C和Unix进行系统编程”学习C.我从第4章遇到的问题让我很困惑。问题如下:
在以下代码中,第一个printf()到达 产生输出“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所有的东西)版本:
#include <stdio.h>
#include <stdlib.h>
void funct(int *p2);
int main(){
int *p;
printf("main p - address: %p\n", p);
funct(p);
printf("main p - address: %p\n", p);
printf("main p value: %d\n", *p);
}
void funct(int *p2){
printf("funct (pre malloc) p2 - address: %p\n", p2);
p2 = (int *)malloc(4);
printf("funct (post malloc) p2 - address: %p\n", p2);
*p2 = 14;
printf("funct p2 value: %d\n", *p2);
}
我已经使用gcc和clang(在ubuntu linux上)编译了这个示例,并且clang不会为应该执行此操作的代码生成seg错误。我现在已经困惑了一段时间,无法想象为什么或如何做到这一点。欢迎任何见解。
感谢。
答案 0 :(得分:7)
int *p;
funct(p);
printf("%d\n",*p);
这是错误的。 p
按值传递。因此,在函数中进行的修改不会影响p
中的main
。并且取消引用未初始化的指针行为是未定义的。
您实际需要做的是 -
funct(&p) ; // in main
void funct( int **p ){
*p = malloc(sizeof(int));
// ...
}
答案 1 :(得分:3)
这是未定义的行为,不必导致崩溃(或任何其他特定行为)。编译器可以自由地为这种情况生成它喜欢的任何代码。既然你问为什么clang生成的代码没有崩溃,我们需要深入研究这些代码。以下是在x86_64上使用-O3
进行编译时clang trunk产生的内容:
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可以证明表达式具有未定义的行为。然后打印的值是%esi
调用时printf
调用中的任何内容(在我的机器上,恰好是-1
)。这可能不是您所期望的,但这是未定义行为的本质!