переключение регистра: метка регистра не сводится к целочисленной константе - PullRequest
3 голосов
/ 08 апреля 2019

Я прекрасно знаю механизм, стоящий за оператором switch, и почему требуется целочисленная константа.Я не понимаю, почему следующая метка case не считается целочисленной константой.Что тогда?Несуществующая переменная?Кто-нибудь может классифицировать это?Разве компилятор C действительно должен быть таким тупым?

struct my_struct {
    const int my_int;
};

switch (4) {

case ((struct my_struct) { 4 }).my_int:

    printf("Hey there!\n");
    break;

}

И, конечно, ...

error: case label does not reduce to an integer constant
  case ((struct my_struct) { 4 }).my_int:

РЕДАКТИРОВАТЬ, чтобы ответить на комментарий Евгения:

Какой у вас настоящий вариант использования?Если это целочисленная константа, зачем делать это так сложно?

Я пытался найти хитрый хак для переключения между двухсимвольными строками, используя union вместо struct, как в следующем примере:

#include <stdio.h>

union my_union {
    char my_char[sizeof(int)];
    int my_int;
};

void clever_switch (const char * const my_input) {

    switch (((union my_union *) my_input)->my_int) {

    case ((union my_union) { "hi" }).my_int:

        printf("You said hi!\n");
        break;

    case ((union my_union) { "no" }).my_int:

        printf("Why not?\n");
        break;

    }

}

int main (int argc, char *argv[]) {

    char my_string[sizeof(int)] = "hi";

    clever_switch(my_string);

    return 0;

}

… Что, конечно, не компилируется.

На моей машине ((union my_union) { "hi" }).my_int равно 26984, а ((union my_union) { "no" }).my_int равно 28526.Однако я не могу написать эти числа самостоятельно, потому что они зависят от порядкового номера машины (так что, очевидно, моя машина имеет порядковый номер).Но компилятор знает о последнем и точно знает во время компиляции, какое число будет ((union my_union) { "no" }).my_int.

Раздражает то, что я могу уже сделать это, но только используяочень неясный (и немного менее эффективный) синтаксис.Следующий пример компилируется просто отлично:

#include <stdio.h>

void clever_switch (const char * const my_input) {

    #define TWO_LETTERS_UINT(FIRST_LETTER, SECOND_LETTER) ((unsigned int) ((FIRST_LETTER) << 8) | (SECOND_LETTER))

    switch (TWO_LETTERS_UINT(my_input[0], my_input[1])) {

    case TWO_LETTERS_UINT('h', 'i'):

        printf("You said hi!\n");
        break;

    case TWO_LETTERS_UINT('n', 'o'):

        printf("Why not?\n");
        break;

    }

    #undef TWO_LETTERS_UINT

}

int main (int argc, char *argv[]) {

    clever_switch("hi"); /* "You said hi!" */
    clever_switch("no"); /* "Why not?" */

    return 0;

}

Таким образом, остается вопрос: компилятор C (или стандарт C в данном случае) действительно должен быть таким тупым?

Ответы [ 5 ]

5 голосов
/ 08 апреля 2019

Хотя выражение ((struct my_struct) { 4 }).my_int действительно вычисляется во время выполнения до 4, оно не является постоянным. Случай переключения требует постоянного выражения.

Я вижу, что вы объявили my_int как const. Но это только означает, что это не может быть изменено позже. Это не значит, что выражение ((struct my_struct) { 4 }).my_int является константой.

Если вы используете if-statement вместо switch-case, все будет в порядке.

if (((struct my_struct) { 4 }).my_int == 4) {
    printf("Hey there!\n");
}
4 голосов
/ 08 апреля 2019

Метка case в выражении switch требует выражения целочисленной константы , которое определяется как:

Выражение целочисленной константы должно иметь тип целого числа и должноиметь только операнды, которые являются целочисленными константами, константами перечисления, символьными константами, выражениями sizeof, результатом которых являются целочисленные константы, выражения _Alignof и плавающие константы, которые являются непосредственными операндами приведений.Операторы приведения в выражении с целочисленной константой должны преобразовывать только арифметические типы в целочисленные типы, кроме как в качестве части операнда, к оператору sizeof или _Alignof.

Выражение ((struct my_struct) { 4 }).my_int не квалифицируется как целочисленная константавыражение по этому определению, даже если это целочисленное выражение, значение которого может быть определено во время компиляции.

1 голос
/ 08 апреля 2019

Относительно вопроса , почему стандарт C не позволяет компилятору принимать

case ((struct my_struct) { 4 }).my_int:

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

Но мы можем сказать следующее:

  1. Первоначальный стандарт 1989 C преднамеренно исключил любое количество функций, которые могли бы быть реализованы, но только при значительных затратах на сложность реализации, требования к памяти во время компиляции и т. Д. Например, Первоначальное обоснование различия между «константным выражением» и « integer константным выражением» в стандарте заключалось в том, что компилятору никогда не нужно выполнять арифметику с плавающей точкой во время компиляции.

    Запрашиваемая вами функция примерно так же сложна, как и

    static const int CONSTANT = 123;
    ...
    switch (x) { case CONSTANT: ... }
    

    , что также не требуется для работы в C (хотя это в C ++).

  2. Дополнения к стандарту C с 1989 года были относительно небольшими и только в ответ на существенный спрос. В частности, насколько я могу судить, «реализация этой функции более не является дорогостоящей» не считается достаточной причиной.

Это лучший ответ, который я могу вам дать.

1 голос
/ 08 апреля 2019

Это наименьший общий знаменатель.

Стандарт 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).

0 голосов
/ 09 апреля 2019

Хорошо. Почему это невозможно, похоже, было подробно объяснено ... ... тогда я просто оставлю это здесь.

case ((int)((struct my_struct) { 4 }).my_int):

clang 9, arch-linux, x86_64

...