![]() |
Модераторы: bsa |
![]() ![]() ![]() |
|
GQU |
|
|||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
Почему NULL сделали как ((void *)0), а не просто 0 ??
|
|||
|
||||
feodorv |
|
||||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
Кстати, это довольно тонкий вопрос. NULL вместо 0 нужен лишь в одном единственном случае - при передаче нулевого указателя в функцию с переменным числом аргументов. Во всех остальных случаях NULL, определенный как ((void *)0), не нужен, его вполне может заменить простой 0. Так в чём же проблема с передачей нулевого указателя в функцию с переменным числом аргументов? Допустим, мы имеем функцию с переменным числом аргументов:
Чем же нехорош вызов этой функции в виде
В первом случае 0 трактуется как "целое число", во втором - как "указатель". Если размеры (sizeof) целого числа и указателя различаются (а в эпоху 16-битных процессоров это было повальной практикой), то в стек вызова func поступает разное количество байт: в случае "0" - 2 нулевых байта, в случае "(void *) 0" - честных 4 нулевых байта для 16-битной системы. И получается, что в первом случае в стек не докладывается 2 нужных нулевых байта, которые func всё равно изымает из стека, но которые будут мусорными и содержать с огромной вероятностью отнюдь не нули, что приведет к появлению в процессе выполнения func случайного ненулевого указателя, что в свою очередь приведет, скорее всего, к краху программы (это не говоря даже о неправильном размере сдвига стекового указателя на 2 байта в первом случае в противовес 4-м байтам во втором). Для 32-битных систем, для которых sizeof(int) равно sizeof(void *) (не знаю ни одной 32-х битной ОС, для которой это не так, но допускаю, что такие могут существовать), такой проблемы не существует. Однако, казалось бы, проблема возрождается вновь с появлением 64-битных систем, для которых sizeof(int) есть 4, а sizeof(void *) есть 8. Но принимая во внимание заодно задачу корректного выравнивания данных в 64-битных системах в стек вызова подпрограммы помещается 8 байт для любого аргумента (если его sizeof, конечно, меньше или равен 8 байтам), поэтому для func 0 и (void *) 0 будут выглядеть одинаково. Впрочем, хотя NULL, определенный как "(void *) 0", выглядит наследием древнего 16-битного прошлого, о его предназначении знать надо ![]() -------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
||||||
|
|||||||
GQU |
|
|||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
что то я вас не пойму
так на 16ти битных машинах указатель, как и int занимает 16 бит, а не 32, хотя нет, там как то по другому все.. Это сообщение отредактировал(а) GQU - 24.8.2014, 16:15 |
|||
|
||||
feodorv |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
Ничего подобного. На 16-битных машинах указатель представлял собой два 16-битных значения. При этом на 16-битных ОС первое представляло собой базовый адрес, второе - так называемый индекс. Итоговый адрес в адресном пространстве процесса получался путем сдвига базового значения на 4 позиции влево и приплюсовывания индекса, что-то вроде такого:
Таким образом можно было адресовать до 1 мегабайта памяти (что и было в DOS, например). Иными словами, то, что на 16-битных системах sizeof(int) был 2, совсем не означает, что и sizeof(void *) обязан быть 2. Реальный режим ![]() Это сообщение отредактировал(а) feodorv - 24.8.2014, 21:55 -------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
||||
|
|||||
GQU |
|
||||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
допустим поместили в стек 4 нулевых байта для INT на 64 битной системе, а ОС помещает 8 байт, так не факт что остальные нулевые, хотя наверно они нулями как раз и заполняются или единицами для отрицательных чисел Раз начали говорить про указатели, спрошу, чтобы не создавать еще одну тему NULL это реальный нулевой адрес или нет??
мне кажется эти 2 байта и не нужны, потому что когда функция вызывается, компилятор знает типы параметров, он знает, что 0 - это INT и не пойдет искать еще 2 байта,хотя нет, в прототипе же будет написано, что это указатель ![]() Это сообщение отредактировал(а) GQU - 24.8.2014, 17:54 |
||||||
|
|||||||
feodorv |
|
||||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
Именно. В 64-х битных системах можно (на ассемблере) поместить в стек 4-х байтовую величину со сдвигом указателя на 8 байт, но смысла с в этом мало. Компиляторы все равно генерят код, в котором 4-х байтовое значение расширяется до 8-ми байтовой величины. Для знакового int оставшиеся 32 бита заполняются знаком, для беззнакового int - просто нулями. Компилятор - да, знает. Но об этом не знает функция func. В момент выполнения она надеется увидеть в стеке 4 полноценных байта адреса, а не два обрезанных. А что имеется в виду под реальным? Значение 0 в C/C++ принято присваивать тем указателям, которые (как бы) никуда не указывают. Потом, сравнивая значение указателя с 0, можно посмотреть, рабочий это указатель или нет. Например:
Вместо удобного 0 (а команды процессора, заносящие 0 в свои регистры, очень короткие и быстрые), можно было бы договориться о другом значении, например, ((void *) -1), но вот было принято вполне понятное решение о нуле. 0-ой байт вполне себе обычный байт в пространстве памяти процесса, и ему вполне можно было бы присваивать какое-то значение, если бы современные ОС специально не были бы так устроены, чтобы попытка чтения/записи по этому адресу не приводила бы к нарушению доступа (вылета программы). Более того, даже несколько первых (и последних) страниц процесса намеренно делают недоступными для чтения/записи, чтобы сказывались ошибки программирования. Вот представьте себе такой код:
В случае нехватки памяти (или некорректного обращения с кучей процесса) ptr будет указывать на первый байт процесса. Если бы не упомянутая защита, баг мог бы привести к непредсказуемым последствиям. Была у меня ситуация с VDD (то есть Virtual DOS Device под виндами, это вызов 32-х битной среды из 16-битной), когда запись по нулевому адресу прошла (в 32-х битном окружении). Но там, видимо, такой защиты не было предусмотрено, а жаль ![]() Так что ответ на Ваш вопрос будет таким: адрес реальный (при условии переданной процессу первой страницы памяти), но не предназначенный для использования (то есть для чтения/записи). -------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
||||||
|
|||||||
GQU |
|
||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
т.е для каждой программы свой процесс, для каждого процесса свой нулевой адрес?
так не факт, что если мы выйдем за пределы кучи, то попадем на эти страницы недоступные для записи/чтения Это сообщение отредактировал(а) GQU - 24.8.2014, 19:35 |
||||
|
|||||
feodorv |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
Да, если не предприняты специальные меры.
Гм, что подразумевается под "выходом за пределы кучи"? Не факт, но если где-то возник NULL и некорректное с ним обращение, то с очень высокой вероятностью. Отлавливание багов - дело весьма занимательное, и я пока не знаю ни одного способа избежать их со 100% вероятностью. -------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
||||
|
|||||
GQU |
|
||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
закрываются адреса, которые уходят в минус? ![]() Как такое может быть?
тут я не могу понять Это сообщение отредактировал(а) GQU - 24.8.2014, 21:35 |
||||
|
|||||
feodorv |
|
||||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
Никогда не встречали такого кода:
А сколько будет (NULL - 1)? То есть что напечатает следующий код:
-------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
||||
|
|||||
GQU |
|
|||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
FFFFFFFF тогда получается нулевой байт не первый в выделенном процессе Это сообщение отредактировал(а) GQU - 24.8.2014, 21:52 |
|||
|
||||
feodorv |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
В смысле? В смысле зацикленности адресации памяти в силу ограниченности числа бит, выделенных на указатель? Ну так и unsigned int обладает тем же свойством))) Но поскольку отсчет идет все-таки от нулевого байта, то он-таки первый. Но спереди и сзади него тоже есть байты, этого не отнять. Только сзади находится как раз FFFFFFFF-ый ![]() -------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
|||
|
||||
GQU |
|
||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
так это адрес нашего процесса или другова? или к другим процессам мы вообще доступа не имеем? PS: не увидел сообщения выше, не обновил страничку
ну тут таже фишка, как и в unsigned int, а то я понять не мог Это сообщение отредактировал(а) GQU - 24.8.2014, 22:21 |
||||
|
|||||
feodorv |
|
|||
![]() Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Комодератор Сообщений: 2214 Регистрация: 30.7.2011 Репутация: 12 Всего: 45 |
Ну, пока мы ведем речь лишь о нашем процессе. Но и в любом другом будет такая же ситуация. Адрес - это всего лишь число, номер байта в адресном пространстве процесса (нашего ли или не нашего). В современных ОС процессы отделены друг от друга, но существуют развитые способы межпроцессного взаимодействия. И, действительно, иногда приходится иметь дело с адресами другого процесса (что не означает, что в нашем процессе нет этого же адреса, но уже нашего). Все пользовательские процессы похожи друг на друга, всем выделяется адресное пространство с номерами байт 0-FFFFFFFF (для 32-х битных систем), но только это адресное пространство разное для разных процессов. Плоская модель памяти ![]() -------------------- Напильник, велосипед, грабли и костыли - основные инструменты программиста... |
|||
|
||||
GQU |
|
||||||
Опытный ![]() ![]() Профиль Группа: Участник Сообщений: 272 Регистрация: 13.2.2011 Репутация: нет Всего: нет |
что значит некорректное обращение с кучей можете это на примере полной программы, я что то не могу врубиться если бы не упомянутая защита, врятли бы указатель указывал на первый байт процесса, в случае некорректного обращения с кучей ![]()
А что, процессу могут и не передать первую страницу памяти?
Это же оно и есть? Неиспользуемая зона, для перехвата обращений по нулевым указателям Это сообщение отредактировал(а) GQU - 29.8.2014, 23:53 |
||||||
|
|||||||
![]() ![]() ![]() |
Правила форума "C/C++: Для новичков" | |
|
Запрещается! 1. Публиковать ссылки на вскрытые компоненты 2. Обсуждать взлом компонентов и делиться вскрытыми компонентами
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, bsa. |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Для новичков | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |