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

Я думаю, ответ на ваш явный вопрос:

Можно ли гарантировать это в соответствии с буквой стандарта?

Является "да", так как стандартная реакция на это поведение и говорит, что реализация должна определить ее. По-видимому, "нет" - это также хороший ответ по той же причине.