#include <stdio.h>
int main(int argc, char *argv[])
{
int arr[5] = {1, 2, 3, 4, 5};
arr[6] = 7; // [1, 2, 3, 4, 5, 6]
printf("arr[6] = %d\n", arr[6]);
return 0;
}
Насколько я помню, раньше при компиляции выдавалось предупреждающее сообщение, а при запуске — неизвестное значение. (Или я мог видеть сообщение о неисправности сегмента).
Поэтому хотелось бы знать, с какой версии приведенный выше код начал работать корректно.
Моя текущая версия GCC выглядит следующим образом:
gcc-13 (Homebrew GCC 13.2.0) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Примечание: комментарий в вашем коде неверен. arr[6] = 7; // [1, 2, 3, 4, 5, 6]
— вы назначаете 7
седьмому элементу, поэтому [1, 2, 3, 4, 5, indeterminate, 7]
было бы более подходящим (если бы в коде было определено поведение)
Этот вопрос похож на: Насколько опасно получать доступ к массиву за пределами его границ?. Если вы считаете, что это другое, отредактируйте вопрос, поясните, чем он отличается и/или как ответы на этот вопрос не помогают решить вашу проблему.
Тест: замените определение arr на int x=1, arr[5] = {1, 2, 3, 4, 5}, y=2;
. Добавьте printf("x=%d, y=%d\n",x,y);
внизу. Если запустить программу, я получу x=1, y=7
. Итак, вы знаете, где хранится arr[6]
.
@Wiimm — В компиляторе, который я использую, значение нигде не сохраняется. Он просто оптимизирует массив и делает printf("arr[6] = %d\n", 7);
.
Спасибо всем ответили! Я понимаю, что мой вопрос был неточным, и хорошие ответы более чем компенсировали мою неясность!
Вы используете слово «правильно работать» неправильно. Чтобы что-то работало правильно, должно быть определение правильного поведения, но его нет, и код имеет неопределенное поведение во всех версиях.
Это означает, что программа может выйти из строя, напечатать «привет, мир», зависнуть или сделать что угодно, включая печать arr[6] = 7
, что не более правильно, чем любое другое поведение.
Отсутствие сбоя просто означает, что любая память, которую вы перезаписали с помощью доступа к записи за пределами границ, не привела к немедленному сбою выполнения кода, например. перезапись обратного адреса.
Тем не менее, ваша программа перезаписала часть памяти, которую она не должна была делать в пределах своих выделенных переменных. А в других случаях это могла быть локальная переменная в стеке, которая могла бы позже изменить поток кода.
Повреждение памяти может привести ко многим вещам, а не только к "немедленному сбою сегмента", а не к немедленному сбою - это не "программа работает нормально :) :)"
Возьмем, например. программа
#include <stdio.h>
void bad_func(int index) {
int arr[5] = {1, 2, 3, 4, 5};
arr[index] = 7;
printf("arr[%d] = %d\n", index, arr[index]);
return;
}
int main(int argc, char *argv[])
{
for(int i = 0; i < 9999; i++) {
bad_func(i),
printf("..survived, phew!\n");
}
return 0;
}
скомпилировано с помощью gcc -o crash crash.c
. Это означает, что мы медленно увеличиваем индекс, при котором происходит повреждение памяти. На моей машине с Ubuntu с gcc 12.3.0 я получаю:
$ ./crash
arr[0] = 7
..survived, phew!
arr[1] = 7
..survived, phew!
arr[2] = 7
..survived, phew!
arr[3] = 7
..survived, phew!
arr[4] = 7
..survived, phew!
arr[5] = 7
..survived, phew!
arr[6] = 7
*** stack smashing detected ***: terminated
Aborted (core dumped)
В Windows с GCC 14.1.0 я получаю
>crash.exe
arr[0] = 7
..survived, phew!
arr[1] = 7
..survived, phew!
arr[2] = 7
..survived, phew!
arr[3] = 7
..survived, phew!
arr[4] = 7
..survived, phew!
arr[5] = 7
..survived, phew!
arr[6] = 7
..survived, phew!
arr[7] = 7
..survived, phew!
arr[8] = 7
..survived, phew
<crash>
Таким образом, в зависимости от компилятора и явного расположения памяти, SEGFAULT происходит по определенным индексам.
Как было сказано ранее, сбой SEGFAULT — это лишь одно из возможных последствий записи из-за нехватки памяти, которую делает ваша программа. Рассмотрим программу:
#include <stdio.h>
#include <string.h>
void bad_func(int index) {
int arr[5] = {1, 2, 3, 4, 5};
int my_variable = 0;
arr[index] = 7;
printf("arr[%d] = %d\n", index, arr[index]);
if (my_variable) {
printf("my_variable suddenly changed!! %d\n", my_variable);
}
return;
}
int main(int argc, char *argv[])
{
for(int i = 0; i < 9999; i++) {
bad_func(i),
printf("..survived, phew!\n");
}
return 0;
}
при компиляции в моей системе Linux с помощью gcc -O0 -o crash -fno-stack-protector crash.c
я получаю
arr[0] = 7
..survived, phew!
arr[1] = 7
..survived, phew!
arr[2] = 7
..survived, phew!
arr[3] = 7
..survived, phew!
arr[4] = 7
..survived, phew!
arr[5] = 7
..survived, phew!
arr[6] = 7
..survived, phew!
arr[7] = 7
my_variable suddenly changed!! 7
..survived, phew!
arr[8] = 7
..survived, phew!
Memory access error (core dumped)
В этом конкретном случае расположения памяти доступ arr[7] фактически был эквивалентен перезаписи переменной int my_variable
. И это сказалось на дальнейшем ходе программы.
Насколько я помню, раньше при компиляции выдавалось предупреждающее сообщение, а при запуске — неизвестное значение. (Или я мог видеть сообщение о неисправности сегмента).
Поэтому хотелось бы знать, с какой версии приведенный выше код начал работать корректно.
Тестирование каждой версии GCC, доступной для x86-64 в Compiler Explorer, показывает, что ни одна из них не выдает диагностическое сообщение для этого доступа к массиву за пределами границ. Поэтому вряд ли ваши воспоминания верны.
Возможности включают в себя:
Неопределённое поведение не определено. Segfault не гарантируется, равно как и печать каких-либо «неизвестных значений».