user-0 提问: 函数中局部变量在-O0选项下奇怪的执行过程

回复:
user: user-0
content: 引用 5 楼 chenzheng1030 的回复:这个问题很有意思，其实这个问题的难点不在-O0的情况下为什么出现循坏打印，我觉得难点在为什么-O2会出现segment fault。 花时间看了下，贴下来跟群主分享一下。 首先说说-O0，为什么出现循环打印。其实这个问题很简单，因为编译器在编译的时候就会为每个局部变量在栈上分配空间，打印一下变量i和指针p的地址以及p的内容，楼主就知道为什么会循环了。 下面是我再X86 64位上的结果：带上-g，然后用gdb去调试。 (gdb) p &i $1 = (int *) 0xbffff258 (gdb) p &p $2 = (int **) 0xbffff25c (gdb) p p $3 = (int *) 0xbffff248 (gdb)   局部变量i在栈上的地址是(int *) 0xbffff258，指针p指向ss1，ss1的地址是0xbffff248,所以每次循环4次之后，*(p+4)的地址实际上是0xbffff258，这个地址是i的地址，所以这个赋值实际上是将i的地址又赋为了0，所以会出现循环打印。  其实我重点想说的是-O2的情况下为什么会出现core dump。 这个问题也分为两个部分。 首先第一个部分想说的是，为什么-O2为什么不像-O0一样出现循环打印呢。这个其实是因为编译器做了优化，将循环变量i存在了寄存器里面，将变量i的值不再保存在栈上了，每次循环的时候都是从寄存器里取i的值，所以*(p+4)的值，不可能会改变i的值。这个结论可以在编译的时候加上-g，然后查看调试信息里的变量i是怎么取值得到的。  <4><42d>: Abbrev Number: 26 (DW_TAG_variable)     <42e>   DW_AT_name        : i	     <430>   DW_AT_decl_file   : 1	     <431>   DW_AT_decl_line   : 16	     <432>   DW_AT_type        : <0x3b>	     <436>   DW_AT_location    : 0x5c	(location list)    //i是从location list中取值      0000005c 08048510 0804851c (DW_OP_reg3 (ebx))   //location 0x5c表示的是从寄存器3中取值，也就是i是存在ebx中的     0000005c <End of list>  第二个是解释为什么-O2会出现core dump。这个概括的说是因为*(p+i) = 0 是这条语句破坏了程序的运行布局。包括改了只读存储区的内容或者是改了其他的东西，比如改了返回地址。 比如，我将程序稍微改了一下，将循环次数从10000次改为8次，程序仍然会出现core dump。（8这个数字是个临界值） 调试core文件： gdb a.out core Core was generated by `./a.out'. Program terminated with signal 11, Segmentation fault. #0  0x00000000 in ?? () (gdb) p $pc $1 = (void (*)()) 0 发现问题了，发生crash的时候，pc值指向了0，这个pc值就是在函数test2中，进行*(p+i) = 0的时候，由于栈是从大到小，而且程序的返回地址是存在局部变量之前，即高地址，所以*(p+i)=0，会将pc值改为了0，这样在test2返回的时候，返回到了地址0，所以就出现了crash。  之前说过8是个临界数字，如果大于等于8会出现crash，为7或者更小就不会出现crash，但是局部变量的值可能会出现意想不到的的改变。 高手；分析细致入微，真是专家级别；

user: user-1
content: 这个问题很有意思，其实这个问题的难点不在-O0的情况下为什么出现循坏打印，我觉得难点在为什么-O2会出现segment fault。 花时间看了下，贴下来跟群主分享一下。 首先说说-O0，为什么出现循环打印。其实这个问题很简单，因为编译器在编译的时候就会为每个局部变量在栈上分配空间，打印一下变量i和指针p的地址以及p的内容，楼主就知道为什么会循环了。 下面是我再X86 64位上的结果：带上-g，然后用gdb去调试。 (gdb) p &i $1 = (int *) 0xbffff258 (gdb) p &p $2 = (int **) 0xbffff25c (gdb) p p $3 = (int *) 0xbffff248 (gdb)   局部变量i在栈上的地址是(int *) 0xbffff258，指针p指向ss1，ss1的地址是0xbffff248,所以每次循环4次之后，*(p+4)的地址实际上是0xbffff258，这个地址是i的地址，所以这个赋值实际上是将i的地址又赋为了0，所以会出现循环打印。  其实我重点想说的是-O2的情况下为什么会出现core dump。 这个问题也分为两个部分。 首先第一个部分想说的是，为什么-O2为什么不像-O0一样出现循环打印呢。这个其实是因为编译器做了优化，将循环变量i存在了寄存器里面，将变量i的值不再保存在栈上了，每次循环的时候都是从寄存器里取i的值，所以*(p+4)的值，不可能会改变i的值。这个结论可以在编译的时候加上-g，然后查看调试信息里的变量i是怎么取值得到的。  <4><42d>: Abbrev Number: 26 (DW_TAG_variable)     <42e>   DW_AT_name        : i	     <430>   DW_AT_decl_file   : 1	     <431>   DW_AT_decl_line   : 16	     <432>   DW_AT_type        : <0x3b>	     <436>   DW_AT_location    : 0x5c	(location list)    //i是从location list中取值      0000005c 08048510 0804851c (DW_OP_reg3 (ebx))   //location 0x5c表示的是从寄存器3中取值，也就是i是存在ebx中的     0000005c <End of list>  第二个是解释为什么-O2会出现core dump。这个概括的说是因为*(p+i) = 0 是这条语句破坏了程序的运行布局。包括改了只读存储区的内容或者是改了其他的东西，比如改了返回地址。 比如，我将程序稍微改了一下，将循环次数从10000次改为8次，程序仍然会出现core dump。（8这个数字是个临界值） 调试core文件： gdb a.out core Core was generated by `./a.out'. Program terminated with signal 11, Segmentation fault. #0  0x00000000 in ?? () (gdb) p $pc $1 = (void (*)()) 0 发现问题了，发生crash的时候，pc值指向了0，这个pc值就是在函数test2中，进行*(p+i) = 0的时候，由于栈是从大到小，而且程序的返回地址是存在局部变量之前，即高地址，所以*(p+i)=0，会将pc值改为了0，这样在test2返回的时候，返回到了地址0，所以就出现了crash。  之前说过8是个临界数字，如果大于等于8会出现crash，为7或者更小就不会出现crash，但是局部变量的值可能会出现意想不到的的改变。

user: user-2
content: 越界是未定义行为，不能用没有运行出错来证明正确

user: user-3
content: 进程意外退出会在当前目录下产生‘core’文件或形如‘core.数字’的文件比如‘core.1234’ 使用命令 gdb 运行程序名 core或core.数字 进入gdb然后使用bt命令 可以查看进程意外退出前函数调用的堆栈，内容为从上到下列出对应从里层到外层的函数调用历史。 如果进程意外退出不产生core文件，参考“ulimit -c core文件最大块大小”命令

user: user-4
content: 我怀疑，跟编译选项没关系。起码错误（段错误）跟编译选项没关系

user: user-4
content: for(int i=0;i<10000;++i){                   *(p+i)=0;                   printf("%d %p\n",i,p+i);                   //sleep(2);         } 这部分代码可能会遇到非法地址访问，因为p+i，指向的地址很有可能会越过此函数栈，并访问非法地址，所以会导致段错误。 如果要验证局部变量在函数栈上是如何分配的，分配顺序是怎样的，建议你定义一个局部数组，然后打印数组中的元素和它的地址。这样起码不会越界（10000太大了，10000  * 4，访问的大小约40k字节，这个函数栈不会有这么大，因为你定义的这个函数里没有局部变量需要这么大的。