зарезервированное значение указателя, отличное от NULL

Как я могу создать зарезервированное значение указателя?

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

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

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

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


person Demi    schedule 21.12.2013    source источник
comment
Почему бы вам вместо этого не использовать помеченный союз? struct { int type; union { char *string_value; double float_value; } }   -  person    schedule 21.12.2013
comment
Тогда длина строки (поскольку строка может содержать '\0') потребует еще одного целочисленного поля.   -  person nullptr    schedule 21.12.2013
comment
Вы хотите, чтобы приложение вылетало при доступе к зарезервированному узлу? Принятый ответ, кажется, подразумевает это. Вместо использования заведомо недействительного адреса вы можете использовать заведомо действующий адрес. Просто создайте глобальную переменную static const string invalid_string;. Вы можете сравнить адрес любой строки с адресом этого объекта и разыменовать его без сбоев. Обратите внимание, что сбой иногда является хорошим и желательным поведением, но это зависит от того, чего вы хотите. Если вы считаете доступ к зарезервированному узлу программной ошибкой, она действительно должна рухнуть.   -  person Damon    schedule 21.12.2013
comment
Да, было бы логической ошибкой обращаться к указателю, если бы он имел магическое значение, поскольку это означает, что структура хранит число.   -  person Demi    schedule 21.12.2013


Ответы (3)


В любой современной системе для таких целей можно просто использовать значения указателя 1, 2, ... 4095. Другим частым выбором является (uintptr_t)-1, который технически хуже, но, тем не менее, используется чаще, чем 1.

Почему эти значения "безопасны"?
Современные системы защищают от обращения к указателю NULL, делая невозможным сопоставление чего-либо с нулевым виртуальным адресом. Почти любое разыменование указателя NULL приведет к попаданию в эту несуществующую область, и аппаратное обеспечение сообщит системе ОС, что произошло что-то плохое, что приведет к тому, что ОС выполнит сегментацию процесса.
Поскольку страницы виртуальной памяти выровнены по страницам (не менее 4 КБ текущее оборудование), и ничего не отображается на нулевой адрес, ничто не может быть отображено на весь диапазон 0, ..., 4095, защищая все эти адреса одинаково, и вы можете использовать их как значения специального назначения.

Сколько места виртуальной памяти зарезервировано для этой цели, является системным параметром, в linux он управляется /proc/sys/vm/mmap_min_addr, и пользователь root может изменить его на ноль, что отключит эту защиту (что было бы не очень умной идеей). По умолчанию в Ubuntu — 64 КБ (т. е. 16 страниц).

По этой же причине (uintptr_1)-1 менее безопасен, чем 1; хотя любая загрузка более чем одного байта приведет к нулевой странице, сам адрес (uintptr_1)-1 не обязательно защищен таким образом. Следовательно, выполнение строковых операций над (char*)-1 не обязательно приводит к ошибке сегментации.

Редактировать:
Мое первоначальное объяснение со специальным сопоставлением кажется немного устаревшим, возможно, так дела обрабатывались на старой платформе Mac/PPC. Несмотря на то, что эффект почти такой же, я изменил детали ответа, чтобы отразить современный Linux. В любом случае, важным моментом является не как защита от нулевых страниц, а то, что любая нормальная современная система будет иметь некоторую защиту от нулевых страниц, которая охватывает по крайней мере указанный диапазон адресов. Более подробную информацию можно найти в этом ответе SO: https://stackoverflow.com/a/12645890/2445184

person cmaster - reinstate monica    schedule 21.12.2013
comment
Можете ли вы привести какой-либо источник для вашего ответа, я хотел узнать/прочитать об этом более подробно. - person Don't You Worry Child; 21.12.2013
comment
Большинство современных аппаратных средств не могут адресовать более 48 бит. На любом текущем процессоре Intel при доступе к -1 всегда будет segfault. - person SoapBox; 21.12.2013
comment
@ 0xF1 Это была информация из моей головы, которая, признаюсь, немного устарела, но я обновил свой ответ более новой информацией. Извините, если я запутал вас. - person cmaster - reinstate monica; 21.12.2013
comment
@SoapBox Нет, то, что аппаратное обеспечение не может адресовать более 48 бит, не означает, что (uint64_t)-1 находится за пределами адресуемого диапазона. На самом деле архитектура x86-64 указывает, что первые 16 бит должны быть знаковым расширением оставшихся 48 бит, как в случае с (uint64_t)-1. Недопустимым адресом будет 0x0000ffffffffffffull. - person cmaster - reinstate monica; 21.12.2013

В стандартном C (и стандартном C++) подход, который работает на 100%, прост: объявить переменную, использовать ее адрес как магическое значение.

char *ptr;
char magic;
if (ptr == &magic) { ... }

Это гарантирует, что magic никогда не будет пересекаться с другим объектом.

Значения магического указателя, такие как (char *) 1, тоже имеют свои преимущества, но их так легко ошибиться (даже если вы игнорируете теоретические реализации, где (char *) 1 может быть допустимым объектом, если вы используете (int *) 1 в качестве значения магического указателя, а оптимизатор предполагает int * значения выровнены соответствующим образом, он может удалить проверки, которые не являются операциями, только в 100% действительном коде, а не в вашем коде), что я бы рекомендовал стандартный подход и, возможно, временно переключиться на значения магического указателя, только если вы обнаружите, что они вам помогают отлаживать.

person Community    schedule 21.12.2013
comment
Единственная проблема заключается в том, что попытка доступа к переменной не приводит к возникновению исключения или сигнала, что усложняет отладку. - person Demi; 21.12.2013
comment
@Demetri Да, именно поэтому я включил последнее предложение. :) Но в большом количестве случаев такие инструменты, как valgrind, уже обнаружат недопустимые обращения (не обязательно обращение к magic напрямую, для этого потребуются специальные аннотации, но прочитайте один байт за ним, и вы получите полезное сообщение ). - person ; 21.12.2013

mmapзапись адреса может завершиться ошибкой, если адрес уже назначен. Вероятно, было бы лучше использовать адрес какой-либо статической переменной или функции. Или получить уникальный адрес через malloc(1).

person nullptr    schedule 21.12.2013
comment
+1, мой ответ, по сути, то, что вы предлагаете, но я подумал, что было бы полезно расширить. - person ; 21.12.2013
comment
Я мог бы получить адрес от mmap() - person Demi; 23.12.2013