Я недавно начал изучать C, и я беру класс с C в качестве предмета. В настоящее время я играю с петлями и сталкиваюсь со странным поведением, которое не знаю, как объяснить.
#include <stdio.h>
int main()
{
int array[10],i;
for (i = 0; i <=10 ; i++)
{
array[i]=0; /*code should never terminate*/
printf("test \n");
}
printf("%d \n", sizeof(array)/sizeof(int));
return 0;
}
На моем ноутбуке под управлением Ubuntu 14.04 этот код не ломается. Это бежит к завершению. На компьютере моей школы под управлением CentOS 6.6 он также работает нормально. В Windows 8.1 цикл никогда не завершается.
Еще более странным является то, что когда я изменяю условие for
цикла на:, i <= 11
код заканчивается только на моем ноутбуке с Ubuntu. Он никогда не заканчивается в CentOS и Windows.
Может кто-нибудь объяснить, что происходит в памяти и почему разные ОС, работающие с одним и тем же кодом, дают разные результаты?
РЕДАКТИРОВАТЬ: Я знаю, что цикл for выходит за пределы. Я делаю это намеренно. Я просто не могу понять, как поведение может отличаться в разных ОС и компьютерах.
i
сохраняется сразу после концаarray
, и вы перезаписываете ееarray[10]=0;
. Это может быть не так в оптимизированной сборке на той же платформе, которая может хранитьсяi
в регистре и вообще никогда не ссылаться на него в памяти.Ответы:
Вы только что обнаружили, что топание памяти. Вы можете прочитать больше об этом здесь: Что такое «топот памяти»?
При выделении
int array[10],i;
эти переменные помещаются в память (в частности, они размещаются в стеке, который представляет собой блок памяти, связанный с функцией).array[]
иi
, вероятно, соседствуют друг с другом в памяти. Похоже, что на Windows 8.1,i
находится по адресуarray[10]
. На CentOS,i
находится по адресуarray[11]
. А в Ubuntu его нет ни в одном месте (может быть, вarray[-1]
?).Попробуйте добавить эти операторы отладки в ваш код. Вы должны заметить, что на итерации 10 или 11
array[i]
указывает наi
.источник
array[10]
разрушает фрейм стека. Как может быть разница между кодом с выводом отладки или без него? Если адресi
никогда не нужен, компилятор может оптимизироватьi
. в регистр, таким образом изменяя расположение памяти в стеке ...array[10]=0
. Если вы скомпилировали свой код с оптимизацией, это, вероятно, не произойдет (потому что C имеет правила псевдонимов, которые ограничивают то, какие типы обращений к памяти должны перекрывать другую память. Как локальная переменная, адрес которой вы никогда не берете, я думаю, что компилятор должен быть в состоянии предположить, что ничто не псевдоним это. массива - неопределенное поведение. Всегда старайтесь избегать зависимости от этогоОшибка лежит между этими частями кода:
Поскольку
array
всего 10 элементов, на последней итерацииarray[10] = 0;
происходит переполнение буфера. Переполнения буфера НЕПРАВИЛЬНОЕ ПОВЕДЕНИЕ , что означает, что они могут отформатировать ваш жесткий диск или заставить демонов вылететь из вашего носа.Все переменные стека довольно распространены рядом друг с другом. Если
i
находится там , гдеarray[10]
записи на, то УБ будет сброшеныi
на0
, что приводит к несогласованной петле.Чтобы исправить, измените условие цикла на
i < 10
.источник
rm -rf /
даже когда вы не root, не «форматирование» всего диска, конечно, но уничтожение всех ваших данных. Уч.В каком последнем запуске цикла вы пишете
array[10]
, но в массиве всего 10 элементов, пронумерованных от 0 до 9. Спецификация языка C говорит, что это «неопределенное поведение». На практике это означает, что ваша программа будет пытаться записать в тотint
размерный фрагмент памяти, который находится сразуarray
в памяти. То, что происходит затем, зависит от того, что на самом деле лежит, и это зависит не только от операционной системы, но в большей степени от компилятора, от параметров компилятора (таких как настройки оптимизации), от архитектуры процессора, от окружающего кода и т. д. Он может даже варьироваться от исполнения к исполнению, например, из-за рандомизации адресного пространства (возможно, не в этом игрушечном примере, но это происходит в реальной жизни). Некоторые возможности включают в себя:i
. Цикл никогда не заканчивается, потому чтоi
перезапускается с 0.array
находится в конце страницы виртуальной памяти, а следующая страница не отображается.Что вы заметили в Windows, так это то, что компилятор решил поместить переменную
i
сразу после массива в память, поэтому вarray[10] = 0
конечном итоге присвоилi
. В Ubuntu и CentOS компилятор там неi
размещался. Почти во всех реализациях C локальные переменные группируются в памяти, в стеке памяти , за одним главным исключением: некоторые локальные переменные могут быть полностью помещены в регистры . Даже если переменная находится в стеке, порядок переменных определяется компилятором, и он может зависеть не только от порядка в исходном файле, но и от их типов (чтобы не тратить память на ограничения выравнивания, которые могли бы оставить дыры) по их именам, по некоторым хеш-значениям, используемым во внутренней структуре данных компилятора и т. д.Если вы хотите узнать, что решил сделать ваш компилятор, вы можете сообщить ему код ассемблера. Да, и научитесь расшифровывать ассемблер (это проще, чем писать). С GCC (и некоторыми другими компиляторами, особенно в мире Unix), передайте возможность
-S
создавать ассемблерный код вместо двоичного. Например, вот фрагмент ассемблера для цикла от компиляции с GCC на amd64 с опцией оптимизации-O0
(без оптимизации) с комментариями, добавленными вручную:Здесь переменная
i
находится на 52 байта ниже вершины стека, а массив начинается на 48 байтов ниже вершины стека. Так что этот компилятор оказалсяi
перед массивом; ты бы переписалi
если бы вам пришлось писатьarray[-1]
. Если вы изменитеarray[i]=0
наarray[9-i]=0
, вы получите бесконечный цикл на этой конкретной платформе с этими опциями компилятора.Теперь давайте скомпилируем вашу программу с
gcc -O1
.Это короче! Компилятор не только отказался выделить место в стеке для
i
- он только когда-либо хранится в регистреebx
- но он не потрудился выделить памятьarray
или сгенерировать код для установки его элементов, потому что он заметил, что ни один из элементов когда-либо используются.Чтобы сделать этот пример более наглядным, давайте удостоверимся, что назначения массива выполнены, предоставив компилятору то, что он не может оптимизировать. Самый простой способ сделать это - использовать массив из другого файла - из-за отдельной компиляции компилятор не знает, что происходит в другом файле (если он не оптимизирует во время компоновки, что
gcc -O0
илиgcc -O1
нет). Создать исходный файлuse_array.c
содержащийи измените свой исходный код на
Компилировать с
На этот раз ассемблерный код выглядит так:
Теперь массив находится в стеке, 44 байта сверху. Как насчет
i
? Это нигде не появляется! Но счетчик цикла хранится в реестреrbx
. Это не совсемi
, но адресarray[i]
. Компилятор решил, что, поскольку значениеi
никогда не использовалось напрямую, не было смысла выполнять арифметику для вычисления места хранения 0 во время каждого запуска цикла. Вместо этого этот адрес является переменной цикла, и арифметика для определения границ была выполнена частично во время компиляции (умножить 11 итераций на 4 байта на элемент массива, чтобы получить 44) и частично во время выполнения, но один раз и навсегда до запуска цикла ( выполнить вычитание, чтобы получить начальное значение).Даже на этом очень простом примере мы видели, как изменение параметров компилятора (включение оптимизации) или изменение чего-то незначительного (
array[i]
наarray[9-i]
) или даже изменение чего-то явно не связанного (добавление вызоваuse_array
) может существенно изменить то, что генерировала исполняемая программа компилятором делает. Оптимизация компилятора может сделать много вещей, которые могут казаться неинтуитивными в программах, которые вызывают неопределенное поведение . Вот почему неопределенное поведение остается полностью неопределенным. Когда вы слегка отклоняетесь от треков, в реальных программах может быть очень трудно понять связь между тем, что делает код, и тем, что он должен был сделать, даже для опытных программистов.источник
В отличие от Java, C не выполняет проверку границ массива, т. Е. Нет
ArrayIndexOutOfBoundsException
, работа по проверке правильности индекса массива остается за программистом. Выполнение этого нарочно приводит к неопределенному поведению, может произойти все что угодно.Для массива:
индексы действительны только в диапазоне
0
до9
. Тем не менее, вы пытаетесь:доступ
array[10]
здесь, изменить состояние наi < 10
источник
У вас есть нарушение границ, и на не завершающих платформах я полагаю, что вы случайно устанавливаете
i
ноль в конце цикла, чтобы он начинался заново.array[10]
является недействительным; она содержит 10 элементов,array[0]
черезarray[9]
, иarray[10]
является 11 - м. Ваш цикл должен быть написан , чтобы остановить до10
того , как следует:Там, где
array[10]
земли определяются реализацией, и, что забавно, на двух ваших платформах, они приземляются, наi
которых эти платформы, очевидно, сразу и выкладываютсяarray
.i
установлен на ноль и цикл продолжается вечно. Для других ваших платформi
может быть расположен доarray
илиarray
может иметь некоторые отступы после него.источник
Вы объявляете
int array[10]
средствоarray
имеет индекс0
к9
(всего10
целочисленных элементов он может содержать). Но следующий цикл,будет цикл
0
в10
средство11
времени. Следовательно, когдаi = 10
он переполнит буфер и вызовет неопределенное поведение .Так что попробуйте это:
или,
источник
Не определено в
array[10]
и дает неопределенное поведение как описано ранее. Думайте об этом так:У меня есть 10 товаров в моей продуктовой корзине. Они есть:
0: коробка с хлопьями
1: хлеб
2: молоко
3: пирог
4: яйца
5: торт
6: 2 литра соды
7: салат
8: гамбургеры
9: мороженое
cart[10]
не определено, и может давать исключение вне границ в некоторых компиляторах. Но, очевидно, многие этого не делают. Кажущийся 11-й предмет - это товар, которого нет в корзине.Одиннадцатый пункт указывает на то, что я собираюсь назвать «полтергейстом». Этого никогда не было, но оно было там.Почему некоторые компиляторы дают
i
индексarray[10]
илиarray[11]
дажеarray[-1]
из - за вашу инициализацию / оператор объявления. Некоторые компиляторы интерпретируют это как:int
дляarray[10]
и еще одинint
блок. Чтобы было проще, расположите их рядом друг с другом».array[10]
это не указывало наi
.i
вarray[-1]
(потому что индекс массива не может или не должен быть отрицательным), или размещайте его в совершенно другом месте, потому что ОС может с этим справиться, и это безопаснее.Некоторые компиляторы хотят, чтобы дела шли быстрее, а некоторые компиляторы предпочитают безопасность. Это все о контексте. Например, если бы я разрабатывал приложение для древней ОС BREW (ОС обычного телефона), его не волновало бы безопасность. Если бы я разрабатывал для iPhone 6, то он мог бы работать быстро, несмотря ни на что, поэтому мне нужно было бы сделать упор на безопасность. (Серьезно, вы читали Руководство Apple App Store или читали о разработке Swift и Swift 2.0?)
источник
Поскольку вы создали массив размером 10, условие цикла должно быть следующим:
В настоящее время вы пытаетесь получить доступ к неназначенному местоположению из памяти,
array[10]
и это вызывает неопределенное поведение . Неопределенное поведение означает, что ваша программа будет вести себя неопределенным образом, поэтому она может давать разные результаты при каждом выполнении.источник
Ну, компилятор C традиционно не проверяет границы. Вы можете получить ошибку сегментации, если вы ссылаетесь на местоположение, которое не «принадлежит» вашему процессу. Тем не менее, локальные переменные размещаются в стеке, и в зависимости от того, как распределена память, область, находящаяся за пределами array (
array[10]
), может принадлежать сегменту памяти процесса. Таким образом, ловушка ошибки сегментации не создается, и это то, что вы, похоже, испытываете. Как уже отмечали другие, это неопределенное поведение в C, и ваш код может считаться ошибочным. Поскольку вы изучаете C, вам лучше привыкнуть к проверке границ в вашем коде.источник
Помимо возможности того, что память может быть размещена так, чтобы попытка записи
a[10]
фактически перезаписываласьi
, также было бы возможно, чтобы оптимизирующий компилятор мог определить, что тест цикла не может быть достигнут со значениемi
больше десяти без кода, впервые обращавшегося к несуществующий элемент массиваa[10]
.Поскольку попытка доступа к этому элементу будет неопределенным поведением, компилятор не будет иметь никаких обязательств в отношении того, что программа может сделать после этого момента. Более конкретно, поскольку компилятор не будет обязан генерировать код для проверки индекса цикла в любом случае, когда он может быть больше десяти, он вообще не будет обязан генерировать код для его проверки; вместо этого можно предположить, что
<=10
тест всегда будет давать значение true. Обратите внимание, что это будет верно, даже если код будет читать,a[10]
а не писать.источник
Когда вы выполняете итерацию «мимо»,
i==9
вы назначаете ноль «элементам массива», которые фактически располагаются за массивом , поэтому вы перезаписываете другие данные. Скорее всего, вы перезаписываетеi
переменную, которая находится послеa[]
. Таким образом, вы просто сбрасываетеi
переменную в ноль и перезапускаете цикл.Вы можете узнать это сами, если напечатаете
i
в цикле:вместо просто
Конечно, этот результат сильно зависит от распределения памяти для ваших переменных, что, в свою очередь, зависит от компилятора и его настроек, поэтому это, как правило, неопределенное поведение - поэтому результаты на разных машинах, разных операционных системах или разных компиляторах могут отличаться.
источник
ошибка в массиве порций [10], w / c также является адресом i (массив int [10], i;). если для массива [10] задано значение 0, то значение i будет равно 0, поскольку c / сбрасывает весь цикл и вызывает бесконечный цикл. будет бесконечный цикл, если массив [10] находится между 0-10. правильный цикл должен быть для (i = 0; i <10; i ++) {...} int array [10], i; для (i = 0; i <= 10; i ++) array [i] = 0;
источник
Я предложу кое-что, что я не нашел выше:
Попробуйте назначить массив [i] = 20;
Я думаю, что это должно завершить код везде ... (учитывая, что вы сохраняете i <= 10 или ll)
Если это произойдет, вы можете твердо решить, что ответы, указанные здесь, уже являются правильными [ответ, связанный с растоптанием памяти, например.]
источник
Здесь две вещи не так. Int i на самом деле является элементом массива array [10], как видно из стека. Поскольку вы позволили индексированию фактически сделать массив [10] = 0, индекс цикла i никогда не будет превышать 10. Сделайте это
for(i=0; i<10; i+=1)
.i ++ - это, как сказал бы K & R , «плохой стиль». Он увеличивает i на величину i, а не 1. i ++ - для математики указателей, а i + = 1 - для алгебры. Хотя это зависит от компилятора, это не хорошее соглашение для переносимости.
источник
i
является Notan элемента массиваa[10]
, нет никаких обязательств или даже предложения для компилятора , чтобы положить его в стеке сразу послеa[]
- он может также быть расположен перед массивом, или отделяться с некоторым дополнительным пространством. Он может быть даже размещен вне основной памяти, например, в регистре процессора. Это также неверно++
для указателей, а не для целых чисел. Совершенно неверно, что «i ++ увеличивает i на величину i» - прочтите описание оператора в определении языка!i
- этоint
тип. Это целое число , а не указатель; целое число, используемое в качестве индекса кarray
,.