Bubblesort程序与MSVC和TCC的意外输出

我的一个朋友把这个代码发给了我,说它没有按预期工作:

#include void main() { int a [10] ={23, 100, 20, 30, 25, 45, 40, 55, 43, 42}; int sizeOfInput = sizeof(a)/sizeof(int); int b, outer, inner, c; printf("Size is : %d \n", sizeOfInput); printf("Values before bubble sort are : \n"); for ( b = 0; b < sizeOfInput; b++) printf("%d\n", a[b]); printf("End of values before bubble sort... \n"); for ( outer = sizeOfInput; outer > 0; outer-- ) { for ( inner = 0 ; inner  a[inner + 1] ) { int tmp = a[inner]; a[inner] = a [inner+1]; a[inner+1] = tmp; } } printf ( "Bubble sort total array size after inner loop is %d :\n",sizeOfInput); printf ( "Bubble sort sizeOfInput after inner loop is %d :\n",sizeOfInput); } printf ( "Bubble sort total array size at the end is %d :\n",sizeOfInput); for ( c = 0 ; c < sizeOfInput; c++) printf("Element: %d\n", a[c]); } 

我正在使用Micosoft Visual Studio命令行工具在Windows XP计算机上进行编译。 cl /EHsc bubblesort01.c

我的朋友在恐龙机器上获得了正确的输出(代码是使用TCC编译的)。
我的输出很意外。 arrays之间神秘地增长。

如果更改代码以便将变量sizeOfInput更改为sizeOfInputt ,则会给出预期结果!

在Microsoft Visual C ++开发人员中心进行的搜索不会为“sizeOfInput”提供任何结果。

我不是C / C ++专家,很想知道为什么会发生这种情况 – 任何C / C ++专家都可以对此有所了解?
无关的注意事项:我认真考虑重写整个代码以使用快速排序或合并排序,然后再将其发布到此处。 但是,毕竟,它不是Stooge排序……

编辑:我知道代码不正确(它读取超出最后一个元素),但我很好奇为什么变量名称有所不同。

就像interjay提到的答案一样,一旦你进入未定义的行为,所有的赌注都会被取消。 但是,当你说只是重命名变量改变了程序的行为时,我对正在发生的事情感到好奇(未定义与否)。

首先,我不相信重命名变量会改变编译器的输出(所有其他条件相同),但果然 – 当我尝试它时,我很惊讶地看到你所描述的确切内容。

所以我让编译器转储它为每个源文件版本生成的代码的程序集,然后进行比较。 这是我在编译器描述中如何布局局部变量的内容:

  ***** test.sizeOfInput.cod _c$ = -56 ; size = 4 _b$ = -52 ; size = 4 _inner$ = -48 ; size = 4 _a$ = -44 ; size = 40 >>> _sizeOfInput$ = -4 ; size = 4 _main PROC ***** test.sizeOfInputt.cod _c$ = -56 ; size = 4 >>> _sizeOfInputt$ = -52 ; size = 4 _b$ = -48 ; size = 4 _inner$ = -44 ; size = 4 _a$ = -40 ; size = 40 _main PROC ***** 

您会注意到,当变量名为sizeOfInput ,编译器将其放在比数组a更高的地址(刚好超过数组的末尾),并且当变量名为sizeOfInputt它将其置于比数组a而不是刚刚结束数组。 这意味着在具有名为sizeOfInput的变量的构建中,修改a[10]时发生的未定义行为正在更改sizeOfInput的值。 在使用名称sizeOfInputt的构建中,由于该变量不在数组的末尾,因此对a[10]的写入会破坏其他内容。

至于为什么编译器会以一种明显无关紧要的方式改变变量的方式来排列变量 – 我不知道。

但这是一个很好的例子,为什么你不应该依赖局部变量的布局(或几乎任何变量,尽管你可以依赖于struct元素的布局顺序),以及为什么当涉及到未定义的行为时,“它在我的机器上工作“并没有把它作为certificate有效的证据。

您的代码读取超过数组的末尾。 outer的最大值为10, inner的最大值为9,因此a[inner+1]将读取a[10] 。 这将为您提供未定义的行为 ,这解释了为什么不同的编译器会给出不同的结果。

至于变量名称有所不同:它可能没有。 如果您运行相同的代码两次(使用相同的变量名称),则可能会得到不同的结果。 基本上,当调用未定义的行为时,您无法确定程序将执行的任何操作,因此最好不要尝试在变量名称之类的内容中查找含义。

变量名也有可能产生影响。 这取决于编译器的实现:例如,使用不同的变量名可能会使编译器以某种方式组织内存,这可能导致程序不同。 我认为如果你改变变量名,大多数编译器会输出相同的代码,所以这可能只是运气问题。

Michael Burr的回复揭示了编译器的一个有趣行为。 但我仍然怀疑局部变量名可能会影响其在函数的活动记录堆栈中的布局。

我使用VC ++ 2013上面的命令行(cl.exe / EHsc progam.cpp)并且无法重复它 – 更改变量名称不会改变程序行为,而是显示随机崩溃稳定(某些运行返回结果很好,一些运行崩溃)。

上述随机崩溃的原因是__security_cookie直接存储在数组a上方(较大地址),而a被定义为烧结整数数组,结果将取决于__security_cookie值的符号位(误解释)。 如果它是一个大于100的正整数,它仍然是数组a最大值,所以sort不会将它切换到其他位置,那么函数末尾的检查( __security_check_cookie )就可以了。 如果它小于100或负整数,它将在排序后切换到数组中的lower元素,因此__security_check_cookie报告失败。 这意味着程序行为取决于__security_cookie的随机生成值。

我将原始测试程序更改为以下以简化测试。 我还将输出扩展为包含off-by-one元素(arrayLen + 1),并且我们可以根据定义的数组a之后的元素中的原始值来预测行为。

 #include #define arrayLen sizeOfInputt void main() { int a [10] ={23, 100, 20, 30, 25, 45, 40, 55, 43, 42}; int arrayLen = sizeof(a)/sizeof(int); int b, outer, inner, c; printf("Size is : %d \n", arrayLen); printf("Values before bubble sort are : \n"); for ( b = 0; b < arrayLen + 1; b++) printf("%d\n", a[b]); printf("End of values before bubble sort... \n"); for ( outer = arrayLen; outer > 0; outer-- ) { for ( inner = 0 ; inner < outer ; inner++) { printf ( "Comparing positions: %d and %d\n",inner,inner+1); if ( a[inner] > a[inner + 1] ) { int tmp = a[inner]; a[inner] = a [inner+1]; a[inner+1] = tmp; } } printf ( "Bubble sort total array size after inner loop is %d :\n",arrayLen); printf ( "Bubble sort arrayLen after inner loop is %d :\n",arrayLen); } printf ( "Bubble sort total array size at the end is %d :\n",arrayLen); for ( c = 0 ; c < arrayLen; c++) printf("Element: %d\n", a[c]); }