Является ли указатель тегами в C undefined в соответствии со стандартом?
Некоторые динамически типизированные языки используют указательный тег как быстрый способ определить или сузить тип среды представления представляемого значения. Классический способ сделать это - преобразовать указатели в подходящее целое число и добавить значение тега по наименее значимым битам, которые, как предполагается, равны нулю для выровненных объектов. Когда объект должен быть доступен, биты тега маскируются, целое преобразуется в указатель, а указатель разыменовывается как обычно.
Это само по себе все в порядке, за исключением того, что все зависит от одного колоссального предположения: что выровненный указатель преобразует в целое число, гарантированное наличие нулевых битов в правильных местах.
Можно ли гарантировать это в соответствии с буквой стандарта?
Хотя стандартный раздел 6.3.2.3 (ссылки на проект C11) говорит, что результат преобразования из указателя в целое определяется реализацией, мне интересно, являются ли правила арифметики указателей в 6.5.2.1 и 6.5.6 эффективно сдерживают результат преобразования указателя- > целого, чтобы следовать тем же предсказуемым арифметическим правилам, которые многие программы уже принимают. (6.3.2.3 примечание 67, по-видимому, предполагает, что это и есть намеченный дух стандарта в любом случае, а не то, что это означает много.)
Я специально думаю о случае, когда можно выделить большой массив для работы в качестве кучи для динамического языка, и поэтому указатели, о которых мы говорим, относятся к элементам этого массива. Я предполагаю, что начало самого C-выделенного массива может быть помещено в выровненное положение с помощью некоторых вторичных средств (несмотря ни на что, обсудите это). Скажем, у нас есть массив восьмибайтовых "cons cells"; можем ли мы гарантировать, что указатель на любую данную ячейку преобразует в целое число с наименьшими тремя битами, свободными для тега?
Например:
typedef Cell ...; // such that sizeof(Cell) == 8
Cell heap[1024]; // such that ((uintptr_t)&heap[0]) & 7 == 0
((char *)&heap[11]) - ((char *)&heap[10]); // == 8
(Cell *)(((char *)&heap[10]) + 8); // == &heap[11]
&(&heap[10])[0]; // == &heap[10]
0[heap]; // == heap[0]
// So...
&((char *)0)[(uintptr_t)&heap[10]]; // == &heap[10] ?
&((char *)0)[(uintptr_t)&heap[10] + 8]; // == &heap[11] ?
// ...implies?
(Cell *)((uintptr_t)&heap[10] + 8); // == &heap[11] ?
(Если я правильно понимаю, если реализация обеспечивает uintptr_t
, то поведение undefined, намеченное в пункте 6.3.2.3 в пункте 6, не имеет значения, верно?)
Если все это выполняется, я бы предположил, что это означает, что вы можете полагаться на младшие бит любого преобразованного указателя на элемент выравниваемого массива Cell
, чтобы быть свободным для пометки. Делают ли они && это?
(Насколько мне известно, этот вопрос гипотетический, поскольку нормальное предположение имеет место для обычных платформ в любом случае, и если вы нашли тот, где он этого не сделал, вы, вероятно, не захотели бы посмотреть на стандарт C для руководства а не платформенные документы, но это не так.)
Ответы
Ответ 1
Это само по себе все в порядке, за исключением того, что все зависит от одного колоссального Предположим, что выровненный указатель преобразует в целое число гарантированно иметь нулевые биты в нужных местах.
Можно ли гарантировать это в соответствии с буквой стандарт?
Это возможно для реализации, чтобы гарантировать это. Результат преобразования указателя на целое определяется реализацией, и реализация может определить его любым способом, если он соответствует стандартным требованиям.
Стандарт абсолютно не гарантирует этого вообще.
Конкретный пример: я работал над системой Cray T90, у которой был компилятор C, работающий под UNIX-подобной операционной системой. В аппаратном обеспечении адрес представляет собой 64-битное слово, содержащее адрес 64-битного слова; не было аппаратных байтовых адресов. Байт-указатели (void*
, char*
) были реализованы в программном обеспечении путем сохранения 3-битного смещения в неиспользуемых 3-х разрядах старшего разряда 64-разрядного указателя слов.
Все преобразования указателя на указатель, указатель-на-целое и целые-на-указатели просто копировали представление.
Это означает, что указатель на 8-байтовый выровненный объект, преобразованный в целое число, может иметь любой бит-шаблон в младших 3 битах.
Ничто в стандарте не запрещает это.
Суть: схема, подобная той, которую вы описываете, которая играет в игры с представлениями указателей, может работать, если вы делаете определенные предположения о том, как текущая система представляет указатели, - пока эти предположения будут действительны для текущего система.
Но такие предположения не могут быть на 100% надежными, поскольку стандарт ничего не говорит о представлении указателей (кроме того, что они имеют фиксированный размер для каждого типа указателя и что представление можно рассматривать как массив unsigned char
).
(Стандарт даже не гарантирует, что все указатели имеют одинаковый размер.)
Ответ 2
Вы правы относительно соответствующих частей стандарта. Для справки:
Целое число может быть преобразовано в любой тип указателя. За исключением, как указано ранее, результат определяется реализацией, может быть не правильно выровнен, может не указывать на объект ссылочного типа и может быть ловушечным представлением.
Любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен в целочисленном типе, поведение undefined. Результат не должен находиться в диапазоне значений любого целочисленного типа.
Так как преобразования определены реализацией (за исключением случаев, когда целочисленный тип слишком мал, и в этом случае он undefined), ничего стандартного вам не расскажет об этом поведении. Если ваша реализация делает необходимые гарантии, вы настроены. В противном случае, слишком плохо.
Я думаю, ответ на ваш явный вопрос:
Можно ли гарантировать это в соответствии с буквой стандарта?
Является "да", так как стандартная реакция на это поведение и говорит, что реализация должна определить ее. По-видимому, "нет" - это также хороший ответ по той же причине.