Подтвердить что ты не робот

Почему компиляторы не предупреждают об индексах статического массива вне пределов?

Недавно мой коллега укусил плохо, выписав из строя статический массив в стеке (он добавил к нему элемент без увеличения размера массива). Разве компилятор не поймает такую ​​ошибку? Следующий код компилируется с помощью gcc, даже с параметрами -Wall -Wextra, и все же он явно ошибочен:

int main(void)
{
  int a[10];
  a[13] = 3;  // oops, overwrote the return address
  return 0;
}

Я уверен, что это поведение undefined, хотя я не могу найти отрывок из стандарта C99, говорящий так на данный момент. Но в простейшем случае, когда размер массива известен как время компиляции, а индексы известны во время компиляции, не должен ли компилятор выдавать предупреждение как минимум?

4b9b3361

Ответ 1

GCC предупреждает об этом. Но вам нужно сделать две вещи:

  • Включить оптимизацию. Без, по крайней мере, -O2, GCC не делает достаточного анализа, чтобы знать, что такое a, и что вы убежали от края.
  • Измените свой пример так, чтобы фактически использовался [], иначе GCC генерирует программу no-op и полностью отменил ваше задание.

.

$ cat foo.c 
int main(void)
{
  int a[10];
  a[13] = 3;  // oops, overwrote the return address
  return a[1];
}
$ gcc -Wall -Wextra  -O2 -c foo.c 
foo.c: In function ‘main’:
foo.c:4: warning: array subscript is above array bounds

BTW: Если вы вернули [13] в свою тестовую программу, это тоже не сработает, так как GCC снова оптимизирует массив.

Ответ 2

Вы пробовали -fmudflap с помощью GCC? Это проверки времени выполнения, но они полезны, так как чаще всего вы все равно должны делать расчетные индексы времени исполнения. Вместо того, чтобы молча продолжать работу, он уведомит вас об этих ошибках.

-fmudflap -fmudflapth -fmudflapir  Для интерфейсов, которые его поддерживают (C и С++), все рискованные инструменты разыменование указателя/массива операции, некоторые стандартные            функции библиотеки/кучи библиотеки и некоторые другие связанные конструкции с испытаниями на дальность/валидность. Модули, оснащенные такими инструментами            должны быть защищены от переполнения буфера, недействительного использования кучи, а некоторые другие классы программирования C/С++ ошибки. Инструмент-            tation опирается на отдельную библиотеку времени выполнения (libmudflap), которая будут связаны в программе, если -fmudflap предоставляется по ссылке            время. Управление временем выполнения инструментальной программы контролируется среды MUDFLAP_OPTIONS переменная. См. "Env            MUDFLAP_OPTIONS = -help a.out" для своих опций.

Используйте -fmudflapth вместо -fmudflap для компиляции и ссылки, если ваша программа многопоточная. использование -fmudflapir, кроме того            на -fmudflap или -fmudflapth, если инструментарий должен игнорировать чтение указателя. Это производит меньше приборов (и там,            преждевременное выполнение) и по-прежнему обеспечивает некоторую защиту от прямая копия памяти, пишет, но позволяет ошибочно            читать данные для распространения внутри программы.

Вот то, что mudflap дает мне для вашего примера:

[[email protected] cpp]$ gcc -fstack-protector-all -fmudflap -lmudflap mudf.c        
[[email protected] cpp]$ ./a.out
*******
mudflap violation 1 (check/write): time=1229801723.191441 ptr=0xbfdd9c04 size=56
pc=0xb7fb126d location=`mudf.c:4:3 (main)'
      /usr/lib/libmudflap.so.0(__mf_check+0x3d) [0xb7fb126d]
      ./a.out(main+0xb9) [0x804887d]
      /usr/lib/libmudflap.so.0(__wrap_main+0x4f) [0xb7fb0a5f]
Nearby object 1: checked region begins 0B into and ends 16B after
mudflap object 0x8509cd8: name=`mudf.c:3:7 (main) a'
bounds=[0xbfdd9c04,0xbfdd9c2b] size=40 area=stack check=0r/3w liveness=3
alloc time=1229801723.191433 pc=0xb7fb09fd
number of nearby objects: 1
[[email protected] cpp]$

У него есть множество опций. Например, он может разблокировать процесс gdb при нарушениях, может показать вам, где просочилась ваша программа (используя -print-leaks) или обнаруживать неинициализированные чтения переменных. Используйте MUDFLAP_OPTIONS=-help ./a.out, чтобы получить список параметров. Поскольку mudflap выводит только адреса, а не имена файлов и строки источника, я написал небольшой gawk script:

/^ / {
    file = gensub(/([^(]*).*/, "\\1", 1);
    addr = gensub(/.*\[([x[:xdigit:]]*)\]$/, "\\1", 1);
    if(file && addr) {
        cmd = "addr2line -e " file " " addr
        cmd | getline laddr
        print $0 " (" laddr ")"
        close (cmd)
        next;
    }
}

1 # print all other lines

Вытяните в него вывод грязи, и он отобразит исходный файл и строку каждой записи обратной линии.

Также -fstack-protector[-all]:

-fstack-protector    Извлеките дополнительный код, чтобы проверить переполнение буфера, например атаки с разбивкой пакетов. Это делается путем добавления защитной переменной к функциям с уязвимыми объектами. Сюда входят функции, которые вызывают alloca, и функции с буферами размером более 8 байтов. Охранники инициализируются, когда функция вводится, а затем проверяется, когда функция выходит. Если проверка защиты не удалась, выводится сообщение об ошибке и программа выходит из системы.

-fstack-protector-all    Как -fstack-протектор, за исключением того, что все функции защищены.

Ответ 3

Вы правы, поведение undefined. Указатели C99 должны указывать внутри или только один элемент за пределами объявленных или распределенных по иерархии данных структур.

Я никогда не мог понять, как люди gcc решат, когда следует предупреждать. Я был потрясен, узнав, что -Wall сам по себе не будет предупреждать о неинициализированных переменных; вам, как минимум, нужно -O, и даже тогда предупреждение иногда опускается.

Я предполагаю, что, поскольку неограниченные массивы настолько распространены в C, компилятор, вероятно, не имеет способа в своих деревьях выражений представлять массив, размер которого известен во время компиляции. Поэтому, хотя информация присутствует в декларации, я предполагаю, что при ее использовании она уже потеряна.

I вторая рекомендация valgrind. Если вы программируете на C, вы должны запускайте valgrind в каждой программе, все время, пока вы больше не сможете получить удар производительности.

Ответ 4

Это не статический массив.

Undefined или нет, он записывает в адрес 13 целых чисел от начала массива. Какова ваша ответственность. Существует несколько методов С, которые намеренно неправильно распределяют массивы по разумным причинам. И эта ситуация не является чем-то необычным в неполных единицах компиляции.

В зависимости от настроек вашего флага существует ряд функций этой программы, которые будут помечены, например, тот факт, что массив никогда не используется. И компилятор может так же легко оптимизировать его из существования и не сказать вам - дерево, падающее в лес.

Это путь. Это ваш массив, ваша память, делают то, что вам нужно.:)

(Существует множество инструментов, которые помогут вам найти такие вещи, и вы должны использовать их в либеральном режиме. Однако они не все работают через компилятор, хотя компиляция и компоновка часто утомительны, как есть.)

Ответ 5

Причина C не в том, что C не имеет информации. Утверждение типа

int a[10];

выполняет две вещи: выделяет sizeof(int)*10 байты пробела (плюс, возможно, небольшое мертвое пространство для выравнивания), и он помещает запись в таблицу символов, которая читает, концептуально,

a : address of a[0]

или в терминах C

a : &a[0]

и все. Фактически, в C вы можете обменивать *(a+i) на a[i] в (почти *) все случаи без эффекта BY DEFINITION. Таким образом, ваш вопрос эквивалентен запросу "почему я могу добавить любое целое число в это значение (адрес)?"

* Pop quiz: что в этом случае не так?

Ответ 6

Философия C заключается в том, что программист всегда прав. Таким образом, он тихо позволит вам получить доступ к любому адресу памяти, который вы там даете, предполагая, что вы всегда знаете, что делаете, и не будете беспокоить вас предупреждением.

Ответ 7

не должен компилятор выдавать предупреждение, по крайней мере?

Нет; Компиляторы C обычно не выполняют проверки границ массива. Очевидным отрицательным эффектом этого является, как вы говорите, ошибка с поведением undefined, которое может быть очень трудно найти.

Положительная сторона этого - возможное небольшое преимущество производительности в некоторых случаях.

Ответ 8

Я считаю, что некоторые компиляторы делают в некоторых случаях. Например, если моя память служит мне правильно, более новые компиляторы Microsoft имеют опцию "Проверка безопасности буфера", которая обнаруживает тривиальные случаи переполнения буфера.

Почему не все компиляторы делают это? Либо (как уже упоминалось ранее) внутреннее представление, используемое компилятором, не поддается такому типу статического анализа или оно недостаточно велико для списка приоритетов авторов. Честно говоря, это позор в любом случае.

Ответ 9

В gcc есть некоторое расширение для этого (со стороны компилятора) http://www.doc.ic.ac.uk/~awl03/projects/miro/

с другой стороны, шина, крыса и множество других инструментов статического анализа кода имели бы обнаружили, что.

Вы также можете использовать valgrind в своем коде и видеть результат. http://valgrind.org/

другая широко используемая библиотека, похоже, является libefence

Это просто проектное решение. Что теперь ведет к этим вещам.

Отношения Friedrich

Ответ 10

-fbounds-check опция доступна с помощью gcc.

стоит пойти через эту статью http://www.doc.ic.ac.uk/~phjk/BoundsChecking.html

'le dorfier' дал ответ на ваш вопрос, хотя его программа и это способ поведения C.