Могу ли я когда-нибудь получить доступ к нулевому адресу? - PullRequest
52 голосов
/ 04 мая 2010

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

Если есть, то как это решить, если 0 - нулевой указатель и все?

Если нет, то почему он так не нужен?

Ответы [ 17 ]

67 голосов
/ 04 мая 2010

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

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

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

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

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

void *p = 0;

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

18 голосов
/ 04 мая 2010

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

struct foo
{
      int field;
};

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

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

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

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

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

12 голосов
/ 04 мая 2010

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

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

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

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

9 голосов
/ 04 мая 2010

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

Если машина использует ненулевой битовый шаблон для нулевых указателей, компилятор обязан сгенерировать его, когда программист запросит, записав «0» или «NULL», нулевой указатель. Следовательно, #defining NULL как 0 на машине, для которой внутренние нулевые указатели отличны от нуля, является таким же допустимым, как и на любом другом, потому что компилятор должен (и может) генерировать правильные нулевые указатели машины в ответ на неукрашенные 0, наблюдаемые в контекстах указателей.

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

7 голосов
/ 04 мая 2010

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

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

6 голосов
/ 04 мая 2010

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

6 голосов
/ 04 мая 2010

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

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

5 голосов
/ 04 мая 2010

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

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

3 голосов
/ 04 мая 2010

Я скомпилировал некоторый код, используя 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 с уникальным значением. Если нет, вам не нужно беспокоиться об этом. ;)

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

1 голос
/ 04 мая 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...