스위치 케이스에서 유효하지만 쓸모없는 구문?


207

약간의 오타를 통해 실수 로이 구성을 찾았습니다.

int main(void) {
    char foo = 'c';

    switch(foo)
    {
        printf("Cant Touch This\n");   // This line is Unreachable

        case 'a': printf("A\n"); break;
        case 'b': printf("B\n"); break;
        case 'c': printf("C\n"); break;
        case 'd': printf("D\n"); break;
    }

    return 0;
}

것 같다 printf의 상단에 switch문이 완전히 도달도 유효하지만입니다.

도달 할 수없는 코드에 대한 경고조차없이 깨끗한 컴파일을 얻었지만 이것은 의미가없는 것 같습니다.

컴파일러가이 코드를 도달 할 수없는 코드로 표시해야합니까?
이것이 어떤 목적에도 도움이됩니까?


51
GCC에는이를위한 특별한 플래그가 있습니다. 그것은이다-Wswitch-unreachable
엘리 Sadoff

57
"이것은 어떤 목적에도 도움이됩니까?" 글쎄, 당신은 goto도달 할 수없는 부분에 출입 할 수 있으며, 다양한 해킹에 유용 할 수 있습니다.
HolyBlackCat

12
@HolyBlackCat 도달 할 수없는 모든 코드에 해당되지 않습니까?
Eli Sadoff

28
@EliSadoff 실제로. 나는 그것이 특별한 목적을 달성 하지 못한다고 생각합니다 . 금지 할 이유가 없기 때문에 허용됩니다. 결국 여러 레이블이 switch있는 조건부 goto입니다. goto 레이블로 채워진 규칙적인 코드 블록에서와 마찬가지로 본문에 거의 동일한 제한이 있습니다.
HolyBlackCat

16
@MooingDuck의 예제는 더프 장치의 변형 ( en.wikipedia.org/wiki/Duff's_device )
Michael Anderson

답변:


226

아마도 가장 유용하지는 않지만 완전히 쓸모없는 것은 아닙니다 . 이를 사용하여 switch범위 내에서 사용 가능한 로컬 변수를 선언 할 수 있습니다 .

switch (foo)
{
    int i;
case 0:
    i = 0;
    //....
case 1:
    i = 1;
    //....
}

표준 ( N1579 6.8.4.2/7)에는 다음 샘플이 있습니다.

예 인공 프로그램 조각에서

switch (expr)
{
    int i = 4;
    f(i);
case 0:
    i = 17;
    /* falls through into default code */
default:
    printf("%d\n", i);
}

식별자가 i자동 저장 기간 (블록 내)으로 존재하지만 초기화되지 않은 객체 이므로 제어 표현식에 0이 아닌 값이 있으면 printf함수에 대한 호출이 결정 되지 않은 값에 액세스합니다. 마찬가지로 함수 호출에 f도달 할 수 없습니다.

PS BTW, 샘플이 유효한 C ++ 코드가 아닙니다. 이 경우 ( N4140 6.7/3, 강조 광산) :

변수가 스칼라 유형 , 사소한 기본 생성자가있는 클래스 유형 및 사소한 소멸자를 갖지 않는 한, 자동 저장 기간이있는 변수가 범위 내에 있지 않은 지점에서 범위 내에있는 지점으로 90 이 점프하는 프로그램 은 , 이러한 유형 중 하나의 cv 규정 버전 또는 이전 유형 중 하나의 배열이며 초기화 프로그램 (8.5) 없이 선언됩니다 .


90) switch진술 의 조건 에서 사례 라벨로의 이전은이 점에서 점프로 간주된다.

교체 그래서 int i = 4;으로 int i;만든다 그것은 유효한 C ++.


3
"...하지만 초기화되지 않았습니다 ..." i4로 초기화 된 것 같습니다 . 무엇을 놓치고 있습니까?
yano

7
변수가 static인 경우 변수는 0으로 초기화되므로 안전한 사용법도 있습니다.
Leushenko

23
@yano 우리는 항상 i = 4;초기화를 뛰어 넘기 때문에 결코 일어나지 않습니다.
AlexD

12
물론 이죠! 질문의 요점은 ... 이 어리 석음을 삭제하려는 욕구가 강하다
yano

1
좋은! 때로는 내부에 임시 변수가 필요 case했고 항상 서로 다른 이름을 사용 case하거나 스위치 외부에서 정의해야했습니다.
SJuan76

98

이것이 어떤 목적에도 도움이됩니까?

예. 명령문 대신 첫 번째 레이블 앞에 선언을 넣으면 완벽하게 이해할 수 있습니다.

switch (a) {
  int i;
case 0:
  i = f(); g(); h(i);
  break;
case 1:
  i = g(); f(); h(i);
  break;
}

선언 및 명령문에 대한 규칙은 일반적으로 블록에 대해 공유되므로 명령문에 대한 명령문도 허용하는 규칙과 동일합니다.


또한 첫 번째 명령문이 루프 구문 인 경우 루프 본문에 대소 문자 레이블이 표시 될 수 있습니다.

switch (i) {
  for (;;) {
    f();
  case 1:
    g();
  case 2:
    if (h()) break;
  }
}

더 읽기 쉬운 코드 작성 방법이 있지만 완벽하게 유효하고 f()호출에 도달 하는 경우 이와 같은 코드를 작성하지 마십시오 .


@MatthieuM Duff의 장치에는 루프 내부에 케이스 레이블이 있지만 루프 앞의 케이스 레이블로 시작합니다.

2
재미있는 예제로 찬성 투표 해야하는지, 실제 프로그램 에서이 글을 쓰는 미친 광경에 대해 찬성 투표 해야하는지 확실하지 않습니다. :). 심연으로 뛰어 들어 한 조각으로 돌아온 것을 축하합니다.
Liviu T.

@ChemicalEngineer : Duff의 장치에서와 같이 코드가 루프의 일부인 경우 { /*code*/ switch(x) { } }더 깨끗해 보일 수 있지만 잘못되었습니다 .
벤 Voigt

39

Duff 's Device 라는 유명한 사용법이 있습니다.

int n = (count+3)/4;
switch (count % 4) {
  do {
    case 0: *to = *from++;
    case 3: *to = *from++;
    case 2: *to = *from++;
    case 1: *to = *from++;
  } while (--n > 0);
}

여기에 우리가 가리키는 버퍼를 복사 from가 가리키는 버퍼에 to. count데이터 인스턴스를 복사 합니다.

do{}while()문은 전에 먼저 시작 case레이블과 case레이블이에 포함된다 do{}while().

이렇게하면 do{}while()루프 끝에서 발생하는 조건 분기의 수가 약 4 배로 줄어 듭니다 (이 예에서는 상수를 원하는 값으로 조정할 수 있음).

이제 옵티마이 저가 때때로이를 위해 (특히 스트리밍 / 벡터화 된 명령어를 최적화하는 경우)이를 수행 할 수 있지만 프로파일 가이드 최적화가 없으면 루프가 클 것으로 예상되는지 알 수 없습니다.

일반적으로 변수 선언이 발생하여 모든 경우에 사용될 수 있지만 스위치가 끝난 후에는 범위를 벗어납니다. (초기화는 건너 뜁니다.)

또한 스위치 별이 아닌 제어 흐름을 사용하면 위에서 설명한 것처럼 또는 스위치 블록의 해당 섹션으로 이동할 수 있습니다 goto.


2
물론, 이것은 여전히 순서로, 첫 번째 경우 위의 문장을 허용하지 않고 가능한 것 do {case 0:사정, 처음에 점프 대상을 배치하고자하는 것은 모두 *to = *from++;.
벤 Voigt

1
@ BenVoigt 나는 퍼팅 do {이 더 읽기 쉽다고 주장합니다 . 예. Duff의 장치에 대한 가독성에 대한 논쟁은 어리 석고 무의미하며 간단한 방법입니다.
Fund Monica의 소송

1
@ QPaysTaxes C에서 Simon Tatham의 Coroutines를 확인해야합니다 . 아니면 아닐 수도 있습니다.
Jonas Schäfer

@ JonasSchäfer 흥미롭게도, 그것은 기본적으로 C ++ 20 코 루틴이 당신을 위해 할 것입니다.
Yakk-Adam Nevraumont

15

Linux에서 gcc를 사용한다고 가정하면 4.4 이하 버전을 사용하는 경우 경고 메시지가 표시됩니다.

gcc 4.4 이후 에는 -Wunreachable-code 옵션 이 제거되었습니다 .


문제를 직접 경험하면 항상 도움이됩니다!
16tons

@JonathanLeffler : 선택한 특정 최적화 단계에 영향을받는 gcc 경고의 일반적인 문제는 여전히 사실이며, 사용자 경험이 좋지 않습니다. 깔끔한 디버그 빌드와 실패한 릴리스 빌드가있는 것은 정말 성가신 일입니다 : /
Matthieu M.

@MatthieuM .: 의미 론적 분석보다는 문법적으로 관련된 경우 (예 : 코드가 두 가지 분기에서 모두 리턴되는 "if"를 따른다)와 같은 경우에 이러한 경고를 감지하기가 매우 쉬울 것 같습니다. 경고. 반면에 디버그 빌드에없는 릴리스 빌드에서 오류 또는 경고가있는 경우가 있습니다 (아무것도없는 경우 디버깅을 위해 해킹을 해킹 해야하는 곳에서) 해제).
supercat

1
@MatthieuM .: 특정 변수가 코드의 어느 지점에서 항상 거짓임을 발견하기 위해 의미 론적 의미 론적 분석이 필요한 경우, 해당 변수가 참인 조건부 코드는 그러한 분석이 수행 된 경우에만 도달 할 수없는 것으로 발견됩니다. 다른 한편으로, 나는 그러한 코드가 구문 적으로 도달 할 수없는 코드에 대한 경고와는 다르게 도달 할 수 없다는 것을 고려할 것 입니다. 일부 프로젝트 구성에서는 다양한 조건이 가능하지만 다른 프로젝트에서는 불가능하다는 것이 완전히 정상적 이어야하기 때문 입니다. 프로그래머에게 도움이 될 수도 있습니다.
supercat

1
... 일부 구성이 다른 구성보다 더 큰 코드를 생성하는 이유를 알기 위해 (예 : 컴파일러가 어떤 구성에서는 불가능한 것으로 간주 할 수 있지만 다른 구성에서는 그렇지 않음), 최적화 할 수있는 코드에 "잘못된"항목이 있다는 의미는 아닙니다. 몇 가지 구성으로 패션.
supercat

11

변수 선언뿐만 아니라 고급 점프도 가능합니다. 스파게티 코드를 사용하지 않는 경우에만 잘 활용할 수 있습니다.

int main()
{
    int i = 1;
    switch(i)
    {
        nocase:
        printf("no case\n");

        case 0: printf("0\n"); break;
        case 1: printf("1\n"); goto nocase;
    }
    return 0;
}

인쇄물

1
no case
0 /* Notice how "0" prints even though i = 1 */

switch-case는 가장 빠른 제어 흐름 절 중 하나입니다. 따라서 프로그래머에게 매우 유연해야하며 때로는 이와 같은 경우가 포함됩니다.


그리고 사이의 차이가 무엇인가 nocase:와는 default:?
i486

@ i486 i=4트리거되지 않을 때 nocase.
Sanchke Dellowar

@SanchkeDellowar 그게 내가 의미하는 바입니다.
njzk2

왜 단순히 사례 0 앞에 사례 1을 넣고 대체를 사용하는 대신 도대체 그렇게해야합니까?
Jonas Schäfer

@JonasWielicki이 목표에서는 그렇게 할 수 있습니다. 그러나이 코드는 수행 할 수있는 작업의 표시 사례 일뿐입니다.
Sanchke Dellowar

11

switch명령문 내의 코드 또는 case *:레이블이이 코드 내의 위치 에 대한 구조적 제한은 거의 없습니다 . * 이것은 더프 장치 와 같은 프로그래밍 트릭을 가능하게합니다. 하나의 구현은 다음과 같습니다.

int n = ...;
int iterations = n/8;
switch(n%8) {
    while(iterations--) {
        sum += *ptr++;
        case 7: sum += *ptr++;
        case 6: sum += *ptr++;
        case 5: sum += *ptr++;
        case 4: sum += *ptr++;
        case 3: sum += *ptr++;
        case 2: sum += *ptr++;
        case 1: sum += *ptr++;
        case 0: ;
    }
}

보시다시피 레이블 switch(n%8) {case 7:레이블 사이의 코드에 도달 할 수 있습니다.


* 으로 supercat 고맙게도 코멘트 지적 : C99,도 때문에 goto나 라벨이 (그것을 할 수 case *:VLA 선언을 포함하는 선언의 범위 내에서 나타날 수 라벨 없음). 따라서 레이블 배치에 구조적 제한 이 없다고 말하는 것은 올바르지 않습니다 case *:. 그러나 더프의 장치는 C99 표준보다 오래되었으며 VLA에 의존하지 않습니다. 그럼에도 불구하고, 나는 이것 때문에 첫 문장에 "가상"을 삽입해야한다는 느낌이 들었다.


가변 길이 어레이의 추가로 인해 이와 관련된 구조적 제한이 부과되었습니다.
supercat

@supercat 어떤 종류의 제한이 있습니까?
cmaster-monica reinstate

1
가변적으로 선언 된 객체 또는 유형의 범위 내에 는 goto또는 switch/case/default레이블 도 표시 되지 않습니다 . 이는 블록에 가변 길이 배열 객체 또는 유형의 선언이 포함 된 경우 레이블이 해당 선언보다 우선해야 함을 의미합니다. 표준에는 혼란스러운 문장이 있는데, 어떤 경우에는 VLA 선언의 범위가 switch 문 전체로 확장된다고 제안합니다. 그것에 대한 내 질문 은 stackoverflow.com/questions/41752072/… 를 참조하십시오 .
supercat

@ supercat : 당신은 그 말을 오해했습니다 (질문은 당신이 질문을 삭제 한 이유입니다). VLA가 정의 될 수있는 범위에 대한 요구 사항을 부과합니다. 해당 범위를 확장하지 않고 특정 VLA 정의를 유효하지 않게합니다.
Keith Thompson

@ KeithThompson : 네, 오해했습니다. 각주에서 현재 시제를 이상하게 사용하면 상황이 혼란스러워졌으며이 개념은 금지로 더 잘 표현 될 수 있다고 생각합니다. "본문에 VLA 선언이 본문에 포함 된 스위치 문에는 스위치 또는 케이스 레이블이 포함되지 않아야합니다 해당 VLA 선언의 범위 "를 참조하십시오.
supercat

10

경고를 생성 하는 데 필요한 gcc옵션 과 관련된 답변을 얻었습니다 -Wswitch-unreachable.이 답변은 유용성 / 가치 부분 을 자세히 설명하는 것 입니다.

C11§6.8.4.2 장에서 똑바로 인용 ( 내 강조 )

switch (expr)
{
int i = 4;
f(i);
case 0:
i = 17;
/* falls through into default code */
default:
printf("%d\n", i);
}

식별자가 i자동 저장 기간 (블록 내)으로 존재하지만 초기화되지 않는 객체 이므로 제어 표현식에 0이 아닌 값이 있으면 printf 함수에 대한 호출이 결정 되지 않은 값에 액세스합니다. 마찬가지로 함수 호출에 f도달 할 수 없습니다.

매우 설명이 필요합니다. 이를 사용하여 switch명령문 범위 내에서만 사용 가능한 로컬 범위 변수를 정의 할 수 있습니다 .


9

"루프 반"을 구현하는 것이 가능하지만 최선의 방법은 아닙니다.

char password[100];
switch(0) do
{
  printf("Invalid password, try again.\n");
default:
  read_password(password, sizeof(password));
} while (!is_valid_password(password));

@RichardII 그것은 말장난입니까? 설명 해주십시오.
Dancia

1
@Dancia 그는 이것이 분명히 이와 같은 일을하는 가장 좋은 방법 은 아니라고 말할 수 있으며,“그렇지 않을 수도있다”는 말은 과소 평가 된 것입니다.
Fund Monica의 소송
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.