Некоторые динамически типизированные языки используют указательный тег как быстрый способ определить или сузить тип среды представления представляемого значения. Классический способ сделать это - преобразовать указатели в подходящее целое число и добавить значение тега по наименее значимым битам, которые, как предполагается, равны нулю для выровненных объектов. Когда объект должен быть доступен, биты тега маскируются, целое преобразуется в указатель, а указатель разыменовывается как обычно.
Это само по себе все в порядке, за исключением того, что все зависит от одного колоссального предположения: что выровненный указатель преобразует в целое число, гарантированное наличие нулевых битов в правильных местах.
Можно ли гарантировать это в соответствии с буквой стандарта?
Хотя стандартный раздел 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 для руководства а не платформенные документы, но это не так.)