Является ли пометка указателя в 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 ответа:
Это само по себе все в порядке, за исключением того, что все это зависит от одного колоссального предположение: выровненный указатель преобразуется в целое число гарантированно иметь нулевые биты в нужных местах.
Можно ли гарантировать это в соответствии с буквой стандартный?
Это возможно для реализации, чтобы гарантировать это. Результат преобразования указателя в целое число определяется реализацией, и реализация может определить его любым способом ему нравится, пока он соответствует требованиям стандарта.
Стандарт абсолютноне гарантирует этого вообще.
Конкретный пример: я работал над системой Cray T90, в которой компилятор C работал под управлением UNIX-подобной операционной системы. В аппаратном обеспечении адрес-это 64-разрядное слово, содержащее адрес 64-разрядного слова; аппаратных байтовых адресов не было. Байтовые указатели (void*
,char*
) были реализованы в программном обеспечении путем хранения 3-битного смещения в в противном случае неиспользуемые 3 бита высокого порядка 64-разрядного указателя слова.Все преобразования "указатель-указатель", "указатель-целое число" и "целое число-указатель" просто копировали представление.
Это означает, что указатель на 8-байтовый выровненный объект при преобразовании в целое число может иметь любой битовый шаблон в младших 3 битах.Ничто в стандарте не запрещает этого.
Суть: схема, подобная описанной вами, которая играет в игры с представлениями указателей, может работать , Если Вы делаете определенные предположения о том, как текущая система представляет указатели-до тех пор, пока эти предположения оказываются справедливыми для текущей системы.
Но никакие такие предположения не могут быть на 100% надежными, потому что стандарт ничего не говорит о том, как представлены указатели (кроме того, что они имеют фиксированный размер для каждого типа указателей, и что представление можно рассматривать как массивunsigned char
).(стандарт даже не гарантирует, что все указатели имеют одинаковый размер.)
Вы правы относительно соответствующих частей стандарта. Для справки:
Целое число может быть преобразовано в любой тип указателя. За исключением ранее указанного, результат определяется реализацией, может быть неправильно выровнен, может не указывать на объект ссылочного типа и может быть представлением ловушки.
Любой тип указателя может быть преобразован в целочисленный тип. За исключением ранее указанного, результат определяется реализацией. Если результат не может быть представленным в целочисленном типе, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа.
Поскольку преобразования определяются реализацией (за исключением случаев, когда целочисленный тип слишком мал, и в этом случае он не определен), стандарт ничего не скажет вам об этом поведении. Если ваша реализация дает гарантии, которые вы хотите, вы настроены. В противном случае-очень плохо.
Я думаю, ответ на ваш явный Вопрос:
Можно ли гарантировать это в соответствии с буквой стандарта?
- это "да", поскольку стандарт указывает на это поведение и говорит, что реализация должна его определить. Возможно, "нет" - это такой же хороший ответ по той же причине.