【发布时间】:2020-12-30 20:11:48
【问题描述】:
我有以下 C 代码:
#include <stdlib.h>
#include <stdio.h>
char* foo() {
char abc[4] = "abc";
return abc;
}
int main() {
printf("%s", foo());
return 0;
}
如果我用 gcc 编译它并运行可执行文件,我得到(null)% 作为输出。
如果我运行稍加修改的代码:
#include <stdlib.h>
#include <stdio.h>
char* foo() {
char abc[4] = "abc";
return abc;
}
int main() {
printf("%c", *(foo()));
return 0;
}
我遇到了分段错误。
我的问题是:为什么我的第一个代码不会出现分段错误?我正在运行 Linux 和 gcc 版本:gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
这两个代码在编译时都会生成一个warning: function returns address of local variable [-Wreturn-local-addr] 警告
【问题讨论】:
-
关于“为什么我的第一个代码不会出现分段错误?”:在这两种情况下,程序的行为都好像
foo返回了一个空指针。 (这种行为不是由 C 标准定义的,可能是优化器行为的结果,而不是您应该依赖的东西。)当您将指针传递给%s时,printf实现检查是否它是一个空指针并打印“(null)”而不是尝试取消引用它。当您尝试自己取消引用指针以将printf传递给%c时,没有进行初步检查,因此程序崩溃了。 -
“为什么这个明显不正确的程序没有崩溃”这个问题从来没有一个有趣的答案。这次只是走运了。继续前进,市民,这里没什么可看的。不要编写错误的程序。显然这说起来容易做起来难,但你至少可以做的是paying attention to compiler warnings。
-
@n.'pronouns'm.:“为什么这个明显不正确的程序崩溃没有一个有趣的答案”这个问题是错误的。有一些东西需要学习,包括编译器如何工作、链接器如何工作、操作系统如何工作等等。在这种情况下,至少有三个答案是错误的,因此显然有信息需要学习。一般来说,了解崩溃的原因有助于诊断未来的错误,从而加快、改进和降低软件开发成本。
-
@n.'pronouns'm.:不,这并不明显。那里有很多东西要学。一是违反的规则。取消引用指向生命周期已结束的对象的指针并不是未定义的。违反的规则是使用这样的指针具有未定义的行为。三个答案错了,这意味着三个人,可能还有更多的读者,不知道或注意到可能导致其他程序行为不端的规则。学习正确的规则并学会识别它,对于避免错误很有用。
-
@n.'pronouns'm.:要了解的另一件事是行为是如何表现出来的。编译器在这里没有做一件简单的事情。代码的直接实现会留下一个地址。它没。许多人不知道编译器对程序进行了如此全面的转换。他们在课堂上学习了一个简单的 C 计算模型,并将优化视为常见子表达式的合并,或者可能重写一些算术。了解当今的编译器进行大型抽象转换是新的。