switch 문이 왜 휴식을 취하도록 설계 되었습니까?


139

간단한 switch 문이 주어지면

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

사례 2에 break 문이 없으면 사례 3에 대한 코드 내에서 실행이 계속됨을 의미합니다. 이것은 우연이 아닙니다. 그런 식으로 설계되었습니다. 이 결정이 내려진 이유는 무엇입니까? 이것이 블록에 대한 자동 중단 의미를 갖는 것과 비교하여 어떤 이점이 있습니까? 근거는 무엇입니까?

답변:


150

많은 답변 이 진술 을 요구하는 이유 로서 넘어 질 수있는 능력에 초점을 맞추는 것 같습니다 break.

나는 C가 설계되었을 때 이러한 구조가 어떻게 사용되는지에 대한 경험이 거의 없기 때문에 단순히 실수라고 생각합니다.

Peter Van der Linden은 자신의 저서 "Expert C Programming"에서 다음과 같이 설명합니다.

Sun C 컴파일러 소스를 분석하여 기본 폴 스루 사용 빈도를 확인했습니다. Sun ANSI C 컴파일러 프론트 엔드에는 244 개의 switch 문이 있으며 각 문에는 평균 7 개의 사례가 있습니다. 이 모든 경우의 3 %만으로 넘어집니다.

다시 말해서, 정상적인 스위치 동작은 97 %의 시간에 잘못 되었습니다. 이는 컴파일러에만있는 것이 아닙니다. 반대로이 분석에서 사용 된 추락은 종종 다른 소프트웨어보다 컴파일러에서 더 자주 발생하는 상황 (예 : 하나 또는 두 개의 피연산자를 가질 수있는 연산자를 컴파일 할 때)이었습니다. :

switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

    case 1: process_operand( operator->operand_1);
    break;
}

케이스 폴 스루는 결함으로 널리 인식되어 있으며 위에서 보인 것처럼 보풀을 알려주는 특별한 주석 규칙도 있습니다. "이는 실제로 폴 스루가 필요한 경우의 3 % 중 하나입니다."

C #이 각 사례 블록의 끝에 명시 적 점프 명령문을 요구하는 것이 좋습니다 (단일 문장 블록이있는 한 여러 사례 레이블을 쌓을 수는 있음). C #에서는 한 사례가 다른 사례로 넘어갈 수 있습니다 goto.를 사용하여 다음 사례로 넘어 가서 명시 적으로 넘어야합니다 .

Java가 C 의미를 벗어날 기회를 얻지 못한 것은 너무 나쁩니다.


실제로, 나는 그들이 구현의 단순성을 위해 갔다고 생각합니다. 다른 언어는 효율성을 희생시키면서보다 복잡한 경우 (범위, 다중 값, 문자열 등)를 지원했습니다.
— PhiLho

자바는 아마도 습관을 깨고 혼란을 퍼 뜨리고 싶지 않았을 것이다. 다른 동작을 위해서는 서로 다른 의미를 사용해야합니다. 자바 디자이너들은 어쨌든 C에서 벗어날 수있는 많은 기회를 잃었다.
— PhiLho

@PhiLho-아마도 "구현의 단순성"을 통해 진실에 가장 가깝다고 생각합니다.
— Michael Burr

GOTO를 통한 명시 적 점프를 사용하는 경우 일련의 IF 문을 사용하는 것만 큼 생산적이지 않습니까?
— DevinB

3
심지어 파스칼조차도 스위치를 중단없이 구현합니다. C 컴파일러-코더가 그것에 대해 어떻게 생각하지 않을 수 있을까 @@
— Chan Le

30

여러 가지면에서 c는 표준 어셈블리 관용구에 대한 깔끔한 인터페이스입니다. 점프 테이블 구동 흐름 제어를 작성할 때, 프로그래머는 "제어 구조"를 통해 넘어 지거나 뛰어 내릴 수 있습니다.

그래서 c도 같은 일을합니다.


1
나는 많은 사람들이 그런 말을한다는 것을 알고 있지만 그것이 완전한 그림은 아니라고 생각합니다. C는 종종 어셈블리 반 패턴에 대한 추악한 인터페이스입니다. 1. 추악한 표현 : 표현 대신 진술. "선언은 사용을 반영합니다." 영광으로 복사 - 붙여 모듈화 및 휴대 장치. 시스템 방식이 너무 복잡합니다. 버그를 장려 합니다. . (다음 댓글에 계속)NULL
— Harrison

2. 반 패턴 : 두 가지 기본 데이터 표현 관용구 중 하나 인 "깨끗한"태그가 붙은 조합을 제공하지 않습니다. (Knuth, vol 1, ch 2) 대신, 비 표현 적 핵인 "태그가 붙지 않은 조합". 이러한 선택은 사람들이 수십 년 동안 데이터를 생각하는 방식을 방해했습니다. 그리고 종료 NUL문자열은 최악의 생각입니다.
— 해리슨

@HarrisonKlaperman : 문자열 저장 방법이 완벽하지 않습니다. 문자열을 허용하는 루틴이 최대 길이 매개 변수를 승인 한 경우 널 종료 문자열과 관련된 대부분의 문제점은 발생하지 않으며 버퍼 크기 매개 변수를 승인하지 않고 길이 표시 문자열을 고정 크기 버퍼에 저장하는 루틴에서 발생합니다. . 케이스가 단순한 레이블 인 switch 문의 디자인은 현대의 눈에는 이상하게 보일 수 있지만 Fortran DO루프 의 디자인보다 나쁘지 않습니다 .
— supercat

어셈블리에 점프 테이블을 작성하기로 결정하면 case 값을 가져 와서 마술처럼 점프 테이블 아래 첨자로 바꾸고 해당 위치로 이동하여 코드를 실행합니다. 그 후 나는 다음 사건으로 뛰어 들지 않을 것입니다. 나는 모든 경우의 출구 인 균일 한 주소로 뛰어들 것입니다. 다음 사건의 본문에 뛰어 들거나 넘어 질 것이라는 생각은 어리석은 일입니다. 해당 패턴에 대한 사용 사례가 없습니다.
— EvilTeach

2
요즘 사람들은 발을 쏘지 못하게하는 언어와 언어 기능을 청소하고 스스로 보호하는 데 더 많이 사용되지만, 이것은 바이트가 비싼 지역에서 다시 빛나고 있습니다 (C는 1970 년 이전에 다시 시작했습니다). 코드가 1024 바이트 내에 맞아야하는 경우 코드 조각을 재사용해야 할 위험이 큽니다. 동일한 엔드를 공유하는 다른 진입 점에서 시작하여 코드를 재사용하는 것이이를 달성하는 한 가지 메커니즘입니다.
— rpy

23

더프의 장치를 구현하려면 분명히 :

dsend(to, from, count)
char *to, *from;
int 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);
    }
}

3
나는 더프의 장치를 좋아합니다. 매우 우아하고 사악합니다.
— dicroce

7
나중에 SO에 switch 문이있을 때마다 표시되어야합니까?
— billjamesdev

두 개의 닫는 중괄호 ;-)를 놓쳤습니다.
— Toon Krijthe

18

사례가 암시 적으로 중단되도록 설계된 경우에는 실패 할 수 없었습니다.

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

모든 경우에 스위치가 암시 적으로 발생하도록 설계된 경우 스위치를 선택할 수 없습니다. 스위치 케이스 구조는보다 유연하게 설계되었습니다.


12
암시 적으로 끊어 지지만 "fallthrough"키워드가있는 스위치를 이미지화 할 수 있습니다. 어색하지만 실행 가능합니다.
— dmckee --- 전 사회자 고양이

어떻게 더 좋을까요? "문당 코드 블록"보다 이런 방식으로 더 자주 작동하는 case 문을 상상합니다. 그것은 if / then / else 블록입니다.
— billjamesdev

이 질문에 덧붙여서, 각 경우 블록의 스코프 인클로저 {}는 "while"문의 스타일처럼 보이기 때문에 혼란을 더하고 있습니다.
— Matthew Smith

1
@Bill : 더 나쁘다고 생각하지만 Mike B가 제기 한 불만을 해결할 것입니다.
— dmckee --- 전 운영자 고양이 새끼

1
간결함을 위해 중괄호를 생략하고 여러 문장이 있어야합니다. 사례가 정확히 동일한 경우에만 오류를 사용해야한다는 데 동의합니다. 폴 스루를 사용하여 이전 사례를 기반으로 사례를 구축하면 지옥으로 혼동됩니다.
— Bill the Lizard

15

switch 문에있는 case 문은 단순히 레이블입니다.

값을 켤 때 switch 문은 본질적으로 goto를 수행합니다. 일치하는 값으로 레이블을.

즉, 다음 레이블 아래의 코드를 통과하지 않으려면 나누기가 필요합니다.

이것이 이런 식으로 구현 된 이유에 대해서는 switch 문의 폴-스루 특성이 일부 시나리오에서 유용 할 수 있습니다. 예를 들면 다음과 같습니다.

case optionA:
    // optionA needs to do its own thing, and also B's thing.
    // Fall-through to optionB afterwards.
    // Its behaviour is a superset of B's.
case optionB:
    // optionB needs to do its own thing
    // Its behaviour is a subset of A's.
    break;
case optionC:
    // optionC is quite independent so it does its own thing.
    break;

2
두 가지 큰 문제가 있습니다 : 1) break필요한 곳을 잊어 버렸습니다 . 2) 사례 명세서의 순서가 변경되면 오류가 발생하여 잘못된 사례가 실행될 수 있습니다. 따라서 C #의 처리가 훨씬 우수하다는 것을 알았습니다 ( goto case빈 사례 레이블을 제외하고는 추락에 대해 명시 적임).
— Daniel Rose

@DanielRose : 1) breakC #에서도 잊을 수 있는 방법이 있습니다. 가장 간단한 방법 case은 아무것도 하고 싶지 않지만 추가하는 것을 잊었을 때입니다 break(아마 설명 주석으로 묶여 있거나 다른 작업) : 실행은 case아래로 넘어갑니다 . 2) 격려 goto case는 구조화되지 않은 "스파게티"코딩을 장려합니다 case A: ... goto case B; case B: ... ; goto case A;. 특히 케이스가 파일에서 분리되어 조합되어 이해하기 어려운 경우 우발적 인주기 ( ) 로 끝날 수 있습니다 . C ++에서 폴 스루는 현지화되어 있습니다.
— Tony Delroy 2016 년

8

다음과 같은 것을 허용하려면 :

switch(foo) {
case 1:
    /* stuff for case 1 only */
    if (0) {
case 2:
    /* stuff for case 2 only */
    }
    /* stuff for cases 1 and 2 */
case 3:
    /* stuff for cases 1, 2, and 3 */
}

case키워드를 goto레이블 로 생각하면 더 자연스럽게 나타납니다.


8
있음은 if(0)제 1 케이스의 단부에 악 대물 코드를 당황하게하는 경우에만 사용되어야한다.
— Daniel Rose

11
이 모든 대답은 악한 일의 운동이라고 생각합니다. :-)
— R .. GitHub 중지 지원 얼음

3

여러 사례에서 동일한 코드 (또는 동일한 코드를 순서대로)를 실행해야하는 경우 코드 중복을 제거합니다.

어셈블리 언어 수준에서는 각 언어 사이의 구분 여부에 관계가 없으므로 어쨌든 추락에 대한 오버 헤드가 전혀 없습니다. 따라서 특정 경우에 큰 이점을 제공하므로 허용하지 않는 이유는 무엇입니까?


2

벡터에 값을 구조체에 할당하는 경우가 발생했습니다. 데이터 벡터가 구조체의 데이터 멤버 수보다 짧으면 나머지 멤버는 그대로 유지되도록해야했습니다. 그들의 기본값. 이 경우 생략 break이 매우 유용했습니다.

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}

0

여기에 많은 사람들이 지정한 것처럼 단일 코드 블록이 여러 경우에 작동하도록 허용합니다. 이것은 해야 예에서 지정한 "사례 당 코드 블록"보다 switch 문에서 더 일반적으로 발생 합니다.

폴 스루없이 사례 당 코드 블록이있는 경우 if-elseif-else 블록을 사용하는 것이 더 적절 해 보일 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.