Почему ошибка сегментации не возникает?

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

int main(){
    float *arr;
    cout << arr[0] << "\n" --> This prints out a ZERO. I am expecting a seg-fault.
        
    cout << arr[1000] << "\n" --> This gives me a seg-fault
        
    return 0;
}

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

Неопределенное поведение (что и происходит, когда вы разыменовываете указатель) не приводит автоматически к сбоям. С таким же успехом это может сработать, или поджечь ваш компьютер, или вызвать носовые демоны.
Some programmer dude 06.04.2022 13:38

Операционная система компьютера, вероятно, выделяет память для программы в виде страниц (обычно 4 КБ). Вы получаете ошибки сегмента только при доступе к странице (адресу), на которую у вас тоже нет прав. Поэтому, если arr[0] упадет с памятью, принадлежащей вашей программе (случайно), вы не получите ошибку сегмента.

Richard Critten 06.04.2022 13:43

Итак, нет четкой границы, когда он выдает seg-fault, вы имеете в виду? Как и выше, с arr[0] все в порядке, а с arr[1000] — нет. @Someprogrammerdude

San Kim 06.04.2022 13:44

А, это имеет смысл. @RichardCritten Итак, есть некоторая «дополнение», прежде чем я столкнусь с seg-fautl.

San Kim 06.04.2022 13:45
float* arr = nullptr; в некоторых системах при доступе возникнет segfault (технически это неопределенное поведение, поэтому поддерживает эти системы). Без установки arr и последующего доступа к нему программа имеет неопределенное поведение, и в зависимости от вашей платформы arr будет иметь либо некоторое значение произвольный (часто называемое значение мусора), либо на некоторых платформах может быть помечена как неинициализированная, и чтение перед инициализацией вызовет ошибку отклоняться (что-то вроде segfault, но другое).
Eljay 06.04.2022 14:44

Недопустимые разыменования указателя не являются обязательный, чтобы вызвать нарушение сегментации. Поведение формально неопределенный по стандарту C+, что означает, что стандарт не накладывает никаких требований или ограничений на то, что происходит. Произошло нарушение сегментации если ваша (unix) операционная система обнаруживает ваша программа/процесс обращается к памяти, к которой ей не разрешен доступ, и она отправляет сигнал SIGKILL вашему процессу. ОС не будет обнаруживать или сигнализировать что-либо, если происходит недопустимое разыменование указателя для доступа к некоторой памяти, к которой разрешен доступ программе (например, перебор другой переменной).

Peter 06.04.2022 15:27

@SanKim В C++ ошибочно предполагать, что все ошибки приводят к очевидным диагностируемым ошибкам. Большую часть времени может показаться, что он работает или аварийно завершает работу, или работает до определенного момента (который может измениться в любое время без видимой причины), или работает в вашей системе, но ломается в другой. Если у вас не произойдет сбой сразу, это всего лишь возможный результат, на который нельзя полагаться. Завтра может сразу рухнуть. В моей системе это может напечатать 42.

François Andrieux 06.04.2022 15:27

@FrançoisAndrieux Одной из проблемных форм неопределенного поведения является та, которая никогда не вызывает наблюдаемых симптомов на машине разработки, но надежно дает сбой на машине платного клиента - обычно таким образом, чтобы максимизировать неудобства для клиента. Мерфи был оптимистом!

Peter 06.04.2022 15:32
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
117
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

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

Выражение arr[1000] попытается разыменовать адрес, который находится на расстоянии 4000 байт от неинициализированного значения arr (при условии sizeof(float)==4). Типичный размер страницы памяти составляет 4096 байт. Следовательно, если предположить, что неинициализированное значение arr является адресом памяти, который указывает на начало страницы памяти размером 4096 байт, то добавление 4000 к этому адресу не изменит адрес памяти в достаточной степени, чтобы адрес указывал на другую страницу памяти. Однако, если неинициализированное значение arr представляет собой адрес памяти, указывающий где-то в середине страницы памяти, то добавление 4000 к этому адресу заставит его указывать на другую страницу памяти (при условии, что размер страницы памяти составляет 4096 байт). Это, вероятно, объясняет, почему ваша операционная система обрабатывает оба адреса по-разному, так что один доступ к памяти вызывает ошибку сегментации, а другой доступ к памяти не завершается ошибкой.

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


I am wondering if this due to the "smart" design of the compiler that alleviates the crash.

«Разумнее» в таком случае было бы сообщить о какой-то ошибке (т. е. о сбое), а не пытаться облегчить сбой. Это потому, что сбой облегчает поиск ошибки.

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

Если вы хотите, чтобы такие ошибки обнаруживались более надежно, вы можете рассмотреть возможность использования функции, предлагаемой некоторыми компиляторами, которая пытается обнаруживать такие ошибки. Например, и gcc, и clang поддерживают АдресДезинфицирующее средство. В этих двух компиляторах все, что вам нужно сделать, это скомпилировать с параметром командной строки -fsanitize=address. Однако это заставит компилятор добавить дополнительные проверки, что значительно снизит производительность (примерно в два раза) и увеличит использование памяти. Поэтому это следует делать только в целях отладки.

Спасибо за этот подробный ответ. Очень помогает.

San Kim 06.04.2022 15:42

I am expecting a seg-fault to occurr...

Ваша программа имеет неопределенное поведение, поскольку указатель arr не инициализирован, и вы неявно разыменовываете его.

Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.

Таким образом, результат, который вы видите (возможно, видите), является результатом неопределенного поведения. И, как я уже сказал, не полагайтесь на вывод программы с UB. Программа может просто вылететь.

Например, здесь программа не вылетает, а здесь вылетает.

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


1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.

Другие вопросы по теме