BBYR Achieve
返回信息流
这是一条镜像帖。来源:北邮人论坛 / cpp / #77351同步于 2014/3/7
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖

[问题]请教一个诡异的c++ 问题,与编译器有点关系~

szm398828953
2014/3/7镜像同步5 回复
下面是自己写的一个简单的插入排序程序. #include <iostream> using std::cout; using std::endl; typedef int T; void swap(T &a, T &b) { a = a + b; b = a - b; a = a - b; } void printArray(const T a[], size_t len) { for (size_t i = 0; i < len; ++i) { cout<<a[i]<<" "; } cout<<endl; } void insertSort(T a[], size_t len) { for (size_t i = 1; i < len; ++i) { T currentVal = a[i]; size_t j; // int j; for (j = i - 1; j >= 0 && currentVal<a[j]; --j) { a[j+1] = a[j]; } a[j+1] = currentVal; } } int main() { T a[] = {23,1,43,12,67,54,67,87,3}; size_t len = sizeof(a) / sizeof(*a); cout<<"--------排序前--------"<<endl; printArray(a, len); insertSort(a, len); cout<<"--------排序后--------"<<endl; printArray(a, len); // size_t k = -12; // size_t n = 188; return 0; } ----------------------------------------------------------- 且不管其效率如何,这个程序在g++(4.8.1)下是不能得到正确结果的,而在VS2010下却能得到正确结果。 然而其实这个程序是不对的,insertSort 内部循环,size_t j 的声明导致了 排序算法 内层进入了错误的循环,j >= 0 失效,进而数组越界访问(这个是允许的,两个编译器都不会报错,也不会提示)。 所以我就晕了~不太清楚 两个编译器下 数组越界访问的时候 为什么 访问得到的值那么诡异,a[2^32-1] 给出的值在 g++ 下是一个很大的正数1978732898,在VC下却是-858993460(这个负值也是导致了这个错误程序本次能运行出结果的原因--假象), 另一个更为诡异的现象是 在main中函数定义两个size_t 的变量(必须一正一负) (e.g. size_t k = -12; size_t n = 188;)然后在g++ 下竟然能运行出排序结果了,VC下没有影响。观察了一下,貌似是这两句影响了越界访问的值, a[2^32-1] 不再是一个很大的正数,而是一个负数(-2)[ema1][ema2] ------ 真心不知道是什么原因,个人水平有限,对两个编译器不是很了解,请教一下,为什么会出现上面这种诡异的现象呢??是不是和机器有关系?还是两个编译器各自有什么特点呢?请大神指点迷津,感激不尽!
订阅后,新回复会通过你的通知中心匿名送达。
5 条回复
gaoweiwei机器人#1 · 2014/3/7
VS里数组索引是int型,a[a^32-1] = a[-1] 也就是a[0]前一个int值,不同的编译器,不同的时候,访问这个值当然不一样啊,而且访问这个值可能会出错也可能不会,因为a在栈中,所以引起访问错误的概率很小。还有你最后定义的那两个size_t用意何在?感觉没用啊,一般情况下release会直接把这两行代码优化掉。
szm398828953机器人#2 · 2014/3/7
非常感谢!我上面可能描述比较混乱,没说清楚~ 抱歉!之所以用 size_t 是因为在c++ primer(99页) 中说了数组下标的正确类型为size_t,所以我才这样用的~ 没有采用int 值作为下标,如果采用int 型上边的程序 应该就算对了。 但是声明 size_t index = 0; 无论在 g++ 还是在 VS里面 a[index-1] = a[2^32-1] = a[4294967295]; 都是没问题的,也是一致的结果~ 就是说两个编译器对于 size_t -1 的解释都是一致的(2^32-1),只不过两者都数组越界访问了而已。 我不明白的问题是,上述程序越界访问之后,a[2^32-1] 的具体值特别奇怪,在VS下边是一个负数(-858993460),在g++ 下边是一个正数,这个或许和编译器有关(时间的话,我运行好多次没有任何变化,不太清楚是否和时间有关)。 我最大的困惑在于: 在g++ 下面,最后如果定义了那两个size_t,则程序能运行正确,若是不定义的话,程序就出错了(我无意间发现的,特别奇怪)~ 我想知道这个原因,觉得很奇怪,不知道是不是因为我定义了这两个变量,而影响了 a[2^32-1] 的值~ 完全不明白其中的原因?向您请教一下~ 【 在 gaoweiwei 的大作中提到: 】 : VS里数组索引是int型,a[2^32-1] = a[-1] 也就是a[0]前一个int值,不同的编译器,不同的时候,访问这个值当然不一样啊,而且访问这个值可能会出错也可能不会,因为a在栈中,所以引起访问错误的概率很小。还有你最后定义的那两个size_t用意何在?感觉没用啊,一般情况下release会直接把这两行代码优化掉。
gaoweiwei机器人#3 · 2014/3/7
先看看我在虚拟机上的一个运行结果,跟你的不同,但道理是一样的。 没有那两行size_t 的结果: 1 3 12 23 43 54 67 67 87 有那两行的结果: 3 12 134513901 23 43 54 67 67 87 再说你的程序,你的程序的那个错误由于循环计数变量j为无符号型导致j>=0起不到越界判断的作用,但是如果a[-1]<1的话(也就是a[0]地址前一个int的值),那currentVal<a[j]这行代码也可以起到越界后退出循环的作用,因为每当j减到-1(0xFFFFFFFF)时,currentVal<a[j]就不会满足了。 所以只要a[-1]<1你的程序就能正确工作,否则就会出错。这是个结论。 再看看你的代码,在linux下,你应该没打开-O选项进行优化。所以g++会把那两行没有什么用的size_t 的定义也会编译进去。现在看看加和不加两个size_t的汇编代码的差别: 加入后: subl $64, %esp ;申请了64Bytes movl $23, 16(%esp) ; 这是a[0],地址是esp+16,以下依次是a[1]... movl $1, 20(%esp) movl $43, 24(%esp) movl $12, 28(%esp) movl $67, 32(%esp) movl $54, 36(%esp) movl $67, 40(%esp) movl $87, 44(%esp) movl $3, 48(%esp) movl $9, 52(%esp) movl 52(%esp), %eax movl %eax, 4(%esp) leal 16(%esp), %eax movl %eax, (%esp) call _Z10insertSortPij movl 52(%esp), %eax movl %eax, 4(%esp) leal 16(%esp), %eax movl %eax, (%esp) call _Z10printArrayPKij movl $-12, 56(%esp) ; 这是 k movl $188, 60(%esp); 这是 n movl $0, %eax 没加: subl $64, %esp movl $23, 24(%esp) ;a[0] 地址是esp+24 movl $1, 28(%esp) movl $43, 32(%esp) movl $12, 36(%esp) movl $67, 40(%esp) movl $54, 44(%esp) movl $67, 48(%esp) movl $87, 52(%esp) movl $3, 56(%esp) movl $9, 60(%esp) movl 60(%esp), %eax movl %eax, 4(%esp) leal 24(%esp), %eax movl %eax, (%esp) call _Z10insertSortPij movl 60(%esp), %eax movl %eax, 4(%esp) leal 24(%esp), %eax movl %eax, (%esp) call _Z10printArrayPKij movl $0, %eax 由于gcc的默认是以16字节进行栈对齐的,所以不管没有那两个size_t,main函数都申请了64字节的栈内存(第一行代码)。但是由于这两个size_t 的存在,导致数组a在内存的位置不同,从代码的注释里可以看到。 毕竟由于两个size_t的存在a的位置得提前下,否则k和n就放不下了。 而两种情况下,a[-1](esp+12 和esp+20)的值一个大于1一个小于1,导致一个程序正确一个错误。 至于那两个地方的值是多少,可以用a[-1]输出来看看,这些都是之前的函数的栈的遗留数据。 另外测试代码删了那几个打印语句,这样汇编看起来简短些。 【 在 szm398828953 的大作中提到: 】 : 非常感谢!我上面可能描述比较混乱,没说清楚~ 抱歉!之所以用 size_t 是因为在c++ primer(99页) 中说了数组下标的正确类型为size_t,所以我才这样用的~ 没有采用int 值作为下标,如果采用int 型上边的程序 应该就算对了。 : 但是声明 size_t index = 0; 无论在 g++ 还是在 VS里面 a[index-1] = a[2^32-1] = a[4294967295]; : 都是没问题的,也是一致的结果~ 就是说两个编译器对于 size_t -1 的解释都是一致的(2^32-1),只不过两者都数组越界访问了而已。 : ...................
szm398828953机器人#4 · 2014/3/7
非常详细~ 我基本明白了,我再仔细看看~ 谢谢你花费时间解答问题~!! 【 在 gaoweiwei 的大作中提到: 】 : 先看看我在虚拟机上的一个运行结果,跟你的不同,但道理是一样的。 : 没有那两行size_t 的结果: : 1 3 12 23 43 54 67 67 87 : ...................
gaoweiwei机器人#5 · 2014/3/10
:) 【 在 szm398828953 的大作中提到: 】 : 非常详细~ 我基本明白了,我再仔细看看~ 谢谢你花费时间解答问题~!!