Что такое нулевой указатель

Обнаружение в коде дефекта «разыменование нулевого указателя»

Этой статьей мы открываем серию публикаций, посвященных обнаружению ошибок и уязвимостей в open-source проектах с помощью статического анализатора кода AppChecker. В рамках этой серии будут рассмотрены наиболее часто встречающиеся дефекты в программном коде, которые могут привести к серьезным уязвимостям. Сегодня мы остановимся на дефекте типа «разыменование нулевого указателя».

Что такое нулевой указатель. Смотреть фото Что такое нулевой указатель. Смотреть картинку Что такое нулевой указатель. Картинка про Что такое нулевой указатель. Фото Что такое нулевой указатель

Разыменование нулевого указателя (CWE-476) представляет собой дефект, когда программа обращается по некорректному указателю к какому-то участку памяти. Такое обращение ведет к неопределенному поведению программы, что приводит в большинстве случаев к аварийному завершению программы.

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

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

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

Рассмотрим следующий фрагмент кода на C++:

Нетрудно заметить, что если pColl == NULL, выполнится тело этого условного оператора. Однако в теле оператора происходит разыменование указателя pColl, что вероятно приведет к краху программы.

Обычно такие дефекты возникают из-за невнимательности разработчика. Чаще всего блоки такого типа применяются в коде для обработки ошибок. Для выявления таких дефектов можно применить различные методы статического анализа, например, сигнатурный анализа или symbolic execution. В первом случае пишется сигнатура, которая ищет в абстрактном синтаксическом дереве (AST) узел типа «условный оператор», в условии которого есть выражение вида! а, a==0 и пр., а в теле оператора есть обращение к этому объекту или разыменование этого указателя. После этого необходимо отфильтровать ложные срабатывания, например, перед разыменованием этой переменной может присвоиться значение:

Выражение в условии может быть нетривиальным.

Во втором случае во время работы анализатор «следит», какие значения могут иметь переменные. После обработки условия if (!a) анализатор понимает, что в теле условного оператора переменная a равна нулю. Соответственно, ее разыменование можно считать ошибкой.

Приведенный фрагмент кода взят из популярного свободного пакета офисных приложений Apache OpenOffice версии 4.1.2. Дефект в коде был обнаружен при помощи статического анализатора программного кода AppChecker. Разработчики были уведомлены об этом дефекте, и выпустили патч, в котором этот дефект был исправлен ).

Рассмотрим аналогичный дефект, обнаруженный в Oracle MySQL Server 5.7.10:

В этом примере если ident равен 0, то условие будет истинным и выполнится строка:

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

Нетрудно догадаться, что разыменование нулевого указателя – это дефект, не зависящий от языка программирования. Предыдущие два примера демонстрировали код на языке C++, однако с помощью статического анализатора AppChecker можно находить подобные проблемы в проектах на языках Java и PHP. Приведем соответствующие примеры.

Рассмотрим фрагмент кода системы управления и централизации информации о строительстве BIM Server версии bimserver 1.4.0-FINAL-2015-11-04, написанной на языке Java:

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

Теперь рассмотрим фрагмент кода популярной коллекции веб-приложений phabricator, написанной на php:

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

Источник

Как можно и как нельзя использовать нулевой указатель в С++

Что такое нулевой указатель. Смотреть фото Что такое нулевой указатель. Смотреть картинку Что такое нулевой указатель. Картинка про Что такое нулевой указатель. Фото Что такое нулевой указатель

Некоторым этот банальный вопрос уже набил оскомину, но мы взяли 7 примеров и попытались объяснить их поведение при помощи стандарта:

Очевидная, но важная деталь: p, инициализированный нулевым указателем, не может указывать на объект типа А, потому что его значение отлично от значения любого указателя на объект типа А conv.ptr#1.

Disclaimer: статья содержит вольный перевод терминов и выдержек из стандарта на русский язык. Мы рекомендуем английскую версию статьи на dev.to, лишенную неточностей перевода.

Пример 1

Синтаксически это оператор выражения (expression statement, stmt.expr#1), в котором *p является выражением с отброшенным результатом, который, тем не менее, нужно вычислить. Определение унарного оператора * expr.unary.op#1 гласит, что этот оператор осуществляет косвенное обращение (indirection), и результатом является l-значение, которое обозначает объект или функцию, на которую указывает выражение. Его семантика понятна, чего не скажешь о том, должен ли объект существовать. Нулевой указатель в определении не упоминается ни разу.

Можно попробовать зацепиться за косвенное обращение, потому что есть basic.stc#4, в котором четко написано, что поведение при косвенном обращении через недопустимое значение указателя (indirection through an invalid pointer value) не определено. Но там же дается описание недопустимого значения указателя, под которое нулевой не подходит, и дается ссылка на basic.compound#3.4, где видно, что нулевой указатель и недопустимый — это различные значения указателя.

Еще есть примечание в dcl.ref#5, которое гласит, что «the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior», т.е. единственный способ создать такую ссылку — привязать ее к «объекту», полученному за счет косвенного обращения через нулевой указатель, что приводит к неопределенному поведению. Но придаточное в конце может относиться не только к косвенному обращению, но и к «привязать» (to bind), и в этом случае неопределенное поведение вызвано тем, что нулевой указатель не указывает на объект, о чем и говорится в основном тексте пункта dcl.ref#5.

Раз стандарт вместо однозначных формулировок оставляет пространство для интерпретаций в разрезе нашего вопроса, можно обратиться к списку дефектов языковой части стандарта, где Core Working Group среди прочего поясняет текст стандарта. Наш вопрос выделен в отдельный дефект, где CWG довольно давно пришла к неформальному консенсусу (так определен статус drafting), что неопределенное поведение влечет не разыменование само по себе, а конвертация результата разыменования из l-значения в r-значение. Если «неформальный консенсус CWG» звучит недостаточно весомо, то есть другой дефект, в котором рассматривается пример, аналогичный нашему примеру 7. Такой код назван корректным по этой же причине в официальной аргументации CWG.

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

Пример 2

Чтобы вызвать foo, требуется проинициализировать его параметр, для чего нужно вычислить результат оператора «запятая». Его операнды вычисляются слева направо, причем все, кроме последнего, являются выражениями с отброшенным значением так же, как и в примере 1 (expr.comma#1). Следовательно, этот пример также корректен.

Пример 3

Для инициализации a будет выбран неявный конструктор копирования, и для того, чтобы его вызвать, нужно проинициализировать параметр const A& допустимым объектом, в противном случае поведение не определено (dcl.ref#5). В нашем случае допустимого объекта нет.

Пример 4

Выражение этого оператора выражения при вычислении будет раскрыто в (*(p)).data_mem согласно expr.ref#2, которое обозначает (designate) соответствующий подобъект объекта, на который указывает выражение до точки (expr.ref#6.2). Параллели с примером 1 становятся особенно явными, если открыть, скажем, basic.lookup.qual#1, и увидеть, как to refer и to designate взаимозаменяемо используются в том же смысле, что и в expr.ref. Из чего мы делаем вывод, что это корректный код, однако некоторые компиляторы не согласны (см. про проверку константными выражениями в конце статьи).

Пример 5

В продолжение предыдущего примера не будем отбрасывать результат, а проинициализируем им int. В этом случае результат нужно конвертировать в pr-значение, потому что выражения именно этой категории инициализируют объекты (basic.lval#1.2). Так как речь идет об int, будет осуществлен доступ к объекту результата (conv.lval#3.4), что в нашем случае ведет к неопределенному поведению, потому что ни одно из условий basic.lval#11 не соблюдается.

Пример 6

class.mfct.non-static#1 гласит, что функции-члены разрешено вызывать для объекта типа, к которому они принадлежат (или унаследованного от него), или напрямую из определений функций-членов класса. Именно «разрешено» — такой смысл вкладывается в глагол «may be» в директивах ИСО/МЭК, которым следуют все стандарты ИСО. Раз объекта нет, то и поведение при таком вызове не определено.

Пример 7

Проверка с помощью constexpr

Раз константные выражения не могут полагаться на неопределенное поведение (expr.const#5), то можно узнать мнение компиляторов о наших примерах. Пусть они и несовершенны, но как минимум нередко правы. Мы взяли три популярных компилятора, подправили пример под constexpr и для наглядности закомментировали те примеры, которые не компилируются, потому что сообщения об ошибках что у GCC, что у MSVC оставляют желать лучшего на данных примерах: godbolt.

Что получилось в итоге:

#КодПредположениеGCC 10.1Clang 10MSVC 19.24
1*p;++++
2foo((*p, 5));++++
3A a<*p>;
4p->data_mem;++
5int bdata_mem>;
6p->non_static_mem_fn();++
7p->static_mem_fn();++++

Результаты заставляют несколько усомниться в выводе из примера 6 и в большей степени из примера 4. Но также интересно, что мы все сходимся во мнении о ключевом примере 1.

Спасибо, что остались с нами до конца, чтобы проследить за приключениями нулевого указателя в С++! 🙂 Обычно мы делимся на Хабре кусками кода из реальных проектов по разработке встроенного ПО для электроники, но этот раз нас заинтересовали чисто «философские» вопросы, поэтому примеры синтетические.

Если вы разделяете нашу любовь к противоречиям в С++, делитесь «наболевшим» в комментариях.

Источник

10.9 – Нулевые указатели

Нулевые значения и нулевые указатели

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

Помимо адресов памяти, есть еще одно дополнительное значение, которое может содержать указатель: нулевое значение. Нулевое значение – это специальное значение, которое означает, что указатель ни на что не указывает. Указатель, содержащий нулевое значение, называется нулевым указателем.

В C++ мы можем присвоить указателю нулевое значение, инициализировав или присвоив ему литерал 0:

Лучшая практика

Если при создании вы не присваиваете указателям какое-либо значение, инициализируйте их нулевым значением.

Косвенное обращение через нулевые указатели

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

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

Макрос NULL

В C++ есть специальный макрос препроцессора под названием NULL (определен в заголовке ). Этот макрос был унаследован от C, где он обычно используется для обозначения нулевого указателя.

Значение NULL определяется реализацией, но обычно определяется как целочисленная константа 0. Примечание. Начиная с C++11, NULL можно определить как nullptr (что мы обсудим позже).

Лучшая практика

Опасности использования 0 (или NULL ) для нулевых указателей

nullptr в C++11

Начиная с C++11, когда нам нужен нулевой указатель, следует отдавать предпочтение ему, а не нулю:

Для продвинутых читателей

Функция со списком других параметров является новой функцией, даже если функция с таким же именем существует. Мы рассмотрели это в уроке «8.9 – Перегрузка функций».

Лучшая практика

Используйте nullptr для инициализации указателей нулевым значением.

std::nullptr_t

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

Источник

Урок №81. Нулевые указатели

Обновл. 13 Сен 2021 |

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

Нулевое значение и нулевые указатели

Помимо адресов памяти, есть еще одно значение, которое указатель может хранить: значение null. Нулевое значение (или «значение null») — это специальное значение, которое означает, что указатель ни на что не указывает. Указатель, содержащий значение null, называется нулевым указателем.

В языке C++ мы можем присвоить указателю нулевое значение, инициализируя его/присваивая ему литерал 0 :

Поскольку значением нулевого указателя является нуль, то это можно использовать внутри условного ветвления для проверки того, является ли указатель нулевым или нет:

Совет: Инициализируйте указатели нулевым значением, если не собираетесь присваивать им другие значения.

Разыменование нулевых указателей

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

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

Макрос NULL

Однако, поскольку NULL является макросом препроцессора и, технически, не является частью C++, то его не рекомендуется использовать.

Ключевое слово nullptr в C++11

Обратите внимание, значение 0 не является типом указателя, и присваивание указателю значения 0 для обозначения того, что он является нулевым — немного противоречиво, вам не кажется? В редких случаях, использование 0 в качестве аргумента-литерала может привести к проблемам, так как компилятор не сможет определить, используется ли нулевой указатель или целое число 0 :

Для решения этой проблемы в C++11 ввели новое ключевое слово nullptr, которое также является константой r-value.

Начиная с C++11, при работе с нулевыми указателями, использование nullptr является более предпочтительным вариантом, нежели использование 0 :

nullptr также может использоваться для вызова функции (в качестве аргумента-литерала):

Совет: В C++11 используйте nullptr для инициализации нулевых указателей.

Тип данных std::nullptr_t в C++11

Вам, вероятно, никогда это не придется использовать, но знать об этом стоит (на всякий пожарный).

Поделиться в социальных сетях:

Комментариев: 9

с этими указателями….какие-то скороговорки для мозга..

Так и не понял, для чего нужен std::nullptr_t. Зачем его ввели.

Судя по тому, что я узнал за 81 урок и какое мнение сложилось о С++, ответ на Ваш вопрос: «просто так, на всякий пожарный»))))))

ps. 3 допустимых вида инициализации переменных, значит, вас не смущают?)))))

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

Каждая переменная или константа должна быть определенного типа.
Следовательно, у nullptr (как константы, а не ключевого слова) тоже должен быть какой-то тип.
В MSDN есть пример, показывающий зачем ввели дополнительный тип:

Зачем еще оно нужно:
Приходите на собеседование, и дают вам такой код 😉

Источник

Нулевой указатель

Нулевой указатель
Всем привет. Возникли вот такие вопросы: Допустим, есть код: char *a = new char(5); В каких.

Что такое нулевой указатель. Смотреть фото Что такое нулевой указатель. Смотреть картинку Что такое нулевой указатель. Картинка про Что такое нулевой указатель. Фото Что такое нулевой указательНулевой указатель на объект
Здравствуйте. #include using std::cout; using std::endl; class A < public.

Нулевой указатель на функцию
Всем привет. Ещё раз подыму предыдущую тему. Я делаю упражнение на явное связывание dll. Проблема.

Почему возвращается нулевой указатель?
Всем привет. Не могу понять из за чего внутри метода указатель корректно присваивается, видно что.

Не, ну комп взорваться не должен. Что такое нулевой указатель. Смотреть фото Что такое нулевой указатель. Смотреть картинку Что такое нулевой указатель. Картинка про Что такое нулевой указатель. Фото Что такое нулевой указатель

Эээ это у него указатель на 0 равен нулю?

В стандарте этот момент не оговорен. Это Undefined behavior. Разыменование нулевых указателей, которые равны NULL, может дать 0, или любое произвольное значение, или остановку программы, или сигнал какого-либо вида, или исключение. Или все вместе. Или что-то еще.

Добавлено через 50 минут

В dos’е используется real-mode, в котором нету современных настроек страничной памяти. Т.е. вся память в пределах 1 мегабайта является валидной.

Добавлено через 58 секунд
Не в dos’е, а в dos’овских компиляторах. По умолчанию генерируется код под real-mode.

Да, спасибо. Это я уже примерно понял.

Только врубиться не могу, как такое в DOSBox’е получилось?

Что такое нулевой указатель. Смотреть фото Что такое нулевой указатель. Смотреть картинку Что такое нулевой указатель. Картинка про Что такое нулевой указатель. Фото Что такое нулевой указательКак работает нулевой указатель?
Как загнать нулевой указатель на Пробовал вот так, например: <. char *REZ=STR(str2, str1);.

Как проверить нулевой ли указатель?
Как проверить нулевой ли указатель? Почему-то всегда думал что так: if (pointer) Но не работает.

Как работает нулевой указатель null
int x = NULL; указывает на то, что память на переменную x выделилась, но она ни на что не.

Что такое нулевой указатель. Смотреть фото Что такое нулевой указатель. Смотреть картинку Что такое нулевой указатель. Картинка про Что такое нулевой указатель. Фото Что такое нулевой указательЗаставить указатель указывать на нулевой элемент массива
Вроде бы и простой вопрос для начинающих, но почему-то не могу найти на него ответ. Есть функция.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *