Индекс массива не связан с C

Почему 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) работает правильно?

Ответы

Ответ 1

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

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

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

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

Ответ 2

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

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

type name[size];

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

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

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

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

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

Ответ 3

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

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

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

Ответ 4

C не делает этого. Подсистема виртуального memeory OS.

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

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

Ответ 5

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

int *p;
p = 135;

*p = 14;

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

Как сказал JaredPar, C/C++ не всегда выполняет проверку диапазона. Если ваша программа обращается к области памяти вне выделенного массива, ваша программа может аварийно завершить работу, а может и нет, потому что она обращается к какой-то другой переменной в стеке.

Чтобы ответить на ваш вопрос об операторе sizeof в C: Вы можете надежно использовать sizeof (array)/size (array [0]) для определения размера массива, но его использование не означает, что компилятор выполнит какую-либо проверку диапазона.

Мои исследования показали, что разработчики C/C++ считают, что вы не должны платить за то, что вы не используете, и доверяют программистам, чтобы они знали, что они делают. (см. принятый ответ на этот вопрос: Доступ к массиву вне границ не дает ошибок, почему?)

Если вы можете использовать C++ вместо C, может быть, использовать вектор? Вы можете использовать vector [], когда вам нужна производительность (но без проверки диапазона) или, что более предпочтительно, использовать vector.at() (которая имеет проверку диапазона за счет производительности). Обратите внимание, что vector не увеличивает автоматически емкость, если она заполнена: для безопасности используйте push_back(), которая автоматически увеличивает емкость при необходимости.

Больше информации о векторе: http://www.cplusplus.com/reference/vector/vector/