Почему нельзя объявлять переменные в операторе switch?

Я всегда задавался вопросом - почему вы не можете объявить переменные после метки case в операторе switch? В C++ вы можете объявлять переменные практически где угодно (и объявление их близко к первому использованию, очевидно, хорошо), но следующее по-прежнему не сработает:

switch (val)  
{  
case VAL:  
  // This won't work
  int newVal = 42;  
  break;
case ANOTHER_VAL:  
  ...
  break;
}  

Вышеупомянутое дает мне следующую ошибку (MSC):

initialization of 'newVal' is skipped by 'case' label

Это кажется ограничением и для других языков. Почему это такая проблема?

Для объяснения, основанного на грамматике C BNF, см. stackoverflow.com/questions/1180550/weird-switch-error-in-ob‌ j-c /…

johne 12.01.2010 06:30
Вот действительно хорошее чтение about switch statements and labels (ABC:) in general.
Etherealone 17.08.2012 02:33

Я бы сказал: «Почему переменные нельзя инициализировать в операторе switch, а не объявлять?» Поскольку простое объявление переменной дает мне только предупреждение в MSVC.

ZoomIn 09.12.2013 10:21

Если вы поместите все внутри метки case в фигурные скобки {}, то все заработает.

E Purdy 27.08.2020 16:33
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
999
4
303 686
23
Перейти к ответу Данный вопрос помечен как решенный

Ответы 23

Весь оператор switch находится в той же области. Чтобы обойти это, сделайте следующее:

switch (val)
{
    case VAL:
    {
        // This **will** work
        int newVal = 42;
    }
    break;

    case ANOTHER_VAL:
      ...
    break;
}

Примечание скобки.

Попробуй это:

switch (val)
{
    case VAL:
    {
        int newVal = 42;
    }
    break;
}

Я считаю, что проблема заключается в том, что оператор был пропущен, и вы пытались использовать var в другом месте, он не был объявлен.

Вы можете объявлять переменные внутри оператора switch если, вы начинаете новый блок:

switch (thing)
{ 
  case A:
  {
    int i = 0;  // Completely legal
  }
  break;
}

Причина в том, чтобы выделить (и освободить) пространство в стеке для хранения локальных переменных.

Переменная может быть объявлена, но не может быть инициализирована. Кроме того, я почти уверен, что проблема никак не связана со стеком и локальными переменными.

Richard Corden 18.09.2008 18:12

Вы не можете этого сделать, потому что метки case на самом деле являются просто точками входа в содержащий блок.

Наиболее наглядно это иллюстрирует Устройство Даффа. Вот код из Википедии:

strcpy(char *to, char *from, size_t count) {
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}

Обратите внимание, как метки case полностью игнорируют границы блоков. Да, это зло. Но вот почему ваш пример кода не работает. Переход к метке case аналогичен использованию goto, поэтому вам не разрешается перепрыгивать через локальную переменную с помощью конструктора.

Как указывалось на нескольких других плакатах, вам нужно вставить собственный блок:

switch (...) {
    case FOO: {
        MyObject x(...);
        ...
        break; 
    }
    ...
 }

В этой реализации устройства Даффа есть ошибка, которая делает его чрезвычайно медленным: count имеет тип int, поэтому% должен выполнять настоящую операцию деления / по модулю. Сделайте count без знака (или еще лучше, всегда используйте size_t для счетчиков / индексов), и проблема исчезнет.

R.. GitHub STOP HELPING ICE 04.07.2010 00:58

@R ..: Что ?! В системе с дополнением до двух подписи не влияют на модули по степеням 2 (это просто AND для нижних битов) и не влияет на деление по степеням 2, пока в вашей архитектуре процессора есть арифметическая операция сдвига вправо. (SAR в x86, по сравнению с SHR, который предназначен для беззнаковых сдвигов).

Chris Jester-Young 02.09.2010 18:00

@Chris: Я полагаю, он имеет в виду, когда компилятор должен разрешать отрицательные значения, когда «просто И для нижних битов» не выполняется; например, -1% 8 дает -1 в этой системе дополнения до двух с использованием g ++ (знак в этом случае - реализация, определенная в 5.6 / 4).

Roger Pate 02.09.2010 19:45

@Roger: Да, компилятор должен учитывать отрицательные значения и лишние циклы (так что беззнаковый еще более идеален), но на x86 (с которым я тестировал) gcc по-прежнему не генерирует инструкцию DIV. Это эквивалент: num < 0 ? ((num + 7) & 7) - 7 : num & 7 (но без каких-либо ветвлений, умножения или деления вообще). Так что это ни в коем случае не "очень медленно".

Chris Jester-Young 02.09.2010 20:40

Что ж, это все еще значительно медленнее, чем просто побитовое и. На современных процессорах я не удивлюсь, если этот код будет почти таким же медленным, как разделение. Моя точка зрения остается неизменной: вы должны использовать беззнаковые типы или писать побитовые и сами.

R.. GitHub STOP HELPING ICE 03.09.2010 00:33

@Roger: знак больше не определяется реализацией. C99 определяет деление с отрицательными числами и определяет его неверно, так что теперь это неприятное поведение требуется стандартом. :-(

R.. GitHub STOP HELPING ICE 03.09.2010 00:34

@Chris: Я согласен с вами, что R преувеличивает влияние; Я видел только ваш комментарий и знал, что простого И недостаточно.

Roger Pate 03.09.2010 03:21

@R ..: Исходный код в этом комментарии был взят из Википедии, как указано выше. Я только что исправил это, чтобы использовать size_t. Спасибо, что указали на проблемы с оператором% C99; об этом стоит помнить.

emk 06.09.2010 00:48

Также стоит отметить, что исходный код Википедии предназначен для отправки данных в вывод с отображением памяти, что здесь выглядит странно, потому что он не упоминается, и каждый байт копируется в одно и то же место «в». Можно обойти это, либо добавив postfix ++ в to, либо упомянув вариант использования для ввода-вывода с отображением памяти. Совершенно второстепенный по отношению к исходному вопросу :-).

Peter 05.03.2011 02:47

newVal существует во всей области действия переключателя, но инициализируется только при попадании в конечность VAL. Если вы создаете блок вокруг кода в VAL, все должно быть в порядке.

Весь раздел переключателя представляет собой единый контекст объявления. Вы не можете объявить переменную в таком операторе case. Попробуйте вместо этого:

switch (val)  
{  
case VAL:
{
  // This will work
  int newVal = 42;
  break;
}
case ANOTHER_VAL:  
  ...
  break;
}

Переменная может быть объявлена, но не может быть инициализирована.

Richard Corden 18.09.2008 18:11

@Richard Corden Я уверен, что инициализация сработает. Вы все еще утверждаете, что его нельзя инициализировать?

chux - Reinstate Monica 13.09.2013 23:07
Ответ принят как подходящий

Операторы Case - это только этикетки. Это означает, что компилятор интерпретирует это как переход непосредственно к метке. В C++ проблема заключается в области видимости. Фигурные скобки определяют область как все, что находится внутри оператора switch. Это означает, что у вас остается область, в которой будет выполняться переход в код, пропуская инициализацию.

Правильный способ справиться с этим - определить область, специфичную для этого оператора case, и определить в ней свою переменную:

switch (val)
{   
case VAL:  
{
  // This will work
  int newVal = 42;  
  break;
}
case ANOTHER_VAL:  
...
break;
}

По сравнению с открытием новой области видимости - предпочтение читаемости и согласованности кода. Раньше вы могли автоматически получать «лишний» фрейм стека, но теперь этого не должно быть ни в одном приличном оптимизирующем компиляторе.

Tall Jeff 18.09.2008 18:37

Я согласен с Джеффом - слишком легко «предположить» область видимости при чтении оператора switch из-за стиля отступов, который использует большинство людей. Мой собственный стиль - всегда открывать новую область видимости для каждого случая / по умолчанию, если она длиннее одной строки.

Bids 31.01.2009 19:43

workmad3 - Сможете ли вы найти мне вообще какой-нибудь компилятор C++, который сгенерирует новый фрейм стека, если вы не объявите никаких новых переменных? Вы меня вкратце обеспокоили, но ни один из G ++ 3.1, Visual C++ 7 или Intel C++ 8 не будет генерировать какой-либо код для новых областей, в которых вы не объявляете никаких переменных.

Chris Jefferson 07.02.2009 23:11

Может ли кто-нибудь помочь мне с тем, как создается еще один фрейм стека, просто добавляя скобки?

user379888 14.07.2011 01:48

@ workmad3 путем ввода нового блока фигурных скобок не вызывает новый кадр стека stackoverflow.com/questions/2759371/…

MTVS 14.01.2013 13:40

Проверить, был ли создан фрейм стека, легко - вы увидите новую строку в окне стека вызовов и не увидите значений локальных переменных внешнего блока в отладчике, пока вы не переключитесь на «основной» фрейм из функция. Я никогда не видел такого компилятора, даже Visual Studio 6.0, выпущенная в 1998 году, этого не делает.

Marian Spanik 05.04.2016 20:58

@TallJef Я не знаю, о каких «старых временах» вы говорите. Я никогда не встречал компилятора, где все пространство стека для метода не выделялось при вводе метода, за 40 лет.

user207421 10.04.2017 11:05

@EJP: Ну, когда используется _alloca(), компилятор не может знать, сколько места требуется при входе, поэтому ему приходится вносить частичные корректировки.

Ben Voigt 30.11.2017 00:29

В компиляторе IAR я столкнулся со своеобразной ситуацией с подобным государственным менетом. Внутри case был массив (с областью видимости), но память выделялась независимо от ввода case, просто путем ввода функции. Поскольку другие случаи привели к более глубокому стеку, чем этот, в конечном итоге это привело к переполнению стека.

Do-do-new 30.04.2018 14:40

@MarquisofLorne У меня определенно есть. Фактически полагался на это в каком-то приложении, где у меня была рекурсивная функция с временным массивом, который не был выделен через весь вызов функции, а не при выполнении рекурсивного вызова.

gnasher729 10.06.2020 07:38

Если в вашем коде написано «int newVal = 42», то можно разумно ожидать, что newVal никогда не будет неинициализирован. Но если вы перейдете к этому оператору (что вы и делаете), то именно это и произойдет - newVal входит в область видимости, но не был назначен.

Если это именно то, что вы действительно хотели, тогда язык требует сделать это явным, сказав «int newVal; newVal = 42;». В противном случае вы можете ограничить область действия newVal одним случаем, что более вероятно, что вы хотели.

Это может прояснить ситуацию, если вы рассмотрите тот же пример, но с "const int newVal = 42;"

Учитывать:

switch(val)
{
case VAL:
   int newVal = 42;
default:
   int newVal = 23;
}

При отсутствии операторов break иногда newVal объявляется дважды, и вы не знаете, будет ли это до времени выполнения. Я предполагаю, что ограничение вызвано такой путаницей. Каковы будут возможности newVal? Согласно соглашению, это будет весь блок переключателя (между скобками).

Я не программист на C++, но на C:

switch(val) {
    int x;
    case VAL:
        x=1;
}

Работает отлично. Объявление переменной внутри блока переключателя - это нормально. Заявления после того, как охранник дела нет.

@ Mr.32: на самом деле ваш пример показывает, что printf не выполняется, но в этом случае int x не является оператором, а объявлением, x объявляется, пространство для него резервируется каждый раз, когда среда функции складывается, см .: codepad.org/4E9Zuz1e

Petruza 18.12.2011 11:03

Я ожидал найти это при чтении заголовка вопроса, потому что вопрос не в объявлении переменных в метках «case:», а в операторах switch. И только вы (и VictorH, подчеркивая свой ответ) на самом деле говорили о переменных в операторах switch.

cesss 04.01.2018 01:47

Новые переменные можно объявлять только в области видимости блока. Вам нужно написать примерно так:

case VAL:  
  // This will work
  {
  int newVal = 42;  
  }
  break;

Конечно, newVal имеет только объем в фигурных скобках ...

Привет, Ральф

Ok. Просто уточнить это строго не имеет отношения к декларации. Относится только к «перепрыгиванию инициализации» (ISO C++ '03 6.7 / 3)

Во многих сообщениях здесь упоминается, что переход через объявление может привести к тому, что переменная «не будет объявлена». Это неправда. Объект POD может быть объявлен без инициализатора, но он будет иметь неопределенное значение. Например:

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' set (not initialized) to 0, but this statement
            // is jumped when 'i == 1'
     break;
   case 1:
     ++j;   // 'j' is in scope here - but it has an indeterminate value
     break;
}

Если объект не является POD или агрегатным, компилятор неявно добавляет инициализатор, и поэтому невозможно перепрыгнуть через такое объявление:

class A {
public:
  A ();
};

switch (i)  // Error - jumping over initialization of 'A'
{
   case 0:
     A j;   // Compiler implicitly calls default constructor
     break;
   case 1:
     break;
}

Это ограничение не ограничивается оператором switch. Также ошибкой является использование goto для перехода через инициализацию:

goto LABEL;    // Error jumping over initialization
int j = 0; 
LABEL:
  ;

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

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

"Ошибка перепрыгивания инициализации" ??? Только не с моим GCC. При использовании j под меткой может появиться предупреждение «j может быть использован унифицированный», но ошибки нет. Однако в случае переключения возникает ошибка (серьезная ошибка, а не слабое предупреждение).

Mecki 20.02.2010 04:20

@Mecki: Это запрещено в C++. ISO C++ '03 - 6.7 / 3: «... Программа, которая перескакивает из точки, в которой локальная переменная с автоматической продолжительностью хранения не входит в область видимости, в точку, в которой она находится в области видимости, неправильно сформирована, если только переменная не имеет тип POD (3.9) и объявлен без инициализатора (8.5) ».

Richard Corden 22.02.2010 21:21

Да, но это не является незаконным в C (по крайней мере, gcc говорит, что это не так). j будет неинициализированным (иметь какое-то случайное число), но компилятор его компилирует. Однако в случае оператора switch компилятор даже не скомпилирует его, и я не вижу разницы между случаем goto / label и случаем switch.

Mecki 23.02.2010 01:01

@Mecki: В общем, поведение одного компилятора не обязательно отражает то, что действительно разрешено языком. Я проверил как C'90, так и C'99, и оба стандарта включают пример с инициализацией перехода в операторе switch.

Richard Corden 01.03.2010 01:23

Большинство ответов до сих пор неверны в одном отношении: вы может объявляете переменные после оператора case, но вы не могу инициализируете их:

case 1:
    int x; // Works
    int y = 0; // Error, initialization is skipped by case
    break;
case 2:
    ...

Как упоминалось ранее, хороший способ обойти это - использовать фигурные скобки для создания области видимости для вашего случая.

Мистер 32, вы неправильно поняли, в чем заключается ваша ошибка: да, она не будет компилироваться, но не потому, что вы объявляете переменную внутри переключателя. Ошибка возникает из-за того, что вы пытаетесь объявить переменную после оператора, что недопустимо в C.

MrZebra 18.12.2011 19:26

Теперь дни, когда это разрешено в c90 и более новых версиях c

Jeegar Patel 19.12.2011 08:39

Мой любимый трюк со злым переключателем - использовать if (0), чтобы пропустить ненужную метку case.

switch(val)
{
case 0:
// Do something
if (0) {
case 1:
// Do something else
}
case 2:
// Do something in all cases
}

Но очень злой.

Очень хорошо. Пример того, почему: case 0 и case 1 могут, например, по-разному инициализировать переменную, которая затем используется в случае 2.

hlovdal 19.03.2009 13:02

Если вы хотите, чтобы и случай 0, и случай 1 не попали в случай 2 (при этом случай 0 не попал в случай 1). Не знаю, действительно ли это полезно, но точно работает.

Petruza 18.12.2011 10:55

Вы можете просто перейти к нужной метке с goto без обфускации кода

SomeWittyUsername 24.09.2016 22:12

Я просто хотел выделить стройныйточка. Конструкция переключателя создает целую первоклассную область видимости. Таким образом, можно объявить (и инициализировать) переменную в операторе switch перед первой меткой case, без - дополнительной парой скобок:

switch (val) {  
  /* This *will* work, even in C89 */
  int newVal = 42;  
case VAL:
  newVal = 1984; 
  break;
case ANOTHER_VAL:  
  newVal = 2001;
  break;
}

-1 здесь int newVal = 42; никогда не будет казнен. увидеть это codepad.org/PA1quYX3

Jeegar Patel 18.12.2011 09:55

объявление int newValбуду должно выполняться, но не назначение = 42.

Petruza 18.12.2011 11:10

Пока что ответы касались C++.

Для C++ вы не можете перепрыгнуть через инициализацию. Вы можете это сделать в C. Однако в C объявление не является оператором, и за метками case должны следовать операторы.

Итак, допустимый (но уродливый) C, недопустимый C++

switch (something)
{
  case 1:; // Ugly hack empty statement
    int i = 6;
    do_stuff_with_i(i);
    break;
  case 2:
    do_something();
    break;
  default:
    get_a_life();
}

И наоборот, в C++ объявление - это оператор, поэтому следующий допустимый C++, недопустимый C

switch (something)
{
  case 1:
    do_something();
    break;
  case 2:
    int i = 12;
    do_something_else();
}

Второй пример НЕ является действительным C++ (тест с vc2010 и gcc 4.6.1 C++ не позволяет пропустить часть инициализации. Сообщение об ошибке gcc: перекрестная инициализация 'int i'

zhaorufei 26.12.2011 13:50

Интересно, что это нормально:

switch (i)  
{  
case 0:  
    int j;  
    j = 7;  
    break;  

case 1:  
    break;
}

... но это не так:

switch (i)  
{  
case 0:  
    int j = 7;  
    break;  

case 1:  
    break;
}

Я понимаю, что исправить это достаточно просто, но я пока не понимаю, почему первый пример не беспокоит компилятор. Как упоминалось ранее (2 года назад хе-хе), декларация не является причиной ошибки, даже несмотря на логику. Инициализация - это проблема. Если переменная инициализирована и объявлена ​​в разных строках, она компилируется.

Первый не подходит для gcc 4.2: «ошибка: ожидаемое выражение перед int». Как говорят Питер и Мистер 32, «case 0:; int j; ...» и «case 0:; int j = 7; ...» работают и то, и другое. Проблема в C заключается как раз в том, что «case <label>: декларация» не является допустимым синтаксисом C.

dubiousjim 31.05.2012 01:21

Прочитав все ответы и проведя еще несколько исследований, я понял несколько вещей.

Case statements are only 'labels'

В C, согласно спецификации,

§6.8.1 Помеченные заявления:

labeled-statement:
    identifier : statement
    case constant-expression : statement
    default : statement

В C нет предложения, допускающего «помеченное объявление». Это просто не часть языка.

Так

case 1: int x=10;
        printf(" x is %d",x);
break;

Это не будет компилироваться, см. http://codepad.org/YiyLQTYw. GCC выдает ошибку:

label can only be a part of statement and declaration is not a statement

Четное

  case 1: int x;
          x=10;
            printf(" x is %d",x);
    break;

это также не компилируется, см. http://codepad.org/BXnRD3bu. Здесь я тоже получаю ту же ошибку.


В C++, согласно спецификации,

помеченное-объявление разрешено, но помеченное -инициализация не разрешено.

См. http://codepad.org/ZmQ0IyDG.


Решение такого условия - два

  1. Либо используйте новую область видимости, используя {}

    case 1:
           {
               int x=10;
               printf(" x is %d", x);
           }
    break;
    
  2. Или используйте фиктивную инструкцию с меткой

    case 1: ;
               int x=10;
               printf(" x is %d",x);
    break;
    
  3. Объявите переменную перед switch () и инициализируйте ее разными значениями в инструкции case, если она соответствует вашему требованию.

    main()
    {
        int x;   // Declare before
        switch(a)
        {
        case 1: x=10;
            break;
    
        case 2: x=20;
            break;
        }
    }
    

Еще кое-что с оператором switch

Никогда не записывайте в переключатель никаких операторов, которые не являются частью какой-либо метки, потому что они никогда не будут выполняться:

switch(a)
{
    printf("This will never print"); // This will never executed

    case 1:
        printf(" 1");
        break;

    default:
        break;
}

См. http://codepad.org/PA1quYX3.

Вы правильно описали проблему C. Но утверждение, что в C++ помеченная инициализация не разрешена, совершенно не соответствует действительности. Нет ничего плохого в маркированной инициализации в C++. Что C++ не позволяет, так это инициализацию перепрыгивать переменной a в область видимости переменной a. Итак, с точки зрения C, проблема связана с этикеткой case VAL:, и вы правильно ее описали. Но с точки зрения C++ проблема связана с этикеткой case ANOTHER_VAL:.

AnT 07.11.2013 12:14

В C++, в отличие от C, объявления - это подмножество операторов.

Keith Thompson 07.08.2016 03:01

Стандарт C++ имеет: Можно передать в блок, но не в обход объявлений с инициализацией. Программа, которая перескакивает из точки, где локальная переменная с автоматической продолжительностью хранения не находится в области видимости, к точке, где она находится в области видимости, является плохо сформированной, если только переменная не имеет тип POD (3.9) и объявлена ​​без инициализатора (8.5).

Код, иллюстрирующий это правило:

#include <iostream>

using namespace std;

class X {
  public:
    X() 
    {
     cout << "constructor" << endl;
    }
    ~X() 
    {
     cout << "destructor" << endl;
    }
};

template <class type>
void ill_formed()
{
  goto lx;
ly:
  type a;
lx:
  goto ly;
}

template <class type>
void ok()
{
ly:
  type a;
lx:
  goto ly;
}

void test_class()
{
  ok<X>();
  // compile error
  ill_formed<X>();
}

void test_scalar() 
{
  ok<int>();
  ill_formed<int>();
}

int main(int argc, const char *argv[]) 
{
  return 0;
}

Код для демонстрации эффекта инициализатора:

#include <iostream>

using namespace std;

int test1()
{
  int i = 0;
  // There jumps fo "case 1" and "case 2"
  switch(i) {
    case 1:
      // Compile error because of the initializer
      int r = 1; 
      break;
    case 2:
      break;
  };
}

void test2()
{
  int i = 2;
  switch(i) {
    case 1:
      int r;
      r= 1; 
      break;
    case 2:
      cout << "r: " << r << endl;
      break;
  };
}

int main(int argc, const char *argv[]) 
{
  test1();
  test2();
  return 0;
}

Похоже, что анонимные объекты может объявляются или создаются в операторе switch case по той причине, что на них нельзя ссылаться и как таковые не могут перейти в следующий case. Рассмотрим этот пример, компилируемый на GCC 4.5.3 и Visual Studio 2008 (возможно, это проблема соответствия, поэтому эксперты, пожалуйста, взвесьте)

#include <cstdlib>

struct Foo{};

int main()
{
    int i = 42;

    switch( i )
    {
    case 42:
        Foo();  // Apparently valid
        break;

    default:
        break;
    }
    return EXIT_SUCCESS;
}

Если вы собираетесь проголосовать против, объясните, почему. Мне любопытно узнать, почему создание анонимного объекта кажется исключением.

Olumide 31.05.2016 17:03

не DV, но: Весь вопрос в объявлении / объеме именованных переменных. Временный («анонимный объект» не является термином) не является именованной переменной, не является объявлением и не является предметом области видимости (если не привязан к ссылке const с собственной областью видимости). Это выражение, которое живет и умирает в своем заявлении (где бы оно ни было). Следовательно, это не имеет никакого значения.

underscore_d 17.07.2016 20:55

Foo(); не является декларацией; речь идет о декларациях.

M.M 06.07.2017 07:02

Этот вопрос является изначально был помечен как [C] и [C++] одновременно. Исходный код действительно недействителен как в C, так и в C++, но по совершенно другим, не связанным между собой причинам.

  • В C++ этот код недействителен, потому что метка case ANOTHER_VAL: переходит в область действия переменной newVal, минуя ее инициализацию. Переходы, которые обходят инициализацию автоматических объектов, недопустимы в C++. В большинстве ответов эта сторона вопроса решена правильно.

  • Однако в языке C обход инициализации переменной не является ошибкой. Переход в область видимости переменной при ее инициализации разрешен в C. Это просто означает, что переменная остается неинициализированной. Исходный код не компилируется на C по совершенно другой причине. Метка case VAL: в исходном коде прикреплена к объявлению переменной newVal. В языке C объявления не являются операторами. Их нельзя пометить. И это то, что вызывает ошибку, когда этот код интерпретируется как код C.

    switch (val)  
    {  
    case VAL:             /* <- C error is here */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:     /* <- C++ error is here */
      ...
      break;
    }
    

Добавление дополнительного блока {} устраняет проблемы как C++, так и C, хотя эти проблемы очень разные. На стороне C++ он ограничивает область действия newVal, следя за тем, чтобы case ANOTHER_VAL: больше не перескакивал в эту область, что устраняет проблему C++. На стороне C этот дополнительный {} вводит составной оператор, тем самым заставляя метку case VAL: применяться к оператору, что устраняет проблему C.

  • В случае C проблема может быть легко решена без {}. Просто добавьте пустой оператор после метки case VAL:, и код станет действительным.

    switch (val)  
    {  
    case VAL:;            /* Now it works in C! */
      int newVal = 42;  
      break;
    case ANOTHER_VAL:  
      ...
      break;
    }
    

    Обратите внимание, что даже несмотря на то, что теперь он действителен с точки зрения C, он остается недействительным с точки зрения C++.

  • Симметрично, в случае с C++ проблема легко решается без {}. Просто удалите инициализатор из объявления переменной, и код станет действительным.

    switch (val)  
    {  
    case VAL: 
      int newVal;
      newVal = 42;  
      break;
    case ANOTHER_VAL:     /* Now it works in C++! */
      ...
      break;
    }
    

    Обратите внимание, что даже несмотря на то, что теперь он действителен с точки зрения C++, он остается недействительным с точки зрения C.

@AnT: я понимаю, почему тот, который исправляет C++, неприменим для C; однако я не могу понять, как он решает проблему C++ с пропуском инициализации в первую очередь? Разве он не пропустил бы объявление и присвоение newVal при переходе на ANOTHER_VAL?

legends2k 19.06.2015 12:08

@ legends2k: Да, все равно пропускает. Однако, когда я говорю «это устраняет проблему», я имею в виду, что это исправляет ошибка компилятора C++. В C++ запрещено пропускать скалярное объявление с инициализатором, но вполне нормально пропускать скалярное объявление без инициализатора. В точке case ANOTHER_VAL: переменная newVal видна, но с неопределенным значением.

AnT 19.06.2015 17:45

Очаровательный. Я нашел этот вопрос после прочтения §A9.3: Compound Statement из K&R C (второе издание). В этой записи упоминается техническое определение составное заявление, которым является {declaration-list[opt] statement-list[opt]}. Сбитый с толку, поскольку я думал, что объявление БЫЛО утверждением, я поискал его и сразу нашел этот вопрос, пример, в котором указанное несоответствие становится очевидным и фактически перерывы - это программа. Я считаю, что другим решением (для C) было бы поместить другой оператор (возможно, пустой оператор?) перед в объявление, чтобы помеченное заявление был удовлетворен.

Braden Best 04.02.2017 13:04

К сожалению, я только что заметил, что предложенное мной решение с нулевым оператором уже есть в вашем ответе. Тогда не важно.

Braden Best 04.02.2017 13:19

Лучшее решение, вероятно, - просто объявить переменную вне оператора switch.

SeanRamey 07.09.2017 23:07

Стоит отметить, что исправление добавления пустого оператора работает только для C99 и далее. В C89 переменные должны быть объявлены в начале их включающего блока.

Arthur Tacca 12.12.2019 19:40

@AnT Похоже, довольно плохое дизайнерское решение для языка, который должен быть в основном обратно совместимым, чтобы допускать int x; x=42; label:;, но выдает серьезную ошибку для int x=42; label1;. Отказ C принять помеченные объявления кажется таким же произвольным ограничением для C в его текущей форме (но по крайней мере C никогда не рекламировал себя как в основном совместимый с C++).

PSkocik 15.06.2020 14:16

Кстати, перемещение объявления без инициализации до того, как все case-label исправит его на обоих языках.

Deduplicator 19.09.2020 22:41

Блок switchне то же самое, что последовательность блоков if/else if. Я удивлен, что никакой другой ответ не объясняет это четко.

Рассмотрим это заявление switch:

switch (value) {
    case 1:
        int a = 10;
        break;
    case 2:
        int a = 20;
        break;
}

Это может быть удивительно, но компилятор не увидит в нем простой if/else if. Будет получен следующий код:

if (value == 1)
    goto label_1;
else if (value == 2)
    goto label_2;
else
    goto label_end;

{
label_1:
    int a = 10;
    goto label_end;
label_2:
    int a = 20; // Already declared !
    goto label_end;
}

label_end:
    // The code after the switch block

Операторы case преобразуются в метки, а затем вызываются с помощью goto. Скобки создают новую область видимости, и теперь легко понять, почему вы не можете объявить две переменные с одним и тем же именем в блоке switch.

Это может выглядеть странно, но необходимо поддерживать провалиться (то есть не использовать break, чтобы продолжить выполнение до следующего case).

Я написал этот ответ изначально для этот вопрос. Однако, когда я закончил, я обнаружил, что ответ был закрыт. Я разместил это здесь, может быть, кому-то, кому нравятся ссылки на стандарты, это пригодится.

Исходный код, о котором идет речь:

int i;
i = 2;
switch(i)
{
    case 1: 
        int k;
        break;
    case 2:
        k = 1;
        cout<<k<<endl;
        break;
}

На самом деле есть 2 вопроса:

1. Почему я могу объявить переменную после метки case?

Это потому, что в C++ метка должна иметь форму:

N3337 6,1 / 1

labeled-statement:

...

  • attribute-specifier-seqoptcaseconstant-expression : statement

...

А в C++заявление также рассматривается как утверждение (в отличие от C):

N3337 6/1:

statement:

...

declaration-statement

...

2. Почему я могу перепрыгнуть через объявление переменной, а затем использовать его?

Потому что: N3337 6,7 / 3

It is possible to transfer into a block, but not in a way that bypasses declarations with initialization. A program that jumps (The transfer from the condition of a switch statement to a case label is considered a jump in this respect.)

from a point where a variable with automatic storage duration is not in scope to a point where it is in scope is ill-formedunless the variable has scalar type, class type with a trivial default constructor and a trivial destructor, a cv-qualified version of one of these types, or an array of one of the preceding types and is declared without an initializer (8.5).

Поскольку k имеет скалярный тип и не инициализируется в момент объявления, возможен переход через его объявление. Это семантически эквивалентно:

goto label;

int x;

label:
cout << x << endl;

Однако это было бы невозможно, если бы x был инициализирован в момент объявления:

 goto label;

    int x = 58; //error, jumping over declaration with initialization

    label:
    cout << x << endl;

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