Начальные значения для статических объектов должны быть константными выражениями или строковыми литералами. (C 2018 6.7.9 3: «Все выражения в инициализаторе для объекта, который имеет статическую или потоковую продолжительность хранения, должны быть константными выражениями или строковыми литералами».)
6.6 7 определяет формы константных выражений для инициализаторов:
Допускается больше широты для константных выражений в инициализаторах. Такое постоянное выражение должно быть или оценивать одно из следующего:
- выражение арифметической константы,
- константа нулевого указателя,
- константа адресаили
- адресная константа для полного типа объекта плюс или минус целочисленное константное выражение.
Рассмотрим uint64_t bar = (uint64_t)foo + 3;
. foo
номинально является статическим массивом, объявленным ранее, который автоматически преобразуется в указатель на его первый элемент. Это квалифицируется как адресная константа (6.6 9: «адресная константа - это ... указатель на lvalue, обозначающий объект статической длительности хранения, ... Однако, он приводится к uint64_t
, который больше не квалифицируетсякак константа адреса, константа адреса плюс или минус выражение константы или константа нулевого указателя.
Является ли это выражение арифметической константой? 6,6 8 исключает ее:
… Castоператоры в выражении арифметической константы должны преобразовывать только арифметические типы в арифметические типы,…
Таким образом, (uint64_t)foo + 3
не квалифицируется как любая форма константного выражения, требуемая стандартом C. Однако 6.6 10 говорит:
Реализация может принимать другие формы константных выражений.
Таким образом, реализация C может принять (uint64_t) foo + 3
или (uint64_t) foo | 3
какконстантное выражение. Тогда возникает вопрос: почему ваша реализация на C принимает первое, а не второе?
Общая особенность линкеров и objФормат модулей ect заключается в том, что объектный модуль может записывать заполнители для определенных выражений, а компоновщики могут оценивать эти выражения и заменять заполнители вычисленными значениями. Основная цель этой функции - позволить коду в программе ссылаться на мест в данных или другом коде, чьи местоположения не полностью известны во время компиляции, но это будет решено (по крайней мере, относительно некоторой базовой ссылкиточка) во время связывания.
Места в данных или коде измеряются относительно символов (имен), определенных в объектных модулях (или относительно начала секций или сегментов). Таким образом, место может быть фактически описано как «34 байта после начала обычной подпрограммы» или «8 байтов после начала объектной базы». Таким образом, объектный модуль поддерживает заполнители, которые состоят из смещения и имени символа. После того, как компоновщик назначает адреса символам, он просматривает каждый заполнитель, добавляет смещение к назначенному адресу и заменяет заполнитель вычисленным результатом.
Похоже, ваш компилятор, несмотря на приведение uint64_t
,способен распознавать, что (uint64_t) foo
по-прежнему является адресом foo
, и, следовательно, (uint64_t) foo + 3
может быть реализовано путем регулярного использования одного из этих заполнителей.
В отличие от этого, побитовый оператор ИЛИ неподдерживается для использования в этих заполнителях, и поэтому компилятор не может реализовать (uint64_t) foo | 3
. Он не может оценить само выражение (поскольку он не знает конечный адрес для foo
), и он не может написать заполнитель для выражения. Поэтому он не принимает это как константное выражение.