programing

switch 문에서 변수를 선언 할 수없는 이유는 무엇입니까?

nasanasas 2020. 9. 28. 09:16
반응형

switch 문에서 변수를 선언 할 수없는 이유는 무엇입니까?


나는 항상 이것을 궁금해했습니다-왜 switch 문에서 case 레이블 뒤에 변수를 선언 할 수 없습니까? C ++에서는 거의 모든 곳에서 변수를 선언 할 수 있지만 (그리고 처음 사용할 때에 가깝게 선언하는 것은 분명히 좋은 일입니다) 다음은 여전히 ​​작동하지 않습니다.

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

위의 내용은 다음과 같은 오류 (MSC)를 제공합니다.

'newVal'초기화는 'case'레이블에서 건너 뜁니다.

이것은 다른 언어에서도 한계 인 것 같습니다. 이것이 왜 그런 문제입니까?


Case문은 레이블 일뿐 입니다. 이것은 컴파일러가 이것을 레이블로 직접 점프하는 것으로 해석한다는 것을 의미합니다. C ++에서 여기서 문제는 범위 중 하나입니다. 중괄호는 switch명령문 내부의 모든 것으로 범위를 정의합니다 . 이것은 초기화를 건너 뛰는 코드로 더 점프가 수행되는 범위가 남아 있음을 의미합니다. 이를 처리하는 올바른 방법은 해당 case명령문에 특정한 범위를 정의하고 그 안에 변수를 정의하는 것입니다.

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

이 질문 원래 [C]와 [C ++]로 동시에 태그되었습니다. 원래 코드는 실제로 C와 C ++ 모두에서 유효하지 않지만 완전히 다른 관련없는 이유 때문입니다.

  • C ++에서이 코드는 case ANOTHER_VAL:레이블 newVal이 초기화를 우회하는 변수 범위로 점프 하기 때문에 유효하지 않습니다 . 자동 객체의 초기화를 우회하는 점프는 C ++에서 불법입니다. 이 문제는 대부분의 답변에서 올바르게 해결되었습니다.

  • 그러나 C 언어에서 변수 초기화를 우회하는 것은 오류가 아닙니다. 초기화를 통해 변수의 범위로 점프하는 것은 C에서 합법적입니다. 단순히 변수가 초기화되지 않은 상태로 남아 있음을 의미합니다. 원래 코드는 완전히 다른 이유로 C에서 컴파일되지 않습니다. case VAL:원래 코드의 레이블 은 variable 선언에 첨부됩니다 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 관점에서는 유효하지 않습니다.


확인. 이것을 명확히하는 것은 선언과 아무 관련이 없습니다. "초기화를 뛰어 넘는"(ISO C ++ '03 6.7 / 3)에만 관련됩니다.

여기에있는 많은 게시물에서 선언을 건너 뛰면 변수가 "선언되지 않음"이 될 수 있다고 언급했습니다. 이것은 사실이 아닙니다. POD 객체는 이니셜 라이저없이 선언 할 수 있지만 불확실한 값을 갖게됩니다. 예를 들면 :

switch (i)
{
   case 0:
     int j; // 'j' has indeterminate value
     j = 0; // 'j' 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에서 초기화를 건너 뛰는 것은 오류가 아닙니다.

다른 사람들이 언급했듯이 해결책은 변수의 수명이 개별 케이스 레이블로 제한되도록 중첩 블록을 추가하는 것입니다.


전체 switch 문은 동일한 범위에 있습니다. 이 문제를 해결하려면 다음과 같이하십시오.

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

    case ANOTHER_VAL:
      ...
    break;
}

대괄호에 유의 하십시오.


모든 답변과 더 많은 연구를 읽은 후 몇 가지를 얻습니다.

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 ++에서는 사양에 따라

label-declaration은 허용되지만 label-initialization은 허용되지 않습니다.

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을 참조 하십시오 .


case레이블은 실제로 컨테이너 블록의 진입 점일 뿐이 므로이를 수행 할 수 없습니다 .

이것은 Duff의 장치에서 가장 명확하게 설명됩니다 . 다음은 Wikipedia의 일부 코드입니다.

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; 
    }
    ...
 }

지금까지 대부분의 응답은 한 가지 측면에서 잘못되었습니다 . case 문 다음에 변수 선언 할 수 있지만 초기화 수는 없습니다 .

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

앞서 언급했듯이이 문제를 해결하는 좋은 방법은 중괄호를 사용하여 사례에 대한 범위를 만드는 것입니다.


내가 가장 좋아하는 악의 스위치 트릭은 원치 않는 케이스 레이블을 건너 뛰기 위해 if (0)을 사용하는 것입니다.

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

그러나 매우 사악합니다.


이 시도:

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

새 블록을 시작 하면 switch 문 내에서 변수를 선언 할 수 있습니다 .

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

그 이유는 지역 변수 (들)의 저장을 위해 스택에 공간을 할당 (및 회수)하기위한 것입니다.


중히 여기다:

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

break 문이 없으면 때때로 newVal이 두 번 선언되고 런타임까지 수행되는지 여부를 알 수 없습니다. 내 생각 엔 이런 종류의 혼란 때문이라고 생각합니다. newVal의 범위는 무엇입니까? 관습에 따라 스위치 블록 전체 (중괄호 사이)가 표시됩니다.

저는 C ++ 프로그래머는 아니지만 C :

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

잘 작동합니다. 스위치 블록 내에서 변수를 선언하는 것은 괜찮습니다. 케이스 가드 후 선언하지 않습니다.


스위치의 전체 섹션은 단일 선언 컨텍스트입니다. 이와 같은 case 문에서는 변수를 선언 할 수 없습니다. 대신 이것을 시도하십시오.

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

코드에 "int newVal = 42"가 표시되면 newVal이 초기화되지 않을 것이라고 합리적으로 예상 할 수 있습니다. 그러나이 진술 (당신이하고있는 일)을 살펴보면 정확히 일어나는 일입니다. newVal은 범위 내에 있지만 할당되지 않았습니다.

그것이 당신이 정말로 의도 한 것이라면, 언어는 "int newVal; newVal = 42;"라고 말함으로써 그것을 명시 적으로 만들어야합니다. 그렇지 않으면 newVal의 범위를 단일 케이스로 제한 할 수 있습니다.

동일한 예를 고려할 때 "const int newVal = 42;"를 사용하면 상황을 명확히 할 수 있습니다.


슬림포인트 를 강조하고 싶었습니다 . 스위치 구조는 전체적인 일류 시민 범위를 만듭니다. 따라서 추가 대괄호 쌍 없이 첫 번째 case 레이블 앞에 switch 문에서 변수를 선언 (및 초기화) 할 수 있습니다 .

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

지금까지 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();
}

이것이 괜찮다는 것이 흥미 롭습니다.

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

case 1:  
    break;
}

...하지만 이것은 아닙니다 :

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

case 1:  
    break;
}

수정이 충분히 간단하다는 것을 알지만 첫 번째 예제가 컴파일러를 괴롭히지 않는 이유를 아직 이해하지 못합니다. 앞서 언급했듯이 (2 년 전 hehe), 논리에도 불구하고 선언 은 오류의 원인이 아닙니다. 초기화가 문제입니다. 변수가 초기화되고 다른 행에서 선언되면 컴파일됩니다.


나는 이 질문에 대해 원래이 대답을 썼다 . 그러나 내가 그것을 끝내자 나는 대답이 닫혔다는 것을 알았다. 그래서 여기에 게시했는데 표준에 대한 참조를 좋아하는 사람이 도움이 될 것입니다.

문제의 원본 코드 :

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

실제로 두 가지 질문이 있습니다.

1. 왜 case라벨 뒤에 변수를 선언 할 수 있습니까?

C ++ 라벨은 다음과 같은 형식이어야하기 때문입니다.

N3337 6.1 / 1

라벨이 붙은 문구 :

...

  • 속성 지정자 -seqopt case constant-expression :statement

...

그리고에서 C++ 선언 문은 또한으로 간주 (반대 C) :

N3337 6/1 :

성명 :

...

선언문

...

2. 변수 선언을 건너 뛰고 사용할 수있는 이유는 무엇입니까?

이유 : N3337 6.7 / 3

블록으로 전송할 수는 있지만 초기화를 사용하여 선언을 우회하는 방식은 아닙니다 . 점프 프로그램 ( 에서 전송 a의 조건 경우 레이블 스위치 문은 점프 간주된다 이 점에서).

자동 저장 기간이있는 변수가 범위 내에 있지 않은 지점에서 변수에 스칼라 유형 , 사소한 기본 생성자가있는 클래스 유형 및 사소한 소멸자, cv-qualified 버전 이 없으면 범위 내에 있는 변수의 형식 이 잘못 되었습니다. 이 유형 중 하나 또는 이전 유형 중 하나의 배열이며 이니셜 라이저없이 선언됩니다 (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;

새 변수는 블록 범위에서만 decalared 할 수 있습니다. 다음과 같이 작성해야합니다.

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

물론 newVal은 중괄호 안에 범위 만 있습니다.

건배, 랄프


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).


newVal은 스위치의 전체 범위에 존재하지만 VAL 팔다리가 맞을 때만 초기화됩니다. VAL에서 코드 주위에 블록을 생성하면 괜찮을 것입니다.


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;
}

당면한 문제는 진술을 건너 뛰고 다른 곳에서 var를 사용하려고 시도했지만 선언되지 않았다는 것입니다.


익명 개체 참조 할 수없고 다음 사례로 넘어갈 수 없기 때문에 switch 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;
}

참고 URL : https://stackoverflow.com/questions/92396/why-cant-variables-be-declared-in-a-switch-statement

반응형