[c] 스위치 문 : 마지막 경우가 기본값이어야합니까?

다음 switch진술을 고려하십시오 .

switch( value )
{
  case 1:
    return 1;
  default:
    value++;
    // fall-through
  case 2:
    return value * 2;
}

이 코드는 컴파일되지만 C90 / C99에 유효합니까 (= 정의 된 동작)? 기본 사례가 마지막 사례가 아닌 코드를 본 적이 없습니다.

편집 : Jon CageKillianDS가
것처럼 : 이것은 정말 추악하고 혼란스러운 코드이며 잘 알고 있습니다. 나는 일반적인 구문 (정의되어 있습니까?)과 예상 출력에 관심이 있습니다.



답변

C99 표준은 이에 대해 명시 적이 지 않지만 모든 사실을 종합하면 완벽하게 유효합니다.

A casedefault레이블은 레이블과 동일합니다 goto. 6.8.1 레이블이있는 설명을 참조하십시오. 특히 흥미로운 것은 6.8.1.4이며 이미 언급 한 Duff의 장치를 활성화합니다.

모든 명령문 앞에는 식별자를 레이블 이름으로 선언하는 접두사가 올 수 있습니다. 레이블 자체는 제어 흐름을 변경하지 않으며,이를 통해 방해받지 않고 계속됩니다.

편집 : 스위치 내의 코드는 특별한 것이 아닙니다. if추가 점프 레이블이 있는 -statement에서와 같이 일반적인 코드 블록입니다 . 이것은 추락 행동과 이유를 설명합니다break 필요한지 .

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에 대한 호출에 도달 할 수 없습니다.

case 상수는 switch 문 내에서 고유해야합니다.

6.8.4.2.3 각 경우 라벨의 표현은 정수 상수 표현이어야하고, 동일한 스위치 문장에서 두 경우의 경우 상수 표현은 변환 후 동일한 값을 갖지 않아야한다. switch 문에는 기본 레이블이 하나 이상있을 수 있습니다.

모든 사례가 평가 된 후 다음과 같은 경우 기본 레이블로 이동합니다.

6.8.4.2.5 정수 승격은 제어 표현식에서 수행됩니다. 각 경우 레이블의 상수 표현식은 제어 표현식의 승격 된 유형으로 변환됩니다. 변환 된 값이 승격 된 제어 표현식의 값과 일치하면 제어는 일치하는 대소 문자 레이블 다음의 명령문으로 이동합니다. 그렇지 않으면 기본 레이블이 있으면 제어가 레이블이 지정된 명령문으로 이동합니다. 변환 된 대소 문자 상수 표현식이없고 기본 레이블이없는 경우 스위치 본문의 일부가 실행되지 않습니다.


답변

case 문과 default 문은 switch 문에서 임의의 순서로 발생할 수 있습니다. 기본 절은 case 문에서 상수를 일치시킬 수없는 경우 일치하는 선택적 절입니다.

좋은 예 :-

switch(5) {
  case 1:
    echo "1";
    break;
  case 2:
  default:
    echo "2, default";
    break;
  case 3;
    echo "3";
    break;
}


Outputs '2,default'

사례가 코드에서 논리적 순서로 제시되기를 원할 때 (대소 문자 1, 사례 3, 사례 2 / 기본값을 말하지 않음) 사례가 너무 길어서 전체 사례를 반복하지 않으려는 경우 매우 유용합니다. 기본값은 하단에있는 코드


답변

어떤 경우에는 유효하고 매우 유용합니다.

다음 코드를 고려하십시오.

switch(poll(fds, 1, 1000000)){
   default:
    // here goes the normal case : some events occured
   break;
   case 0:
    // here goes the timeout case
   break;
   case -1:
     // some error occurred, you have to check errno
}

요점은 위 코드가 계단식 코드보다 읽기 쉽고 효율적이라는 것 if입니다. default마지막에 넣을 수는 있지만 일반적인 경우 (여기있는 default경우) 대신 오류 사례에주의를 기울이기 때문에 의미가 없습니다 .

실제로, 그것은 좋은 예가 아닙니다 poll. 최대 몇 개의 이벤트가 발생할 수 있는지 알고 있습니다. 나의 진짜 요점은 ‘예외’와 일반적인 경우가있는 정의 된 입력 값 세트 가 있는 경우 가 있다는 것 입니다. 예외 또는 정상적인 경우를 앞에 두는 것이 더 나은 선택입니다.

소프트웨어 분야에서 나는 매우 일반적인 또 다른 경우를 생각합니다. 일부 터미널 값을 사용한 재귀. 스위치를 사용하여 표현할 수있는 경우 default재귀 호출과 구별 요소 (개별 사례)가 포함 된 일반적인 값이 터미널 값이됩니다. 일반적으로 터미널 값에 초점을 맞출 필요가 없습니다.

또 다른 이유는 사례의 순서가 컴파일 된 코드 동작을 변경할 수 있으며 이는 성능에 중요합니다. 대부분의 컴파일러는 코드가 스위치에 나타나는 순서대로 컴파일 된 어셈블리 코드를 생성합니다. 첫 번째 경우는 다른 경우와 매우 다릅니다. 첫 번째 경우를 제외한 모든 경우에는 점프가 발생하고 프로세서 파이프 라인이 비게됩니다. 기본적으로 스위치에서 첫 번째로 나타나는 사례를 실행하는 분기 예측기처럼 이해할 수 있습니다. 다른 경우보다 훨씬 일반적인 경우 첫 번째 사례로 두어야 할 이유가 있습니다.

주석을 읽는 것이 코드 최적화에 대한 인텔 컴파일러 Branch Loop 재구성 을 읽은 후에 원래 포스터가 그 질문을 한 구체적인 이유 입니다.

그러면 코드 가독성과 코드 성능간에 중재가됩니다. 아마도 사건이 먼저 나타나는 이유를 미래 독자에게 설명하기 위해 의견을 작성하는 것이 좋습니다.


답변

예, 이것은 유효하며 일부 상황에서는 유용합니다. 일반적으로 필요하지 않은 경우 필요하지 않습니다.


답변

switch 문에 정의 된 순서가 없습니다. 사례를 레이블과 같은 명명 된 레이블과 같은 것으로 볼 수 있습니다 goto. 사람들이 여기에서 생각하는 것과 달리, 값 2의 경우 기본 레이블로 이동하지 않습니다. 고전적인 예를 들어 설명하기 위해 Duff의 장치switch/case 는 C 에서 극단의 포스터 자식입니다 .

send(to, from, count)
register short *to, *from;
register count;
{
  register 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 문의 끝 이외의 위치에 ‘default’를 두는 것이 적절하다고 생각되는 한 가지 시나리오는 상태가 유효하지 않은 상태 인 경우 머신을 재설정하고 초기 상태 인 것처럼 진행해야하는 상태 머신입니다. 예를 들면 다음과 같습니다.

스위치 (widget_state)
{
  기본값 : / * 레일에서 떨어짐-재설정하고 계속 * /
    widget_state = WIDGET_START;
    /* 실패로 끝나다 */
  사례 WIDGET_START :
    ...
    단절;
  사례 WIDGET_WHATEVER :
    ...
    단절;
}

유효하지 않은 상태가 기계를 재설정하지 않아야하지만 유효하지 않은 상태로 쉽게 식별 할 수있는 경우 대체 배치 :

스위치 (widget_state) { 사례 WIDGET_IDLE : widget_ready = 0; widget_hardware_off (); 단절; 사례 WIDGET_START : ... 단절; 사례 WIDGET_WHATEVER : ... 단절; 기본: widget_state = WIDGET_INVALID_STATE; /* 실패로 끝나다 */ 사례 WIDGET_INVALID_STATE : widget_ready = 0; widget_hardware_off (); ... "안전한"조건을 설정하기 위해 필요한 다른 조치를 취하십시오. }

그런 다음 다른 곳의 코드에서 (widget_state == WIDGET_INVALID_STATE)를 확인하고 오류보고 또는 상태 재설정 동작이 적절하다고 생각되는 것을 제공 할 수 있습니다. 예를 들어, 상태 표시 줄 코드에 오류 아이콘이 표시 될 수 있으며 대부분의 유휴 상태가 아닌 상태에서 비활성화 된 “위젯 시작”메뉴 옵션은 WIDGET_INVALID_STATE 및 WIDGET_IDLE에 대해 활성화 될 수 있습니다.


답변

다른 예를 들어 보자 : “default”가 예상치 못한 경우이고 오류를 기록하지만 합리적인 작업을 수행하려는 경우에 유용 할 수 있습니다. 내 코드 중 일부의 예 :

  switch (style)
  {
  default:
    MSPUB_DEBUG_MSG(("Couldn't match dash style, using solid line.\n"));
  case SOLID:
    return Dash(0, RECT_DOT);
  case DASH_SYS:
  {
    Dash ret(shapeLineWidth, dotStyle);
    ret.m_dots.push_back(Dot(1, 3 * shapeLineWidth));
    return ret;
  }
  // more cases follow
  }