Почему это добавление молча игнорируется?
У меня есть следующий код C:
#include <stdint.h>
#include <stdio.h>
int i;
uint64_t a[] = { (uint64_t)&i, (uint64_t)&i + 0x8000000000000000 };
int main() {
printf("%p %llx %llx\n", &i, a[0], a[1]);
}
Если я скомпилирую это (как C или как С++) с Microsoft Visual Studio Community 2015, а затем запустил его, результат будет похож на следующий:
013E9154 13e9154 13e9154
Кажется, что код + 0x8000000000000000
, который, как я ожидал, установил высокий бит a[1]
, был молча игнорирован.
Однако, если я перемещаю инициализацию a
внутри main
, вывод будет тем, что я ожидал бы:
00179154 179154 8000000000179154
С a
глобальным, почему добавление молча игнорируется? Если попытка добавления действительно устанавливает высокий бит a[1]
или он должен вызвать ошибку компилятора?
Интересно, что если + 0x8000000000000000
в приведенном выше коде заменяется на | 0x8000000000000000
, я получаю "ошибка C2099: инициализатор не является константой".
Изменить: Аналогичная проблема может возникнуть даже при отсутствии бросков. Скомпилированный для x64, следующий код печатает одно и то же значение (например, 000000013FB8D180
) три раза:
#include <stdio.h>
int i;
int * a[] = { &i, &i + 0x100000000 };
int main() {
printf("%p %p %p\n", &i, a[0], a[1]);
}
Ответы
Ответ 1
Инициализатор
(uint64_t)&i + 0x8000000000000000
не является допустимым константным выражением в C. Это не является арифметическим константным выражением, которое допускает только целочисленные константы, плавающие константы, константы перечисления, символьные константы и выражения sizeof в качестве операндов; ни константа адреса, которая не позволяет методам использовать целые типы.
Тем не менее, я ожидаю, что Visual Studio создаст "ошибку C2099: инициализатор не является константой", как это происходит с | 0x8000000000000000
.
Я не уверен в С++.
Ответ 2
Ни один из инициализаторов, используемых в
uint64_t a[] = { (uint64_t)&i, (uint64_t)&i + 0x8000000000000000 };
являются подходящими постоянными выражениями. Педантичное определение константного выражения в C не позволяет вводить значения указателя целым типам, даже если значения указателя удовлетворяют требованиям для постоянной адреса. Это означает, что формально (uint64_t)&i
в этом контексте уже является незаконным.
Однако этот компилятор, по-видимому, принимает (uint64_t)&i
в этом контексте как расширение.
После этого тот факт, что он жалуется, когда +
заменен на |
, оператор, вероятно, внедрен непосредственно в спецификацию языка
6.6 Константные выражения
7 Для постоянных выражений в инициализаторах допускается больше широты. Такое постоянное выражение должно быть или оцениваться одним из следующее:
- арифметическое константное выражение,
- константа нулевого указателя,
- постоянная адреса, или
- константа адреса для типа объекта плюс или минус целочисленное константное выражение.
Опять же, это не точное совпадение, поскольку приведенная выше формулировка позволяет добавлять фиксированное смещение только к константам адреса, но для компилятора, который принимает (uint64_t)&i
в качестве постоянного выражения в этом контексте, было бы необычно продолжать примените ограничение "плюс или минус". Возможность добавлять что-либо к (или вычитать что-то из) константы адреса в C определяется возможностями загрузчиков, которые выполняют перемещение адреса во время загрузки. Погрузчики могут добавлять или вычитать, но они не могут выполнять побитовые операции по адресам.
И, наконец, тот факт, что он не влияет на время выполнения, по-видимому, вызван ограничениями загрузчика, который отвечает за реализацию инициализации статической статистики в стиле C во время запуска.