考虑以下两行C:
int a[1] = {0};
a[1] = 0;
第二行在内存中的某个位置进行写访问。有时这些程序会在执行过程中产生段错误,有时则不会,这取决于我想要的环境,也可能是其他事情。
我想知道是否有办法尽可能地将这些程序强制为段错误(例如,通过以特殊方式编译它们,或者在某些虚拟机中执行它们,我不知道)。
这是出于教育目的。
答案 0 :(得分:9)
根据C语言标准,这些类型的访问是未定义的行为,编译器和运行时没有义务使它们成为段错误(尽管它们显然有时会这样做)。
出于教学目的,您可以查看热门编译器中的地址清理程序,例如GCC(https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html中的-fsanitize=address
)和Clang(https://clang.llvm.org/docs/AddressSanitizer.html)。
简单来说,这些选项会导致编译器使用额外的逻辑来检测内存访问,以捕获越界内存访问并产生用户可见的错误(尽管不是一个非常明显的段错误消息),允许用户发现此类错误,解决它们。
这可能就是你要找的东西。
答案 1 :(得分:2)
Linux上的Valgrind,大多数编译器的堆栈保护,所选运行时的调试选项(例如Windows上的Application Verifier),有很多选项。
在您的示例中,溢出位于堆栈上,这将始终要求编译器发出适当的防护。对于动态内存分配,可以使用应用程序内部使用的C / C ++运行时库或自定义包装器来捕获它。
像valgrind这样的工具会在它们发生时捕获基于堆的缓冲区溢出,因为它们实际上在VM中执行代码。
编译器辅助选项与位于缓冲区前面和后面的金丝雀一起工作,并且通常在释放缓冲区时再次检查。来自地址清理程序系列的选项还可以对固定大小的字段上的所有访问添加额外的检查,但如果涉及原始指针,这将无法工作。
运行时的调试选项通常只提供非常粗略的粒度。通常,他们只需将每个分配放在非连续地址空间的专用页面中即可。访问页面之间的间隙则是即时错误。但是,通常不会立即检测到轻微的缓冲区溢出。
最后还有一些静态代码分析,所有现代编译器都在某种程度上支持这种分析,它可以轻松地检测到至少一些琐碎的错误,例如你的例子中的错误。
这些选项中没有一个能够捕获所有可能的错误。 C语言为您提供了大量选项来实现这些工具无法检测到的未定义行为。