是否有可能在C中总是在数组大小上产生1的段错误?

假设你分配一些数组arr size n ,如下所示:

int arr[n]; // Allocated fine printf("%d", arr[n]); // Segfault if possible 

是否存在这样的数字n ,我总是可以在printf线上触发段错误? 这可能是某些操作系统特有的。

我知道它的未定义行为,并且我知道在访问它时将其更改为超出边界会影响另一个内存区域,这可能会导致我以后出现重大问题。

我的教授说它并不总是会出现段错误,而且我很好奇是否无论如何都要在某种情况下使用某种类型的操作系统或计算机创建一个相当大小的数组,每次都可以进行可靠的段错误。

这是可能的还是没有? 是否有一些我可以创建的条件会导致单个出界访问始终触发段错误。

理论上可能总是如此吗? 但是在实践中不会一直发生吗?

在一般情况下,正如Ben所说,它是未定义的行为。 一般的答案是“不要依赖未定义的行为,它的效果永远不会是确定性的”。

然而,有两种可靠的解决方法可以在特定的,现代化的,普通的系统中实现这一点,这种系统涵盖了大量现代PC,但它不能在所有编译器,架构,操作系统中移植,等等

  1. 只需创建一个数组并将其与堆栈边界对齐 。 尝试访问元素arr[-1] ,或将其与另一个极端对齐。 不保证,但非常可能,因为操作系统不允许您访问受保护的内存,或者如果您正在写入RODATA段,就是这样。
  2. 在Linux上, 只需使用-fstack-protector-strong编译代码,并在堆栈粉碎时观察代码故意崩溃 。 在代码覆盖率测试期间在软件的测试版本上启用此function是个好主意:最好在测试阶段崩溃并修复它,然后部署它并使其在生产中崩溃。

不可以。越界访问是未定义的行为。 UB意味着任何事情都会发生。 在标准系统上,您通常可以找到一种始终导致段错误的方法,但绝不能保证这一点。 如果你在代码中改变其他东西,也许你会得到二进制移位改变你的堆栈分配和改变程序的结果。

例如,在运行RTEMS 4.9.2的PowerPC 5200(不是一个很棒的MMU)上,以下代码不会创建段错误:

 int arr[5]; arr[6] = 10; 

事实上即使这不会产生段错误:

 int *p = 0; while (true) *(p--) = 666; 

确实,undefined意味着未定义

要在打印声明中执行此操作,您可以执行以下操作:

 printf("%d", arr[n]) // Out of bounds access printf("%f", arr[n]) // wrong type access 

但是我会重新进行迭代,虽然这可能会在特定情况下重复出现故障,但绝不能保证始终以这种方式发生。

要使用SIGSEGV可靠地停止POSIX系统,最好的办法是自己提高它:

 raise(SIGSEGV); 

有关强制SIGSEGV信号的更多信息,请参见: 如何以编程方式在C / C ++中导致核心转储

和这里:

C ++为调试目的创建SIGSEGV

警告:这使用来自malloc的数组,所以从技术上讲,它并不完全相同。

但是,这将在末尾添加“警卫”页面/区域,这总是会导致段错误。

我经常使用它来调试“逐个”数组索引。 我发现它非常有用,我在生产代码中将它添加为malloc包装器的一部分。

因此,如果想要提出调试真正问题的东西,这可能会有所帮助:

 // segvforce -- force a segfault on going over bounds #include  #include  #include  #include  #include  #include  #ifndef PAGESIZE #define PAGESIZE 4096 #endif // number of guard pages that follow // NOTE: for simple increments, one is usually sufficient, but for larger // increments or more complex indexing, choose a larger value #ifndef GUARDCNT #define GUARDCNT 1 #endif #define GUARDSIZE (PAGESIZE * GUARDCNT) // crash_alloc -- allocate for overbound protection void * crash_alloc(size_t curlen) { size_t pagelen; void *base; void *endp; pagelen = curlen; // align up to page size pagelen += PAGESIZE - 1; pagelen /= PAGESIZE; pagelen *= PAGESIZE; // add space for guard pages pagelen += GUARDSIZE * 2; base = NULL; posix_memalign(&base,PAGESIZE,pagelen); printf("base: %p\n",base); // point to end of area endp = base + pagelen; printf("endp: %p\n",endp); // back up to guard page and protect it endp -= GUARDSIZE; printf("prot: %p\n",endp); mprotect(endp,GUARDSIZE,PROT_NONE); // point to area for caller endp -= curlen; printf("fini: %p\n",endp); return endp; } // main -- main program int main(int argc,char **argv) { int n; int *arr; int idx; int val; n = 3; arr = crash_alloc(sizeof(int) * n); val = 0; for (idx = 0; idx <= n; ++idx) { printf("try: %d\n",idx); val += arr[idx]; } printf("finish\n"); return val; } 

正如其他人所指出的那样,在一般情况下,您无法确保分段错误,您可以尝试使用精心设计的分配方法,使其在某些系统上更加系统化。

有一种更好的方法来调试代码并检测这种错误:有一个非常有效的工具: valgrind 。 检查它是否适用于您的环境。