C 및 C ++에서 다르게 작동하는 열거 형 상수


81

이유 :

#include <stdio.h>
#include <limits.h>
#include <inttypes.h>

int main() {
    enum en_e {
        en_e_foo,
        en_e_bar = UINT64_MAX,
    };
    enum en_e e = en_e_foo;
    printf("%zu\n", sizeof en_e_foo);
    printf("%zu\n", sizeof en_e_bar);
    printf("%zu\n", sizeof e);
}

4 8 8C 및 8 8 8C ++로 인쇄 합니까 (4 바이트 정수가있는 플랫폼에서)?

나는 UINT64_MAX할당이 모든 열거 상수를 적어도 64 비트로 강제하지만 en_e_foo평범한 C에서는 32로 남아 있다는 인상 을 받았다 .

불일치의 근거는 무엇입니까?


1
어떤 컴파일러? 차이가 나는지는 모르겠지만 그럴 수도 있습니다.
Mark Ransom

@MarkRansom 그것은 gcc로 나왔지만 clang은 동일하게 작동합니다.
PSkocik


3
"4 바이트 정수를 사용하는 플랫폼" 플랫폼뿐만 아니라 유형 너비를 결정하는 컴파일러입니다. 그게 전부일 수 있습니다. (Keith의 답변에 따르면 실제로는 아니지만 일반적으로 이러한 가능성에
유의하십시오.

1
@PSkocik : 실제로 변경된 것은 아닙니다.이 질문이 cc ++ 모두의 유효한 사용을 찾았다는 것입니다 (특정 코드가 둘 사이에 다른 동작을 일으키는 이유를 묻습니다). 또한 괜찮습니다. C ++에서 C 라이브러리를 호출하는 방법과 C에서 호출 할 수있는 C ++를 작성하는 방법을 묻습니다. 매우 좋지 않습니다. C 질문을하고 "그래서 더 많은 시선을 사로 잡습니다"에 C ++ 태그를 던집니다. 또한 좋지 않습니다. C ++ 질문을하고 나중에 "C에 대해서도 대답해야합니다". (그리고 보통의 불평에 대한 - 매우 확인하지 :는 C 태그 ++ 태그는 C를 변경하기 때문에 모두 표준에 존재하는 코드가 사용하는 기능)
벤 보이트

답변:


80

C에서 enum상수는 유형 int입니다. C ++에서는 열거 형입니다.

enum en_e{
    en_e_foo,
    en_e_bar=UINT64_MAX,
};

C에서 이것은 제약 조건 위반 이며 진단이 필요합니다 ( UINT64_MAX 초과하면 INT_MAX아마도 그렇게 할 것입니다). AC 컴파일러는 프로그램을 완전히 거부하거나 경고를 출력 한 다음 동작이 정의되지 않은 실행 파일을 생성 할 수 있습니다. (제약을 위반하는 프로그램이 반드시 정의되지 않은 동작을 가지고 있다는 것은 100 % 명확하지 않지만,이 경우 표준은 동작이 무엇인지 말하지 않으므로 여전히 정의되지 않은 동작입니다.)

gcc 6.2는 이에 대해 경고하지 않습니다. clang은 않습니다. 이것은 gcc의 버그입니다. 표준 헤더의 매크로가 사용될 때 일부 진단 메시지를 잘못 금지합니다. 버그 보고서를 찾아 준 Grzegorz Szpetkowski에게 감사드립니다. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71613

C ++에서 각 열거 유형에는 정수 유형 (반드시 아님 ) 인 기본 유형 이 있습니다 int. 이 기본 유형은 모든 상수 값을 나타낼 수 있어야합니다. 이 경우에, 양 en_e_fooen_e_bar타입 인 en_e경우에도 넓은 적어도 64 비트이어야 int좁다.


10
참고 : UINT64_MAX초과하지 않으려면 최소 65 비트가 INT_MAX필요합니다 int.
Ben Voigt

10
정말 이상한 것은 GCC (5.3.1)와 경고를 방출한다는 것입니다 -Wpedantic18446744073709551615ULL은 불가능 UINT64_MAX.
nwellnhof

4
@dascandy : 아니요, int서명 된 유형이어야하므로 표현할 수 있으려면 65 비트 이상이어야합니다 UINT64_MAX(2 ** 64-1).
Keith Thompson

1
@KeithThompson, 6.7.2.2는 "열거 자 목록의 식별자는 int 유형을 갖는 상수로 선언되며 허용되는 모든 위치에 나타날 수 있습니다."라고 말합니다. 내 이해는 단일 C 열거 형이 선언하는 상수는 열거 형의 유형을 사용하지 않으므로 거기에서 다른 유형을 만드는 것이 큰 확장이 아닙니다 (특히 표준에 대한 확장으로 구현 된 경우).
zneak

2
@AndrewHenle : en_e_bar열거 형보다 크지 않고 en_e_foo작습니다. 열거 형 변수는 가장 큰 상수만큼 컸습니다.
Ben Voigt

25

그 코드는 처음에 유효한 C가 아닙니다.

C99 및 C11의 섹션 6.7.2.2에서는 다음과 같이 말합니다.

제약 :

열거 형 상수의 값을 정의하는 식은으로 표현할 수있는 값을 갖는 정수 상수 식이어야합니다 int.

제약 조건 위반이므로 컴파일러 진단은 필수입니다 (5.1.1.3 참조).

준수 구현은 동작이 정의되지 않음 또는 구현으로 명시 적으로 지정 되더라도 전처리 번역 단위 또는 번역 단위에 구문 규칙 또는 제약 위반이 포함 된 경우 적어도 하나의 진단 메시지 (구현 정의 방식으로 식별 됨)를 생성해야합니다. 한정된.


23

에서 C A는 동안, enum별도의 유형으로 간주되고, 그 자체가 항상 유형이 열거 int.

C11-6.7.2.2 열거 지정자

3 열거 자 목록의 식별자는 int 유형의 상수로 선언됩니다.

따라서 표시되는 동작은 컴파일러 확장입니다.

값이 너무 큰 경우 열거 자 중 하나의 크기 만 확장하는 것이 합리적이라고 말하고 싶습니다.


반면에 C ++에서는 모든 열거 자에 enum선언 된 형식이 있습니다.

따라서 모든 열거 자의 크기는 동일해야합니다. 따라서 전체 크기 enum가 확장되어 가장 큰 열거자를 저장합니다.


11
컴파일러 확장이지만 진단 생성 실패는 부적합입니다.
Ben Voigt

16

다른 사람들이 지적했듯이 제약 조건 위반으로 인해 코드 형식이 잘못되었습니다 (C에서).

GCC 버그 # 71613 (2016 년 6 월보고 됨)이 있는데, 이는 일부 유용한 경고가 매크로로 침묵된다는 것을 나타냅니다.

시스템 헤더의 매크로를 사용하면 유용한 경고가 표시되지 않는 것 같습니다. 예를 들어 아래 예에서 경고는 두 열거 형 모두에 유용하지만 하나의 경고 만 표시됩니다. 다른 경고에 대해서도 마찬가지 일 수 있습니다.

현재 해결 방법은 매크로 앞에 단항 +연산자 를 추가하는 것입니다 .

enum en_e {
   en_e_foo,
   en_e_bar = +UINT64_MAX,
};

GCC 4.9.2를 사용하는 내 컴퓨터에서 컴파일 오류가 발생합니다.

$ gcc -std=c11 -pedantic-errors -Wall main.c 
main.c: In function ‘main’:
main.c:9:20: error: ISO C restricts enumerator values to range ofint’ [-Wpedantic]
         en_e_bar = +UINT64_MAX

12

C11-6.7.2.2/2

열거 상수의 값을 정의하는 표현식은 int.

en_e_bar=UINT64_MAX제약 조건 위반이며 이로 인해 위 코드가 무효화됩니다. C11 초안에 명시된대로 구현을 확인하여 진단 메시지를 생성해야합니다.

적합한 구현은 전처리 번역 단위 또는 번역 단위가 구문 규칙 또는 제약의 위반을 포함하는 경우 적어도 하나의 진단 메시지 (구현 정의 방식으로 식별 됨)를 생성해야합니다. [...]

GCC에 버그가있어 진단 메시지를 생성하지 못한 것 같습니다. (버그가에서 가리키는 대답 하여 그르 Szpetkowski


8
"정의되지 않은 동작"은 런타임 효과입니다. sizeof컴파일 타임 연산자입니다. 여기에는 UB가 없으며, 존재하더라도 sizeof.
Ben Voigt

2
int에 맞지 않는 열거 형이 UB 인 표준 인용문을 찾아야합니다. 나는 그 진술에 대해 매우 회의적이며 이것이 해결 될 때까지 내 투표는 확고한 -1을 유지할 것입니다.
zneak

3
@Sergey : C 표준은 실제로 "열거 상수의 값을 정의하는 표현식은 정수로 표현할 수있는 값을 갖는 정수 상수 표현식이어야합니다."라고 말합니다. 그러나 이것을 위반하는 것은 UB가 아닌 제약 위반, 진단이 필요합니다.
Ben Voigt

3
@haccks : 네? 이것은 제약 위반이며, "전처리 번역 단위 또는 번역 단위에 구문 규칙 또는 제약 조건의 위반이 포함 된 경우 동작이 또한 다음과 같더라도 준수 구현은 적어도 하나의 진단 메시지 (구현 정의 방식으로 식별 됨)를 생성해야합니다. 정의되지 않았거나 구현이 정의 된 것으로 명시 적으로 지정되었습니다. "
Ben Voigt

2
오버플로와 잘림에는 차이가 있습니다. 오버플로는 예상 결과 유형에 비해 너무 큰 값을 생성하는 산술 연산이 있고 부호있는 오버플로가 UB 인 경우입니다. 절단은 대상 유형이 (예 :)로 시작하기에 너무 큰 값이 short s = 0xdeadbeef있고 동작이 구현 정의 된 경우입니다.
zneak

5

나는 표준을 살펴 보았고 내 프로그램은 6.7.2.2p2 때문에 C에서 제약 위반으로 보입니다 .

제약 : 열거 형 상수의 값을 정의하는 표현식은 정수로 표현할 수있는 값을 갖는 정수 상수 표현식이어야합니다.

7.2.5 때문에 C ++에서 정의되었습니다.

기본 유형이 고정되지 않은 경우 각 열거 자의 유형은 초기화 값의 유형입니다. — 열거 자에 대해 이니셜 라이저가 지정된 경우 초기화 값은 표현식과 동일한 유형을 가지며 상수 표현식은 정수 여야합니다. 상수 표현 (5.19). — 첫 번째 열거 자에 대해 이니셜 라이저가 지정되지 않은 경우 초기화 값에는 지정되지 않은 정수 유형이 있습니다. — 그렇지 않으면 초기화 값의 유형은 증가 된 값이 해당 유형에서 표현할 수없는 경우 이전 열거 자의 초기화 값 유형과 동일합니다.이 경우 유형은 증가 된 값을 포함하기에 충분한 지정되지 않은 정수 유형입니다. 그러한 유형이 없으면 프로그램이 잘못된 것입니다.


3
C에서 "정의되지 않음"이 아니라 제약 조건을 위반했기 때문에 "잘못된 형식"입니다. 컴파일러는 위반에 관한 진단을 생성해야합니다.
Ben Voigt

@BenVoigt 차이점에 대해 가르쳐 주셔서 감사합니다. 답변에서 수정했습니다 (다른 답변에서 C ++ 표준의 인용문을 놓 쳤기 때문에 작성했습니다).
PSkocik
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.