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.