cl x64: беззнаковое длинное внешнее / внутреннее объединение: ошибка C2099: инициализатор не является константой / нет ошибки

Дело 1. Файл: test1.c:

unsigned long val = (unsigned long)&"test";

int main() 
{
    return 0;
}

Вызов компилятора: cl test1.c /c

Полученные результаты:

Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test1.c
test1.c(1): warning C4311: 'type cast': pointer truncation from 'char (*)[5]' to 'unsigned long'
test1.c(1): error C2099: initializer is not a constant

Дело 2. Файл: test2.c:

union { unsigned long val; } val =  { (unsigned long)&"test" };

int main() 
{
    return 0;
}

Вызов компилятора: cl test2.c /c

Полученные результаты:

Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

test2.c
test2.c(1): warning C4311: 'type cast': pointer truncation from 'char (*)[5]' to 'unsigned long'

Вопрос: почему после установки unsigned long в union (случай 2, файл test2.c) error C2099: initializer is not a constant ушел?

Заметки:

  • для обеих версий кода cl x86 (та же версия, что и для x64) НИКАКИХ ошибок, НИКАКИХ предупреждений.

  • для обеих версий кода gcc x86/x64 (версия 9.3.0) НИКАКИХ ошибок, НИКАКИХ предупреждений.

UPD. Обратите внимание: вопрос не вsafe code / unsafe code или wrong code / right code. Вопрос оclповедение компилятора. Т.е. почему во втором случае cl считает, чтоinitializer IS a constant (такой вывод делается из-за отсутствия сообщения об ошибке).

1 ответ

Решение

Инициализаторы для объектов со статической продолжительностью хранения должны быть постоянными выражениями.

В стандарте C89 (единственный стандарт, которому соответствует Microsoft C) приведение указателя к целому числу не разрешено в постоянном выражении.

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

Из-за отсутствия сообщения об ошибке нельзя сделать вывод, что инициализатор был принят как константа.

Начиная с C99, стандарт также включает текст "Реализация может принимать другие формы константных выражений". Компилятор должен опубликовать соответствующую документацию со списком выражений, которые он принимает как константы, хотя мне не удалось найти эту документацию для MSVC. (оставьте комментарий, если можете!)

Также может быть уместно отметить правила приведения указателя к unsigned long. Из последнего стандарта:

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

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

Другие вопросы по тегам