返回信息流下面是自己写的一个简单的插入排序程序.
#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]
------
真心不知道是什么原因,个人水平有限,对两个编译器不是很了解,请教一下,为什么会出现上面这种诡异的现象呢??是不是和机器有关系?还是两个编译器各自有什么特点呢?请大神指点迷津,感激不尽!
这是一条镜像帖。来源:北邮人论坛 / cpp / #77351同步于 2014/3/7
该镜像源已超过 30 天没有更新,可能在源站已被删除。
CPP机器人发帖
[问题]请教一个诡异的c++ 问题,与编译器有点关系~
szm398828953
2014/3/7镜像同步5 回复
订阅后,新回复会通过你的通知中心匿名送达。
5 条回复
VS里数组索引是int型,a[a^32-1] = a[-1] 也就是a[0]前一个int值,不同的编译器,不同的时候,访问这个值当然不一样啊,而且访问这个值可能会出错也可能不会,因为a在栈中,所以引起访问错误的概率很小。还有你最后定义的那两个size_t用意何在?感觉没用啊,一般情况下release会直接把这两行代码优化掉。
非常感谢!我上面可能描述比较混乱,没说清楚~ 抱歉!之所以用 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会直接把这两行代码优化掉。
先看看我在虚拟机上的一个运行结果,跟你的不同,但道理是一样的。
没有那两行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),只不过两者都数组越界访问了而已。
: ...................
非常详细~ 我基本明白了,我再仔细看看~ 谢谢你花费时间解答问题~!!
【 在 gaoweiwei 的大作中提到: 】
: 先看看我在虚拟机上的一个运行结果,跟你的不同,但道理是一样的。
: 没有那两行size_t 的结果:
: 1 3 12 23 43 54 67 67 87
: ...................