Неожиданный выпуск программы Bubblesort с MSVC vs TCC
Один из моих друзей отправил мне этот код, сказав, что он работает не так, как ожидалось:
#include<stdio.h>
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 < 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",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 Command Line Tool для компиляции на компьютере под управлением Windows XP.
cl /EHsc bubblesort01.c
Мой друг получает правильный вывод на машине динозавра (код скомпилирован с использованием TCC там).
Мой выход неожиданен. Массив таинственно растет в размерах, между ними.
Если вы измените код так, чтобы переменная sizeOfInput
была изменена на sizeOfInputt
, она дает ожидаемые результаты!
Поиск по Центр разработки Microsoft Visual С++ не дает никаких результатов для "sizeOfInput".
Я не эксперт по C/С++, и мне интересно узнать, почему это происходит - любые эксперты C/С++, которые могут "пролить свет" на это?
Несвязанное примечание: я серьезно подумал о переписывании всего кода, чтобы использовать сортировку quicksort или merge, прежде чем публиковать ее здесь. Но, в конце концов, это не Stooge sort...
Изменить: я знаю, что код неверен (он читается за последним элементом), но мне любопытно, почему имя переменной имеет значение.
Ответы
Ответ 1
Как упоминается ответ на interjay, как только вы попадаете в поведение undefined, все ставки отключены. Однако, когда вы сказали, что просто переименование переменной изменило поведение программы, мне стало интересно, что происходит (undefined или нет).
Во-первых, я не думал, что переименование переменной изменит выход компилятора (при прочих равных условиях), но, конечно же, - когда я его попробовал, я был удивлен, увидев именно то, что вы описали.
Итак, у меня был компилятор, дамп сборки для кода, который он генерировал для каждой версии исходного файла, и провел сравнение. Вот что я нашел в компиляторах описание того, как он выделял локальные переменные:
***** 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
, поведение undefined, возникающее при изменении a[10]
, меняет значение sizeOfInput
. В сборке, которая использует имя sizeOfInputt
, поскольку эта переменная не находится в конце массива, запись в a[10]
уничтожает что-то еще.
Что касается того, почему компилятор будет выставлять переменные по-разному, когда имя одного изменяется, по-видимому, незначительно - я понятия не имею.
Но это хороший пример того, почему вы не должны рассчитывать на компоновку локальных переменных (или почти любых переменных, хотя вы можете рассчитывать на порядок размещения элементов структуры) и почему, когда дело доходит до undefined поведение, "он работает на моей машине", не режет его как доказательство того, что что-то работает.
Ответ 2
Ваш код читается за концом массива. Максимальное значение outer
равно 10, а максимальное значение inner
равно 9, поэтому a[inner+1]
будет читать a[10]
. Это даст вам undefined поведение, что объясняет, почему разные компиляторы дают разные результаты.
Что касается имени переменной, делающего разницу: вероятно, нет. Возможно, если вы дважды запустите тот же код (используя одно и то же имя переменной), вы получите разные результаты. В принципе, при вызове поведения undefined вы не можете быть уверены в том, что ваша программа будет делать, поэтому лучше не пытаться найти смысл в таких вещах, как имена переменных.
Также существует вероятность, что имя переменной имеет значение. Это зависит от реализации компилятора: например, использование другого имени переменной может заставить компилятор каким-то образом организовать память по-разному, что может привести к тому, что программа будет работать по-разному. Я думаю, что большинство компиляторов выведет один и тот же код, если вы измените имена переменных, хотя это, вероятно, было просто удачей.
Ответ 3
Michael Burr reply раскрывает интересное поведение компилятора. Но я все же сомневаюсь, что имя локальной переменной может повлиять на его расположение в стеке для активной записи функции.
Я использую VС++ 2013 с командной строкой выше (cl.exe/EHsc progam.cpp) и не могу ее перепрограммировать - изменение имени переменной не изменяет поведение программы, вместо этого она стабильно обнаруживает случайный сбой (некоторые прогоны возвращают результаты хорошо, а некоторые запускаются сбой).
Причина вышеупомянутого случайного сбоя __security_cookie
хранится непосредственно над (большим адресом) массивом a
, а так как a определяется как целочисленный целочисленный массив, результат будет зависеть от знакового бита (неверно интерпретируемый ) значения __security_cookie
. Если это положительное целое число больше 100, оно по-прежнему является наибольшим значением в массиве a
, поэтому сортировка не переключит его на другие позиции, тогда проверка (__security_check_cookie
) в конце функции будет прекрасной, Если оно меньше 100 или отрицательное целое число, оно будет переключено на нижний элемент в массиве после сортировки, поэтому __security_check_cookie
сообщает об ошибке. Это означает, что поведение программы зависит от случайного сгенерированного значения для __security_cookie
.
Я изменил исходную тестовую программу ниже, чтобы облегчить тестирование. Я также расширил вывод, чтобы включить элемент by-by-one (arrayLen + 1), и мы могли предсказать поведение, основанное на исходном значении в элементе после определенного массива a
.
#include<stdio.h>
#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]);
}