Какая часть разыменования указателей NULL вызывает нежелательное поведение?

Мне интересно, какая часть разыменования NULL ptr вызывает нежелательное поведение. Пример:

//  #1
someObj * a;
a = NULL;
(*a).somefunc();   // crash, dereferenced a null ptr and called one of its function
                   // same as a->somefunc();

//  #2
someObj * b;
anotherObj * c;
b = NULL;
c->anotherfunc(*b);   // dereferenced the ptr, but didn't call one of it's functions

Здесь в # 2 мы видим, что я на самом деле не пытался получить доступ к данным или функции из b, поэтому будет ли это вызывать нежелательное поведение, если * b просто преобразуется в NULL и мы передаем NULL в anotherfunc ()?

 Steve S10 июл. 2009 г., 18:59
@Tom: Если оставить неинициализированным, c, вероятно, будет гораздо менее удобным, чем NULL.
 Tom10 июл. 2009 г., 17:48
Не является ли второй пример плохим, потому что не был инициализирован otherObj * c? c вполне может иметь значение NULL, поэтому c-> другой параметр может разыменовать NULL. Правильно?

Ответы на вопрос(15)

потому что вы по-прежнему указываете компилятору попытаться получить доступ к памяти в местоположении 0 (что запрещено). В зависимости от подписиanotherfunc, вы можете передавать ссылку (которую запрещено инициализировать нулевым объектом), илиcopy из*b.

но это не обязательно нежелательное поведение. Часть полезностиNULL в том, что на большинстве платформ он указывает на память, которая явно недоступна для вашего приложения, и вызывает ошибку сегментации (или нарушение доступа) в тот самый момент, когда вы пытаетесь разыменовать ее.

Его цель - явно пометить содержимое указателей как недействительные.

пределенным поведением, современные процессоры и операционные системы генерируют ошибку сегментации или подобную ошибку.

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

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

(Это не гарантируется стандартом, хотя он работает таким образом на всех компиляторах, с которыми я когда-либо сталкивался)

чтобы сказать, что произойдет, когда вы передадите его null. это может быть хорошо, это может произойти сбой, зависит от кода.

 10 июл. 2009 г., 17:19
Он фактически не передает NULL, он передает объект, на который указывает NULL.
 10 июл. 2009 г., 17:28
Прочитайте пост снова. b инициализируется в третьей строке второго фрагмента. * b не указатель, поэтому не имеет смысла говорить, что он также равен NULL.
 10 июл. 2009 г., 17:24
Объект, на который указывает ноль? Нуль ни на что не указывает. Это специальное значение указателя, которое означает «указывает на ничто».
 10 июл. 2009 г., 17:25
он пропускает ноль. b объявлен, но не инициализирован, поэтому * b равно нулю.

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

//  #2
someObj * b;
anotherObj * c = new anotherObj();  ,      //initialize c
b = NULL;
c->anotherfunc(*b);   // *b is in question not the c dereference

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

Имя значения нулевого указателя0или любое другое постоянное интегральное выражение в контексте указателя (например,3 - 3, например). Также естьNULL макрос, который должен быть равен 0 в C ++, но может быть(void *)0 в C (C ++ настаивает на большей безопасности указателей). В C ++ 0x будет явное значение, называемоеnullptrнаконец, давая нулевому указателю явное имя.

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

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

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

 10 июл. 2009 г., 17:41
+1, лучшее объяснение того, что происходит, когда вы передаете "* b" в качестве аргумента, когда b NULL.
 13 июл. 2009 г., 16:30
За исключением того, что попытка разыменования нулевого указателя является неопределенным поведением, и поэтому реализация может делать все, что угодно. Направленного объекта нет. Не могли бы вы указать, где в стандарте вы ссылаетесь?
 11 июл. 2009 г., 00:33
The * * b '; действительно решает что-то, но это не отменяет ссылку 'b'. В соответствии со стандартом «унарный оператор *»; при применении к указателю возвращает lvalue, ссылающийся на указанный объект (см. ниже).

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

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

 10 июл. 2009 г., 19:19
@jalf: да, это будет правильное поведение от POV стандарта C ++. Я не имел в виду незаконный = & gt; недействительным. Однако, рассматривая вероятность того, что компилятор C ++ выполнит X (как сказал Брайан «для всех, кого вы знаете»), мы учитываем все правила, которым авторы компиляторов пытаются следовать. Насколько я знаю, компилятор нарушает закон, но опять же, насколько мне известно, компилятор нарушает стандарт. Тогда это может на самом деле не быть реализацией C ++, но я тоже этого не знаю ...
 10 июл. 2009 г., 18:32
нет, это не так. Это все еще будет действительная реализация C ++, даже если она нарушит закон. ;)
 10 июл. 2009 г., 17:45
"Это допустимое поведение, поскольку оно не определено". Хотя обратите внимание, что авторы компиляторов обязаны соблюдать применимые законы в своей юрисдикции, в дополнение к стандарту C ++ ;-)
 10 июл. 2009 г., 19:01
Я просто рад, что все те разы, когда я разыменовывал NULL-указатель, он не решил отформатировать мой компьютер.
 10 июл. 2009 г., 19:18
@ Брайан: лично я предпочитаю "загореться" как каноническое неопределенное поведение. Не знаю почему, это просто привлекательно. Или программное обеспечение Dilbert QuikProtect - «если у вас есть звуковая карта, она ругается на вас».

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

Причина, почему этоappears работать, это то, что функции-члены обычно реализуются примерно так:

void anotherfunc(anotherObj* this, someObj& arg);

То есть «это» указатель в основном передается функции как отдельный аргумент. Таким образом, при вызове функции компилятор не проверяет, чтоthis Указатель действителен, он просто передает его в функцию.

Это все еще неопределенное поведение, хотя. Компилятор не гарантирует, что это сработает.

 10 июл. 2009 г., 17:30
Правда до тех пор, пока другая функция не является виртуальной. Если это так, компилятору требуется «this» указатель на vtable, чтобы посмотреть, какой переопределить вызов.
 10 июл. 2009 г., 17:31
правда. Хорошая точка зрения
 26 окт. 2009 г., 15:08
Ну, это не определено, они неhave упомянуть об этом явно. Это то, что "неопределенный означает" ;) Если стандарт делаетnot скажем "вам разрешено вызывать функции-члены по нулевому указателю", тогда он не определен.
 26 окт. 2009 г., 14:43
5.2.5 / 3 говоритpointer->func(); эквивалентно(*pointer).func(); в случае, если указатель действительно указатель на тип класса. Но я на самом деле нашел только дваcomments о разыменовании нулевых указателей: сначала в разделе 1.9example неопределенного поведения и, во-вторых, в разделе 8.3.2 в качестве примечания, объясняющего, почему нулевые ссылки не могут существовать. Я ожидал, что это будет где-то в разделе 5.2 [expr.post] и / или 5.3 [expr.ref]. Есть идеи?

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

Так что простой вызов функции-члена для указателя объекта NULL не обязательно вызывает сбой (если только это не виртуальная функция). Вы получаете неверный доступ к памяти при сбое, только когда пытаетесь получить доступ к переменным-членам объекта или vtable.

В случае № 2 вы можете или не можете получить немедленный сбой, в зависимости от того, какanotherfunc объявлен Если это займетsomeObj по значению вы косвенно обращаетесь к NULL в самом вызове функции, что приводит к сбою. Если это займетsomeObj по ссылке, обычно ничего не происходит, так как ссылки реализуются с помощью указателей под капотом, а фактическое косвенное обращение откладывается до тех пор, пока вы не попытаетесь получить доступ к данным члена.

someObj * b;
anotherObj * c;
b = NULL;
c->anotherfunc(*b); 

Если anotherfunc () принимает ссылку на b, то вы не отменили ссылку на b, вы просто преобразовали ее в ссылку. Если, с другой стороны, это параметр значения, то будет вызван конструктор копирования, а затем вы отменили ссылку на него.

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

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

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

5.3.1

The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points.

Таким образом, «унарный * оператор» возвращает ссылку на объект, на который указывал указатель, к которому он был применен. (На данный момент не было разыменования).

NULL значение. Это означает, что вы можете вызывать не виртуальные функции, потому что они связаны во время компиляции. Он вызывает функцию просто отлично и проходит вNULL this указатель. Теперь, если вы попытаетесь использовать какие-либо переменные-члены, произойдет сбой, потому что он попытается найти их на основеthis указатель передан внутрь. Вы также можете вызывать другие не виртуальные функции по тому же аргументу. Теперь, если вы попытаетесь использовать виртуальную функцию, она сразу же потерпит крах, поскольку попытается найтиvtable отNULL указатель.

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

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

Например, одной из проблем в C ++ является то, что не все возвращаемые типы могут бытьnull во всяком случае, такие какint, Так что призыв кa->>length() было бы сложно узнать, что вернуть, когдаa сам былnull.

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

Наконец, Бак, все остальные говорят о том, как обстоят дела, особенно для языка C ++: разыменование - это механическая операция в большинстве языков: она должна возвращать что-то того же типа иnull обычно хранится как ноль. Старые системы просто зависали, когда вы пытались разрешить ноль, более новые системы распознавали бы особый характер значения при возникновении ошибки.

Кроме того, эти языки более низкого уровня не могут представлятьnull как целое число (или другие основные типы данных), так что вы не могли бы вообще почтитьnull какnull во всех случаях.

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

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

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

Этоis неопределенное поведение указано в различных примечаниях по всему стандарту. Но примечания не являются нормативными: они могут сказать что угодно, но никогда не смогут сформулировать какие-либо правила. Их цель полностью информативна.

То, что вызов функции-члена по нулевому указателю формально, также является неопределенным поведением.

Формальная проблема с простой разыменовкой нулевого указателя состоит в том, что определение идентичности результирующего выражения lvalue невозможно: каждое такое выражение, которое возникает в результате разыменования указателя, должно однозначно ссылаться на объект или функцию, когда это выражение вычисляется. Если вы разыменуете нулевой указатель, у вас нет объекта или функции, которые идентифицирует это lvalue. Это аргумент, используемый стандартом для запрета нулевых ссылок.

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

Комитет обсудил, чтобы решить эту проблему глобально, определив вид lvalue, у которого нет идентификатора объекта или функции: так называемыйempty lvalue, Однако у этой концепции все еще были проблемы, и они решилине принять его.

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

int a = *(int*)0;

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

int *pa = *(int(*)[1])0;

Поскольку чтение из массива просто возвращает его адрес с использованием типа указателя элемента, это, скорее всего, просто создаст нулевой указатель (но поскольку вы разыменовываете нулевой указатель ранее, это все еще формально неопределенное поведение). Другой случай - разыменование нулевых указателей на функции. Здесь также чтение функции lvalue просто даст вам ее адрес, но с использованием типа указателя на функцию:

void(*pf)() = *(void(*)())0;

Как и в других случаях, это тоже неопределенное поведение, но, вероятно,not привести к аварии.

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

assert(this != NULL);

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

Ваш ответ на вопрос