Является ли пометка указателя в C неопределенной в соответствии со стандартом?


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

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

Хотя в стандартном разделе 6.3.2.3 (ссылки на проект C11) говорится, что результат преобразования указателя в целое определяется реализацией, мне интересно, что эффективно ли арифметические правила указателя в 6.5.2.1 и 6.5.6 ограничивают результат преобразования указателя - > целого числа, чтобы следовать тем же предсказуемым арифметическим правилам, которые уже предполагают многие программы. (6.3.2.3 Примечание 67, по-видимому, предполагает, что это является предполагаемым духом стандарта в любом случае, не то чтобы это много значит.)

Я специально думаю о случае, когда можно выделить большой массив, чтобы он действовал как куча для динамического языка, и, следовательно, указатели мы говорим об элементах этого массива. Я предполагаю, что начало самого c-распределенного массива может быть помещено в выровненное положение с помощью некоторых вторичных средств (хотя, конечно, обсудите это тоже). Допустим, у нас есть массив восьмибайтовых "минусов ячеек"; можем ли мы гарантировать, что указатель на любую данную ячейку преобразуется в целое число с наименьшими тремя битами, свободными для тега?

Например:

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, то неопределенное поведение, о котором говорится в пункте 6.3.2.3, не имеет значения, верно?)

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

(Насколько я знаю, этот вопрос гипотетичен, поскольку нормальное предположение справедливо для общих платформ в любом случае, и если вы нашли одну, где ее не было, вы, вероятно, не захотите смотреть на Стандарт C для руководства, а не документы платформы; но это к делу не относится.)

2 11

2 ответа:

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

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

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

Стандарт абсолютноне гарантирует этого вообще.

Конкретный пример: я работал над системой Cray T90, в которой компилятор C работал под управлением UNIX-подобной операционной системы. В аппаратном обеспечении адрес-это 64-разрядное слово, содержащее адрес 64-разрядного слова; аппаратных байтовых адресов не было. Байтовые указатели (void*, char*) были реализованы в программном обеспечении путем хранения 3-битного смещения в в противном случае неиспользуемые 3 бита высокого порядка 64-разрядного указателя слова.

Все преобразования "указатель-указатель", "указатель-целое число" и "целое число-указатель" просто копировали представление.

Это означает, что указатель на 8-байтовый выровненный объект при преобразовании в целое число может иметь любой битовый шаблон в младших 3 битах.

Ничто в стандарте не запрещает этого.

Суть: схема, подобная описанной вами, которая играет в игры с представлениями указателей, может работать , Если Вы делаете определенные предположения о том, как текущая система представляет указатели-до тех пор, пока эти предположения оказываются справедливыми для текущей системы.

Но никакие такие предположения не могут быть на 100% надежными, потому что стандарт ничего не говорит о том, как представлены указатели (кроме того, что они имеют фиксированный размер для каждого типа указателей, и что представление можно рассматривать как массив unsigned char).

(стандарт даже не гарантирует, что все указатели имеют одинаковый размер.)

Вы правы относительно соответствующих частей стандарта. Для справки:

Целое число может быть преобразовано в любой тип указателя. За исключением ранее указанного, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением ловушки.

Любой тип указателя может быть преобразован в целочисленный тип. За исключением ранее указанного, результат определяется реализацией. Если результат не может быть представленным в целочисленном типе, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа.

Поскольку преобразования определяются реализацией (за исключением случаев, когда целочисленный тип слишком мал, и в этом случае он не определен), стандарт ничего не скажет вам об этом поведении. Если ваша реализация дает гарантии, которые вы хотите, вы настроены. В противном случае-очень плохо.

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

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

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