Programming

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

procodes 2020. 5. 29. 23:38
반응형

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


다음 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재귀 호출과 구별 요소 (개별 사례)가 포함 된 일반적인 값이 터미널 값이됩니다. 일반적으로 터미널 값에 초점을 맞출 필요가 없습니다.

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

주석을 읽는 것은 코드 최적화에 대한 인텔 컴파일러 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 (); ... "안전한"조건을 설정하기 위해 필요한 다른 조치를 취하십시오 }

Code elsewhere may then check for (widget_state == WIDGET_INVALID_STATE) and provide whatever error-reporting or state-reset behavior seems appropriate. For example, the status-bar code could show an error icon, and the "start widget" menu option which is disabled in most non-idle states could be enabled for WIDGET_INVALID_STATE as well as WIDGET_IDLE.


Chiming in with another example: This can be useful if "default" is an unexpected case, and you want to log the error but also do something sensible. Example from some of my own code:

  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
  }

There are cases when you are converting ENUM to a string or converting string to enum in case where you are writing/reading to/from a file.

You sometimes need to make one of the values default to cover errors made by manually editing files.

switch(textureMode)
{
case ModeTiled:
default:
    // write to a file "tiled"
    break;

case ModeStretched:
    // write to a file "stretched"
    break;
}

The "default" condition can be anyplace within the switch that a case clause can exist. It is not required to be the last clause. I have seen code that put the default as the first clause. The "case 2:" gets executed normally, even though the default clause is above it.

As a test, I put the sample code in a function, called test(int value){} and ran:

  printf("0=%d\n", test(0));
  printf("1=%d\n", test(1));
  printf("2=%d\n", test(2));
  printf("3=%d\n", test(3));
  printf("4=%d\n", test(4));

The output is:

0=2
1=1
2=4
3=8
4=10

It's valid, but rather nasty. I would suggest it's generally bad to allow fall-throughs as it can lead to some very messy spaghetti code.

It's almost certainly better to break these cases up into several switch statements or smaller functions.

[edit] @Tristopia: Your example:

Example from UCS-2 to UTF-8 conversion 

r is the destination array, 
wc is the input wchar_t  

switch(utf8_length) 
{ 
    /* Note: code falls through cases! */ 
    case 3: r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
    case 2: r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 
    case 1: r[0] = wc;
}

would be clearer as to it's intention (I think) if it were written like this:

if( utf8_length >= 1 )
{
    r[0] = wc;

    if( utf8_length >= 2 )
    {
        r[1] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x0c0; 

        if( utf8_length == 3 )
        {
            r[2] = 0x80 | (wc & 0x3f); wc >>= 6; wc |= 0x800; 
        }
    }
}   

[edit2] @Tristopia: Your second example is probably the cleanest example of a good use for follow-through:

for(i=0; s[i]; i++)
{
    switch(s[i])
    {
    case '"': 
    case '\'': 
    case '\\': 
        d[dlen++] = '\\'; 
        /* fall through */ 
    default: 
        d[dlen++] = s[i]; 
    } 
}

..but personally I would split the comment recognition into it's own function:

bool isComment(char charInQuestion)
{   
    bool charIsComment = false;
    switch(charInQuestion)
    {
    case '"': 
    case '\'': 
    case '\\': 
        charIsComment = true; 
    default: 
        charIsComment = false; 
    } 
    return charIsComment;
}

for(i=0; s[i]; i++)
{
    if( isComment(s[i]) )
    {
        d[dlen++] = '\\'; 
    }
    d[dlen++] = s[i]; 
}

참고URL : https://stackoverflow.com/questions/3110088/switch-statement-must-default-be-the-last-case

반응형