Индекс массива вне границ в C - PullRequest
41 голосов
/ 23 марта 2009

Почему C различается в случае индекса массива вне границы

#include <stdio.h>
int main()
{
    int a[10];
    a[3]=4;
    a[11]=3;//does not give segmentation fault
    a[25]=4;//does not give segmentation fault
    a[20000]=3; //gives segmentation fault
    return 0;
}

Я понимаю, что пытается получить доступ к памяти, выделенной процессу или потоку в случае a[11] или a[25], и выходит за пределы стека в случае a[20000].

Почему компилятор или компоновщик не выдает ошибку, они не знают о размере массива? Если нет, то как sizeof(a) работает правильно?

Ответы [ 9 ]

66 голосов
/ 23 марта 2009

Проблема в том, что C / C ++ фактически не выполняет никакой проверки границ в отношении массивов. От операционной системы зависит доступ к действительной памяти.

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

В последней строке происходит то, что вы получили доступ к той части памяти, которая выделена для стека. В результате вы индексируете часть памяти, которая не выделена вашему процессу или выделена только для чтения. ОС видит это и отправляет ошибку сегмента процессу.

Это одна из причин того, что C / C ++ настолько опасен, когда дело доходит до проверки границ.

21 голосов
/ 23 марта 2009

Segfault не является предполагаемым действием вашей программы на C, которое сообщит вам, что индекс находится за пределами допустимого. Скорее, это непреднамеренное следствие неопределенного поведения.

В C и C ++, если вы объявляете массив как

type name[size];

Вам разрешен доступ только к элементам с индексами от 0 до size-1. Все, что находится за пределами этого диапазона, вызывает неопределенное поведение. Если индекс был близок к диапазону, скорее всего, вы читаете память своей собственной программы. Если индекс был в значительной степени вне диапазона, скорее всего, ваша программа будет убита операционной системой. Но ты не можешь знать, все может случиться.

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

Почему это не предупреждает об этом? Ну, вы можете поставить высокий уровень предупреждения и надеяться на милость компилятора. Это называется качество реализации (QoI). Если какой-то компилятор использует открытое поведение (например, неопределенное поведение) для выполнения чего-то хорошего, он имеет хорошее качество реализации в этом отношении.

[js@HOST2 cpp]$ gcc -Wall -O2 main.c
main.c: In function 'main':
main.c:3: warning: array subscript is above array bounds
[js@HOST2 cpp]$

Если бы вместо этого он отформатировал ваш жесткий диск, увидев доступ к массиву вне границ - что было бы допустимо для него - качество реализации было бы довольно плохим. Мне нравилось читать об этом в документе Обоснование ANSI C .

6 голосов
/ 23 марта 2009

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

То, что вы видите в случае a[11] (и, кстати, a[10]), - это память, которой ваш процесс владеет , но не принадлежит массиву a[]. a[25000] так далеко от a[], что, вероятно, совсем не в вашей памяти.

Изменение a[11] гораздо более коварно, поскольку оно незаметно влияет на другую переменную (или кадр стека, который может вызвать другую ошибку сегментации, когда ваша функция вернется).

3 голосов
/ 23 марта 2009

C не делает этого. Подсистема виртуальной памяти ОС имеет вид.

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

В некоторых системах также существует принудительная концепция «записываемой» памяти в ОС, и вы, возможно, пытаетесь записать память, которая принадлежит вам, но помечена как неписываемая.

2 голосов
/ 23 марта 2009

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

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

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

Но вы здесь не совсем в дураках. Если ваш компилятор не предупреждает вас, Lint может. Кроме того, существует ряд инструментов для обнаружения таких проблем (во время выполнения) для массивов в куче, одним из наиболее известных из которых является Electric Fence (или DUMA ). Но даже Electric Fence не гарантирует, что перехватит все ошибки переполнения.

2 голосов
/ 23 марта 2009

Как уже упоминалось, некоторые компиляторы могут обнаруживать некоторые обращения к массиву за пределами границ во время компиляции. Но проверка границ во время компиляции не поймает все:

int a[10];
int i = some_complicated_function();
printf("%d\n", a[i]);

Чтобы обнаружить это, необходимо использовать проверки во время выполнения, и их избегают в C из-за их влияния на производительность. Даже со знанием размера массива a во время компиляции, то есть sizeof (a), он не может защитить от этого без вставки проверки во время выполнения.

2 голосов
/ 23 марта 2009

Просто добавьте, что говорят другие люди, вы не можете полагаться на то, что в этих случаях программа просто рухнет, нет гарантии того, что произойдет, если вы попытаетесь получить доступ к области памяти за пределами «границ массива». Это так же, как если бы вы сделали что-то вроде:

int *p;
p = 135;

*p = 14;

Это просто случайно; это может сработать. Возможно, нет. Не делай этого. Код для предотвращения подобных проблем.

1 голос
/ 16 июля 2016

С философией всегда доверяй программисту. А также отсутствие проверки границ позволяет программе на C работать быстрее.

1 голос
/ 23 марта 2009

Это не проблема C, это проблема операционной системы. Вашей программе предоставлено определенное пространство памяти, и все, что вы делаете внутри, это нормально. Ошибка сегментации возникает только тогда, когда вы обращаетесь к памяти за пределами вашего пространства процесса.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...