Это наименьший общий знаменатель.
Стандарт C говорит, что ((struct my_struct) { 4 }).my_int
не удовлетворяет ограничениям, наложенным на метки регистра (а именно, что они являются целочисленными константными выражениями), поэтому совместимые компиляторы C не являютсяТребуется быть достаточно умным, чтобы иметь возможность его оптимизировать.
Стандарт не запрещает компилятору оптимизировать его.Действительно, оптимизировать это то, что делает clang.
Ваша программа:
#include <stdio.h>
struct my_struct {
const int my_int;
};
int main()
{
switch (4) {
case ((struct my_struct) { 4 }).my_int:
printf("Hey there!\n");
break;
}
}
просто работает на clang , хотя вы получите предупреждение, если скомпилируете его с помощью -pedantic
.
В других случаях, например, когда проводится различие между VLA и регулярными массивами, различие между целочисленными константными выражениями и другими целочисленными выражениями также влияет на другие конструкции, такие как прыжки на основе switch
или goto
, которые становятся запрещеннымиесли они прыгнут в сферу VLA
.Опять же, компилятор может свернуть его и разрешить такие переходы, пока выполняется хотя бы одна диагностика (clang предупреждает о сворачивании, а не о прыжке).
Если вы используете эти конструкции, а ваш компилятор не будетостановите вас, ваша программа не будет переносимой.
Наконец, константа целых чисел во время компиляции также может влиять на типы в определенном случае.
Ядро Linux, я полагаю, использует нечто подобное
#define IS_CEXPR(X) _Generic((1? (void *) ((!!(X))*0ll) : (int *) 0), int*: 1, void*: 0)
для обнаружения целочисленных константных выражений (что, как я слышал, было частью миссии по отсеиванию VLA).
Это основано на правиле стандарта C, согласно которому выражение целочисленной константы, равное 0, приведенное к (void*)
, является константой нулевого указателя, тогда как регулярное целочисленное выражение, приведенное к (void*)
, является просто пустым указателем, дажеесли известно, что значение выражения равно 0. В правилах определения типа троичной системы различаются (void*)
выражения и константа нулевого указателя, в результате чего (1? (void *) ((!!(X))*0ll) : (int *) 0)
вводится int *
, если X
являетсяцелочисленное константное выражение и void *
в противном случае.
Большинство компиляторов, вероятно, не позволят вам легко обойти нарушения системы типов (особенно внутри _Generic
).