Я мог когда-либо хотеть получить доступ к нулю адреса?

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

Если существует, как это решено с 0 являющийся нулевым указателем и всеми?

В противном случае, что делает это уверенным, что нет такой потребности?

53
задан Community 23 May 2017 в 01:54
поделиться

16 ответов

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

Другими словами, 0 в исходном коде не имеет никакого физического значения. Например, это могло быть 42 или 13 . Т.е. авторы языка, если бы им было так угодно, могли бы сделать так, чтобы вам пришлось сделать p = 42 , чтобы установить указатель p в значение нулевого указателя. Опять же, это не означает, что физический адрес 42 должен быть зарезервирован для нулевых указателей. Компилятору потребуется преобразовать исходный код p = 42 в машинный код, который вставит фактическое физическое значение нулевого указателя ( 0x0000 или 0xBAAD ) в указатель п . Именно так и сейчас с константой 0 .

Также обратите внимание, что ни C, ни C ++ не предоставляют строго определенной функции, которая позволила бы вам назначить конкретный физический адрес указателю. Так что на ваш вопрос о том, «как присвоить указателю адрес 0» формально нет ответа. Вы просто не можете назначить конкретный адрес указателю в C / C ++. Однако в области функций, определяемых реализацией, явное преобразование целого числа в указатель предназначено для достижения этого эффекта.Итак, вы должны сделать это следующим образом

uintptr_t address = 0;
void *p = (void *) address;

Обратите внимание, что это не то же самое, что выполнение

void *p = 0;

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

69
ответ дан 7 November 2019 в 08:25
поделиться

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

Например, несколько лет назад во время работы над встроенной системой (с несколькими доступными инструментами отладки) у нас возникла проблема, которая привела к горячей перезагрузке. Чтобы найти проблему, мы выполняли отладку, используя sprintf (NULL, ...); и последовательный кабель со скоростью 9600 бод. Как я уже сказал - доступно несколько инструментов для отладки. С нашей настройкой мы знали, что горячая перезагрузка не повредит первые 256 байт памяти. Таким образом, после горячей перезагрузки мы могли приостановить загрузчик и выгрузить содержимое памяти, чтобы узнать, что произошло до перезагрузки.

0
ответ дан 7 November 2019 в 08:25
поделиться

Время от времени я использовал загрузки с нулевого адреса (на известной платформе, где это гарантированно будет segfault), чтобы намеренно вылетать из-за информативно названного символа в библиотеке код, если пользователь нарушает какое-то необходимое условие, и мне не доступен хороший способ сгенерировать исключение. « Segfault at someFunction $ xWasnt16ByteAligned » - довольно эффективное сообщение об ошибке, предупреждающее кого-либо о том, что они сделали неправильно и как это исправить. Тем не менее, я бы не рекомендовал приобретать привычку к подобным вещам.

0
ответ дан 7 November 2019 в 08:25
поделиться

C / C ++ не позволяет писать по любому адресу. Это ОС, которая может подавать сигнал, когда пользователь обращается к запрещенному адресу. C и C ++ гарантируют, что любая память, полученная из кучи, будет отличаться от 0.

0
ответ дан 7 November 2019 в 08:25
поделиться

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

1
ответ дан 7 November 2019 в 08:25
поделиться

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

У вас даже нет ОС в терминах настольного / серверного ПК, и у вас нет виртуальной памяти и тому подобного. Так что это нормально и даже необходимо для доступа к памяти по определенному адресу. На современном настольном / серверном ПК это бесполезно и даже опасно.

5
ответ дан 7 November 2019 в 08:25
поделиться

На x86 адрес 0 (точнее, 0000:0000) и его окрестности в реальном режиме являются местом расположения вектора прерываний. В старые добрые времена вы обычно записывали значения в вектор прерываний, чтобы установить обработчики прерываний (или, если вы были более дисциплинированы, использовали службу MS-DOS 0x25). Компиляторы языка Си для MS-DOS определили тип дальнего указателя, который при присвоении NULL или 0 получал битовый шаблон 0000 в своей сегментной части и 0000 в своей части смещения.

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

6
ответ дан 7 November 2019 в 08:25
поделиться

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

6
ответ дан 7 November 2019 в 08:25
поделиться

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

Адрес 0 может быть полезен во встроенных системах (общий термин для ЦП, которого нет в компьютере; они запускают все, от вашей стереосистемы до вашей цифровой камеры). Обычно системы устроены так, что вам не нужно писать по адресу 0. В каждом случае, о котором я знаю, это какой-то особый адрес. Даже если программисту нужно писать в него (например, чтобы настроить таблицу прерываний), ему нужно будет записать в нее только во время начальной последовательности загрузки (обычно небольшой кусок языка ассемблера для настройки среды для C).

7
ответ дан 7 November 2019 в 08:25
поделиться

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

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

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

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

12
ответ дан 7 November 2019 в 08:25
поделиться

Кстати, вам может быть интересно узнать, что с компилятором Microsoft C ++ нулевой указатель на член будет представлен как битовый шаблон 0xFFFFFFFF на 32-битной машине. То есть:

struct foo
{
      int field;
};

int foo::*pmember = 0;     // 'null' member pointer

pmember будет иметь битовый шаблон «все единицы». Это потому, что вам нужно это значение, чтобы отличать его от

int foo::*pmember = &foo::field;

, где битовый шаблон действительно будет «всеми нулями» - поскольку мы хотим смещение 0 в структуре foo.

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

18
ответ дан 7 November 2019 в 08:25
поделиться

Компилятор позаботится об этом за вас (comp.lang.c FAQ):

Если машина использует ненулевой битовый шаблон для нулевых указателей, компилятор несет ответственность за его генерацию, когда программист запрашивает, записывая "0" или "NULL", нулевой указатель. Поэтому, #определение NULL как 0 на машине, для которой внутренние нулевые указатели ненулевые, так же правомерно, как и на любой другой, потому что компилятор должен (и может) все равно генерировать правильные нулевые указатели машины в ответ на необращенные 0, встречающиеся в контекстах указателей.

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

9
ответ дан 7 November 2019 в 08:25
поделиться

Я скомпилировал код с помощью gcc для Motorola HC11, у которой нет MMU, а 0 - вполне подходящий адрес, и был разочарован, обнаружив, что для записи по адресу 0, вы просто пишете в него. Нет никакой разницы между NULL и адресом 0.

И я понимаю, почему. Я имею в виду, что невозможно определить уникальный NULL на архитектуре, где каждая ячейка памяти потенциально допустима, поэтому, я полагаю, авторы gcc просто решили, что 0 достаточно хорошо подходит для NULL, независимо от того, допустимый это адрес или нет.

      char *null = 0;
; Clears 8-bit AR and BR and stores it as a 16-bit pointer on the stack.
; The stack pointer, ironically, is stored at address 0.
1b:   4f              clra
1c:   5f              clrb
1d:   de 00           ldx     *0 <main>
1f:   ed 05           std     5,x

Когда я сравниваю его с другим указателем, компилятор генерирует обычное сравнение. Это означает, что он никоим образом не считает char *null = 0 специальным указателем NULL, и на самом деле указатель на адрес 0 и указатель "NULL" будут равны.

; addr is a pointer stored at 7,x (offset of 7 from the address in XR) and 
; the "NULL" pointer is at 5,y (offset of 5 from the address in YR).  It doesn't
; treat the so-called NULL pointer as a special pointer, which is not standards
; compliant as far as I know.
37:   de 00           ldx     *0 <main>
39:   ec 07           ldd     7,x
3b:   18 de 00        ldy     *0 <main>
3e:   cd a3 05        cpd     5,y
41:   26 10           bne     53 <.LM7>

Итак, отвечая на первоначальный вопрос, я думаю, что мой ответ - проверить реализацию вашего компилятора и выяснить, потрудились ли они вообще реализовать уникальное значение NULL. Если нет, то вам не стоит беспокоиться об этом. ;)

(Конечно, этот ответ не соответствует стандарту.)

.
3
ответ дан 7 November 2019 в 08:25
поделиться

Помните, что во всех обычных случаях вы фактически не видите конкретных адресов. Когда вы выделяете память, ОС предоставляет вам адрес этого фрагмента памяти.

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

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

int* i = new int(); // suppose this returns a pointer to address zero
*i = 42; // now we're accessing address zero, writing the value 42 to it

Итак, если вам нужно для доступа к нулевому адресу он обычно работает нормально.

Значение 0 == null действительно становится проблемой только в том случае, если по какой-то причине вы напрямую обращаетесь к физической памяти. Возможно, вы сами пишете ядро ​​ОС или что-то в этом роде. В этом случае вы собираетесь писать в определенные адреса памяти (особенно те, которые сопоставлены с аппаратными регистрами), и поэтому вам, вероятно, может потребоваться запись по адресу ноль. Но тогда вы действительно обходите C ++ и полагаетесь на специфику вашего компилятора и аппаратной платформы.

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

Таким образом, вы можете просто сделать что-то вроде этого:

int i = 0;
int* zeroaddr = (int*)i;

теперь zeroaddr будет указывать на адрес ноль (*), но, строго говоря, он не будет нулевым указателем, потому что нулевое значение не было постоянным.

(*): это не полностью правда.Стандарт C ++ гарантирует только «отображение, определяемое реализацией» между целыми числами и адресами. Он может преобразовать 0 в адрес 0x1633de20` или любой другой адрес, который ему нравится. Но отображение обычно интуитивно понятное и очевидное, где целое число 0 сопоставляется с нулевым адресом)

0
ответ дан 7 November 2019 в 08:25
поделиться

Да, вы можете захотеть получить доступ к адресу памяти 0x0h. Почему вам это нужно, зависит от платформы. Процессор может использовать это для вектора сброса, так что запись в него приводит к сбросу ЦП. Его также можно использовать для вектора прерывания, как интерфейс с отображением памяти для некоторого аппаратного ресурса (счетчик программ, системные часы и т. Д.), Или он может даже быть действительным как простой старый адрес памяти. Нет ничего волшебного в нулевом адресе памяти, это просто тот, который исторически использовался для специальных целей (векторы сброса и тому подобное).C-подобные языки следуют этой традиции, используя ноль в качестве адреса для указателя NULL, но в действительности базовое оборудование может или не может рассматривать нулевой адрес как особый.

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

1
ответ дан 7 November 2019 в 08:25
поделиться

Если я правильно помню, в микроконтроллере AVR файл регистров отображен в адресное пространство RAM и регистр R0 находится по адресу 0x00. Это явно сделано специально, и, видимо, Atmel считает, что бывают ситуации, когда удобно обращаться к адресу 0x00 вместо явной записи R0.

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

0
ответ дан 7 November 2019 в 08:25
поделиться