Clang / LLVM이 열거 된 모든 사례를 다루는 switch 문에서 기본값 사용에 대해 경고하는 이유는 무엇입니까?


34

다음 enum 및 switch 문을 고려하십시오.

typedef enum {
    MaskValueUno,
    MaskValueDos
} testingMask;

void myFunction(testingMask theMask) {
    switch (theMask) {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
        default: {} //deal with an unexpected or uninitialized value
    }
};

나는 Objective-C 프로그래머이지만 더 많은 사람들을 위해 이것을 순수한 C로 작성했습니다.

-Weverything이있는 Clang / LLVM 4.1은 기본 줄에 경고합니다.

모든 열거 값을 포함하는 스위치의 기본 레이블

이제 이것이 왜 존재하는지 알 수 있습니다. 완벽한 세계에서 인수에 입력하는 유일한 값 theMask은 열거 형에있을 것이므로 기본값이 필요하지 않습니다. 그러나 일부 해킹이 발생하여 초기화되지 않은 정수를 내 아름다운 기능에 던지면 어떻게됩니까? 내 기능은 라이브러리의 드롭으로 제공되며 거기에 들어갈 수있는 것을 제어 할 수 없습니다. 사용 default은 이것을 처리하는 매우 깔끔한 방법입니다.

왜 LLVM 신들은이 행동이 그들의 지옥의 장치에 맞지 않다고 생각합니까? 인수를 확인하기 위해 if 문으로이 작업을 수행해야합니까?


1
내가 말해야 할 이유는-저희가 더 나은 프로그래머가되기 위해서입니다. NSHipster는 다음 과 같이 말합니다"Pro tip: Try setting the -Weverything flag and checking the “Treat Warnings as Errors” box your build settings. This turns on Hard Mode in Xcode." .
Swizzlr

2
-Weverything유용 할 수 있지만 코드를 처리하기에는 코드를 너무 많이 변경하는 데주의하십시오. 이러한 경고 중 일부는 무가치 할뿐만 아니라 비생산적이며 가장 꺼져 있습니다. (실제로 이것이 유스 케이스입니다 -Weverything. 시작하여 이해가되지 않는 것을 끄십시오.)
Steven Fisher

후손에 대한 경고 메시지를 조금 더 확장 할 수 있습니까? 그들보다 일반적으로 더 많은 것이 있습니다.
Steven Fisher

답변을 읽은 후에도 여전히 초기 솔루션을 선호합니다. 기본 진술의 사용 IMHO는 답변에 제공된 대안보다 낫습니다. 작은 참고 사항 : 답변은 실제로 유익하고 유익하며 문제를 해결하는 멋진 방법을 보여줍니다.
bbaja42

@StevenFisher 전체 경고였습니다. 그리고 Killian이 나중에 열거 형을 수정하면 유효한 값이 기본 구현으로 전달 될 가능성이 있음을 지적했습니다. 좋은 디자인 패턴 인 것 같습니다 (이런 경우).
Swizzlr

답변:


31

다음은 clang의 문제보고 나 보호하려는 버전이없는 버전입니다.

void myFunction(testingMask theMask) {
    assert(theMask == MaskValueUno || theMask == MaskValueDos);
    switch (theMask) {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
    }
}

Killian은 왜 clang이 경고를 내는지 설명했습니다. 열거 형을 확장하면 기본 케이스에 빠질 것입니다. 아마도 원하는 것이 아닙니다. 올바른 조치는 기본 사례를 제거하고 처리되지 않은 조건에 대한 경고를받는 것 입니다.

이제 누군가가 열거 외부의 값으로 함수를 호출 할 수 있다고 걱정합니다. 그것은 함수의 전제 조건을 충족시키지 못하는 것처럼 들립니다. testingMask열거 에서 값을 기대한다고 기록 되었지만 프로그래머가 다른 것을 전달했습니다. 따라서 프로그래머 오류를 사용하십시오 assert()(또는 NSCAssert()Objective-C를 사용한다고 말했듯이). 프로그래머가 잘못하면 프로그래머가 잘못하고 있다는 메시지가 표시되면서 프로그램이 충돌하게하십시오.


6
+1. 작은 수정으로, 나는 각 사례를 갖고 끝에를 return;추가하는 것을 선호합니다 assert(false);(법적 열거 형을 처음 assert()과에 나열하여 자신을 반복하는 대신 switch).
Josh Kelley

1
잘못된 열거 형이 바보 프로그래머가 아닌 메모리 손상 버그에 의해 전달되면 어떻게됩니까? 운전자가 Toyota의 브레이크를 눌렀을 때 버그가 열거를 손상 시켰을 때, 브레이크 처리 프로그램이 충돌하고 타서 드라이버에게 텍스트가 표시되어야합니다. "프로그래머가 잘못하고 있습니다. ". 나는 이것이 절벽 가장자리 위로 운전하기 전에 이것이 어떻게 사용자에게 도움이되는지 잘 모르겠습니다.

2
@Lundin은 OP 가하는 일입니까, 아니면 방금 구축 한 무의미한 이론적 인 경우입니까? 그럼에도 불구하고 "메모리 손상 버그"[i]는 프로그래머의 오류입니다. [ii]는 의미있는 방식으로 계속할 수있는 것이 아닙니다 (적어도 질문에 명시된 요구 사항이 아님).

1
@GrahamLee 예상치 못한 오류를 발견했을 때 "레이 다운 앤 다이"가 반드시 최선의 선택은 아니라고 말하는 것입니다.

1
어설 션과 사례가 실수로 동기화되지 않는 새로운 문제가 있습니다.
user253751

9

default여기 에 레이블이 있다는 것은 기대하는 것에 대해 혼란스러워한다는 표시입니다. 당신은 모든 가능한 소진했기 때문에 enum명시 적으로 값을은이 default가능성이 실행되지 않을 수 있으며, 미래의 어느 변경에 대해 당신이 확장 된 경우 때문에, 보호하는 데 필요하지 않습니다 enum, 그 구조는 것이 이미 경고를 생성합니다.

따라서 컴파일러는 모든 기반을 다뤘지만 아직하지 않았다고 생각 하는 것처럼 보이며 항상 나쁜 징조입니다. switch예상되는 형식 으로 변경하기 위해 최소한의 노력을 기울임 으로써 컴파일러에게 현재하고있는 것처럼 보이는 것이 실제로하고있는 것임을 알 수 있습니다.


1
그러나 내 관심사는 더러운 변수이며 그것을 막는 것입니다 (예 : 초기화되지 않은 정수). 그러한 상황에서 switch 문이 기본값에 도달 할 수있는 것 같습니다.
Swizzlr

나는 Objective-C에 대한 정의를 모릅니다.하지만 typdef'd 열거 형이 컴파일러에 의해 시행 될 것이라고 가정했습니다. 다시 말해, "초기화되지 않은"값은 프로그램에 이미 정의되지 않은 동작이있는 경우에만 메소드를 입력 할 수 있으며 컴파일러가이를 고려하지 않는 것이 전적으로 합리적이라고 생각합니다.
Kilian Foth

3
@KilianFoth 아니, 그들은하지 않습니다. Objective-C 열거 형은 Java 열거 형이 아닌 C 열거 형입니다. 기본 정수 유형의 모든 값 이 함수 인수에 존재할 있습니다.

2
또한 열거 형은 런타임에서 정수로 설정할 수 있습니다. 따라서 상상할 수있는 모든 값을 포함 할 수 있습니다.

OP가 맞습니다. 테스트 마스크 m; myFunction (m); 기본 사례에 부딪 칠 것입니다.
Matthew James Briggs 5

4

Clang은 혼란스럽고 기본 문장을 가지고 완벽하게 훌륭한 연습이 있으며 방어 프로그래밍 이라고하며 좋은 프로그래밍 연습으로 간주됩니다 (1). 데스크탑 프로그래밍에는 없지만 미션 크리티컬 시스템에서 많이 사용됩니다.

방어 프로그래밍의 목적은 이론 상으로는 절대 발생하지 않는 예기치 않은 오류를 포착하는 것입니다. 이러한 예기치 않은 오류는 프로그래머가 반드시 함수에 잘못된 입력 또는 "악한 핵"을 제공하는 것은 아닙니다. 버퍼 오버플로, 스택 오버플로, 런 어웨이 코드 및 함수와 관련이없는 유사한 버그로 인해 변수가 손상되었을 수 있습니다. 임베디드 시스템의 경우 특히 외부 RAM 회로를 사용하는 경우 EMI로 인해 변수가 변경 될 수 있습니다.

기본 문장 안에 쓰는 내용에 관해서는 ... 프로그램이 끝나고 프로그램이 완전히 사라 졌다고 의심되면 일종의 오류 처리가 필요합니다. 대부분의 경우 "예기치 않았지만 중요하지 않은"등의 주석이있는 빈 명령문을 추가하기 만하면 예상치 못한 상황에 대한 생각을 표시 할 수 있습니다.


(1) MISRA-C : 2004 15.3.


1
프로그램에 대한 그들의 견해는 이론 상으로는 잘못된 것이 실제로는 잘못 될 수있는 추상적 인 유토피아이기 때문에 일반적인 PC 프로그래머는 일반적으로 방어 프로그래밍의 개념을 완전히 외계인이라고 생각하지 마십시오. 그러므로 누구에게 물어 보느냐에 따라 질문에 대한 다양한 답변을 얻을 수 있습니다.

3
Clang은 혼란스럽지 않습니다. 문체 경고와 같이 항상 유용하지 않은 경고를 포함한 모든 경고 를 명시 적으로 요청했습니다 . (나는 열거 형 스위치에서 기본값을 원하지 않기 때문에 개인적 으로이 경고를 선택합니다. 열거 형 값을 추가하고 처리하지 않으면 경고가 표시되지 않습니다. 따라서 항상 처리합니다. enum 외부의 나쁜 가치 사건.)
Jens Ayton

2
@JensAyton 혼란 스러워요. 스위치 문 에 기본 문 없으면 모든 전문 정적 분석기 도구와 모든 MISRA 호환 컴파일러에 경고가 표시됩니다 . 직접 해보십시오.

4
MISRA-C로 코딩 다시, C 컴파일러에 대한 유일한 유효 사용하지 않고, -Weverything모든 경고가 아닌 특정 사용 사례에 대한 선택 적합합니다. 취향에 맞지 않는 것은 혼란과 같은 것이 아닙니다.
Jens Ayton

2
@Lundin : MISRA-C를 고수한다고해서 마술처럼 프로그램을 안전하고 버그없이 만들 수있는 것은 아니라고 확신합니다. MISRA에 대해 들었습니다 . 실제로, 그것은 누군가의 (인식이지만 논란의 여지가없는) 의견에 지나지 않으며, 규칙의 일부를 자의적으로 고안된 맥락 밖에서 자의적이고 때로는 해로운 것으로 여기는 사람들이 있습니다.
cHao

4

그래도 낫다:

typedef enum {
    MaskValueUno,
    MaskValueDos,

    MaskValue_count
} testingMask;

void myFunction(testingMask theMask) {
    assert(theMask >= 0 && theMask<MaskValue_count);
    switch theMask {
        case MaskValueUno: {}// deal with it
        case MaskValueDos: {}// deal with it
    }
};

열거 형에 항목을 추가 할 때 오류가 덜 발생합니다. 열거 형 값을 부호없는 경우 0보다 큰 테스트를 건너 뛸 수 있습니다. 이 방법은 열거 형 값에 공백이없는 경우에만 작동하지만 종종 그러한 경우가 있습니다.


2
이것은 단순히 유형에 포함하고 싶지 않은 값으로 유형을 오염시킵니다. : 그것은 여기에 좋은 오래된 WTF와 유사성을 가지고 thedailywtf.com/articles/What_Is_Truth_0x3f_
마틴 Sugioarto

4

그러나 일부 해킹이 발생하여 초기화되지 않은 정수를 내 아름다운 기능에 던지면 어떻게됩니까?

그럼 당신은 얻을 정의되지 않은 행동을 하고 default의미가 될 것입니다. 이를 개선하기 위해 할 수있는 일은 없습니다.

좀 더 명확하게하겠습니다. 누군가가 int함수에 초기화되지 않은 것을 전달하면 즉시 정의되지 않은 동작입니다. 함수는 Halting Problem을 해결할 수 있으며 중요하지 않습니다. UB입니다. UB가 호출 된 후에는 수행 할 수있는 작업이 없습니다.


3
조용히 무시하면 로그를 기록하거나 예외를 던지는 것은 어떻습니까?
jgauffin

3
실제로 스위치에 기본 명령문을 추가하고 오류를 정상적으로 처리하는 등 수행 할 수있는 작업이 많이 있습니다. 이것을 방어 프로그래밍이라고 합니다. 이 기능은 잘못된 프로그래머가 잘못된 입력을

1
아니요, 아무것도 처리하지 않습니다. UB입니다. 프로그램은 기능이 입력되는 순간 UB를 가지며 기능 본문은 그것에 대해 아무것도 할 수 없습니다.
DeadMG December

2
@DeadMG 열거 형은 구현에서 해당 정수 유형이 가질 수있는 모든 값을 가질 수 있습니다. 그것에 대해 정의되지 않은 것은 없습니다. 초기화되지 않은 자동 변수의 내용에 액세스하는 것은 실제로 UB이지만 "악의 해킹"(일종의 버그 일 가능성이 있음)은 값 중 하나가 아니지만 함수에 잘 정의 된 값을 던질 수 있습니다. 열거 형 선언에 나열되어 있습니다.

2

기본 진술이 반드시 도움이되는 것은 아닙니다. 스위치가 열거 형 위에 있으면 열거 형에 정의되지 않은 값은 정의되지 않은 동작을 실행하게됩니다.

아시다시피 컴파일러는 다음과 같이 해당 스위치를 기본값으로 컴파일 할 수 있습니다.

if (theMask == MaskValueUno)
  // Execute something MaskValueUno code
else // theMask == MaskValueDos
  // Execute MaskValueDos code

정의되지 않은 동작을 트리거하면 되돌아 갈 수 없습니다.


2
첫째, 그것은 정의되지 않은 행동이 아닌 불특정 한 가치 입니다. 이것은 컴파일러가 더 편리한 다른 값을 가정 할 수 있지만 달을 그린 치즈 등으로 바꿀 수는 없다는 것을 의미합니다. 둘째, 내가 알 수있는 한 C ++에만 적용됩니다. C에서 enum유형의 값 범위는 기본 정수 유형의 값 범위와 동일합니다 (구현 정의이므로 따라서 일관되어야합니다).
Jens Ayton

1
그러나 열거 형 변수와 열거 상수의 인스턴스는 반드시 동일한 너비와 부호를 가질 필요는 없으며 둘 다 정의되어 있습니다. 그러나 C의 모든 열거 형은 해당 정수 유형과 정확히 동일하게 평가되므로 정의되지 않았거나 지정되지 않은 동작이 없습니다.

2

나는 또한 default:모든 경우에 있는 것을 선호합니다 . 나는 평소와 같이 파티에 늦었지만 ... 내가 위에서 보지 못한 다른 생각들 :

  • 특정 경고 (또는도 던지는 경우 오류가 -Werror)에서 오는 -Wcovered-switch-default(에서 -Weverything가 아니라 -Wall). 도덕적 유연성은 켤 수 있습니다 경우 해제 특정 경고를 (즉,에서 몇 가지를 절제 -Wall-Weverything), 던지는 고려 -Wno-covered-switch-default(또는 -Wno-error=covered-switch-default사용하는 경우 -Werror), 그리고 일반적으로 -Wno-...다른 경고를 당신은 불쾌한 찾을 수 있습니다.
  • 들어 gcc(와 더 일반적인 행동 clang), 상담 gcc에 대한 맨 -Wswitch, -Wswitch-enum, -Wswitch-default스위치 문에서 열거 된 유형의 비슷한 상황에 대한 (다른) 동작을.
  • 나는 또한 개념 상이 경고를 좋아하지 않으며, 그 문구를 좋아하지도 않습니다. 나에게 경고의 단어 ( "기본 레이블 ... 모든 것을 다룹니다 ... 값")는 default:사건이 항상 다음과 같이 실행될 것이라고 제안합니다.

    switch (foo) {
      case 1:
        do_something(); 
        //note the lack of break (etc.) here!
      default:
        do_default();
    }

    첫 번째 읽기에, 이것은 당신이로 실행 줄 알았는데 무엇 - 당신의 것을 default:더가 있기 때문에 경우 항상 실행되지 것 break;또는 return;유사한. 이 개념은 (나의 귀와 비슷하다)에서 나오는 다른 유모 스타일 (때로는 도움이 되긴하지만) 해설과 유사하다 clang. 인 경우 foo == 1두 기능이 모두 실행됩니다. 위의 코드에는이 동작이 있습니다. 즉, 후속 사례에서 코드를 계속 실행하려는 경우에만 브레이크 아웃에 실패하십시오! 그러나 이것은 귀하의 문제가 아닌 것으로 보입니다.

pedantic 될 위험에, 다른 완전성에 대한 생각 :

  • 그러나 나는이 동작이 다른 언어 또는 컴파일러의 공격적인 유형 검사와 일치한다고 생각합니다. 가설을 세울 때 일부 환급 int 자신의 특정 유형을 명시 적으로 사용하려는이 함수에 또는 다른 것을 전달하려고 시도하는 경우 컴파일러는 해당 상황에서 공격적인 경고 또는 오류로 동등하게 보호해야합니다. 그러나 그렇지 않습니다! (즉, 적어도 것으로 보인다 gccclang하지 않는 enum유형 검사,하지만 내가 듣고 그 icc않습니다 ). 타입 안전성을 얻지 못하므로 위에서 설명한 것처럼 가치 안전성을 얻을 수 있습니다. 그렇지 않으면 TFA에서 제안한대로 struct형식 안전성을 제공 할 수있는 것을 고려하십시오 .
  • 다른 해결 방법은 당신의 새로운 "값"만들 수 enum등을 MaskValueIllegal, 그를 지원하지 않는 case당신에 switch. 그것은 default:(다른 엉뚱한 가치에 더하여) 먹게 될 것입니다.

긴 라이브 방어 코딩!


1

대안 제안은 다음과 같습니다
. OP는 열거 형 이 예상 되는 int곳 에서 누군가가 통과하는 경우를 막으려 고합니다 . 또는 더 많은 경우에 새로운 헤더를 사용하여 오래된 라이브러리를 최신 프로그램과 연결 한 경우가 있습니다.

int케이스 를 처리하기 위해 스위치를 바꾸지 않겠습니까? 스위치의 값 앞에 캐스트를 추가하면 경고가 제거되고 기본값이 존재하는 이유에 대한 힌트도 제공됩니다.

void myFunction(testingMask theMask) {
    int maskValue = int(theMask);
    switch(maskValue) {
        case MaskValueUno: {} // deal with it
        case MaskValueDos: {}// deal with it
        default: {} //deal with an unexpected or uninitialized value
    }
}

내가 찾을이 훨씬 덜 제기 (가)보다 assert()가능한 각 값을 테스트하거나 심지어의 범위는 가정 만들기 열거 값이 너무 간단한 테스트가 작동하는지 잘 정렬됩니다. 이것은 기본 설정이 정확하고 아름답게 수행되는 추악한 방법입니다.


1
이 게시물은 읽기 어렵습니다 (텍스트의 벽). 더 나은 형태로 편집 하시겠습니까 ?
gnat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.