뭐가 ":-!!" C 코드로?


1665

나는 /usr/include/linux/kernel.h 에서이 이상한 매크로 코드에 부딪쳤다 .

/* Force a compilation error if condition is true, but also produce a
   result (of value 0 and type size_t), so the expression can be used
   e.g. in a structure initializer (or where-ever else comma expressions
   aren't permitted). */
#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
#define BUILD_BUG_ON_NULL(e) ((void *)sizeof(struct { int:-!!(e); }))

무엇을 :-!!합니까?


2
-단항 빼기 <br />! 논리 NOT <br /> 주어진 정수 e와 역이 아니므로 변수는 0 또는 1
CyrillC

69
git blame은 Jan Beulich 가이 특정 형태의 정적 주장을 8c87df4에서 소개 했다고 알려줍니다 . 분명히 그는 그렇게 할만한 충분한 이유가있었습니다 (커밋 메시지 참조).
Niklas B.

55
@Lundin : assert ()는 컴파일 타임 오류를 발생시키지 않습니다. 그것이 위의 구성의 요점입니다.
Chris Pacejo

4
@GreweKokkor 순진하지 마십시오. Linux는 한 사람이 모든 것을 처리하기에는 너무 큽니다. 리누스는 자신의 liutenants를 가지고 있으며, 그들은 상향식에서 변화와 개선을 추진하고 있습니다. Linus는 피처를 원하는지 아닌지 결정하지만 동료를 어느 정도 신뢰합니다. 오픈 소스 환경에서 분산 시스템이 작동하는 방식에 대해 더 자세히 알고 싶다면 youtube.com/watch?v=4XpnKHJAok8(YouTube 비디오)을 확인하십시오 (매우 흥미로운 대화입니다).
Tomas Pruzina

3
@cpcloud sizeof는 값이 아닌 유형을 "평가"합니다. 이 경우 유효하지 않은 유형입니다.
Winston Ewert

답변:


1692

이것은 실제로 표현식 e가 0으로 평가 될 수 있는지 여부를 확인하고 그렇지 않은 경우 빌드를 실패시키는 방법 입니다.

매크로의 이름이 다소 잘못되었습니다. 그것은 더 같은 것을해야 BUILD_BUG_OR_ZERO보다는 ...ON_ZERO. ( 이것이 혼란스러운 이름인지 아닌지에 대해 가끔 논의 되었습니다 .)

다음과 같은 표현을 읽어야합니다.

sizeof(struct { int: -!!(e); }))
  1. (e): 계산식 e.

  2. !!(e): 논리적으로 두 번 부정 : 0if e == 0; 그렇지 않으면 1.

  3. -!!(e): 수치는 2 단계의 발현을 부정 : 0그것이 경우 0; 그렇지 않으면 -1.

  4. struct{int: -!!(0);} --> struct{int: 0;}: 그것이 0이라면, 우리는 폭이 0 인 익명 정수 비트 필드를 가진 구조체를 선언합니다. 모든 것이 정상이며 정상적으로 진행됩니다.

  5. struct{int: -!!(1);} --> struct{int: -1;}반면에 0 이 아닌 경우 음수가됩니다. 음의 너비를 가진 비트 필드를 선언 하면 컴파일 오류가 발생합니다.

따라서 구조체에서 너비가 0 인 비트 필드 (괜찮음) 또는 음의 너비가있는 비트 필드 (컴파일 오류)로 마무리합니다. 그런 다음 sizeof해당 필드 를 가져 와서 size_t적절한 너비 (0이있는 경우 e0이 됨)를 얻습니다 .


어떤 사람들은 물었습니다 : 왜 그냥 assert?

keithmo의 답변 은 다음과 같습니다.

이 매크로는 컴파일 타임 테스트를 구현하는 반면 assert ()는 런타임 테스트입니다.

정확히 맞습니다. 런타임에 커널 에서 이전에 발견되었을 수있는 문제를 감지하고 싶지 않습니다 ! 운영 체제의 중요한 부분입니다. 컴파일 타임에 문제가 감지 될 수있는 정도가 훨씬 좋습니다.


5
@weston 많은 다른 장소. 직접 참조하십시오!
John Feminella

166
C ++ 또는 C 표준의 최신 변형에는 static_assert관련 목적 과 같은 것이 있습니다.
Basile Starynkevitch

54
@Lundin-#error는 3 줄의 코드 # if / # error / # endif를 사용해야하며 전처리기에 액세스 할 수있는 평가에만 작동합니다. 이 핵은 컴파일러가 액세스 할 수있는 모든 평가에서 작동합니다.
Ed Staub

236
리눅스 커널은 리누스가 아직 살아있는 동안 적어도 C ++을 사용하지 않습니다.
Mark Ransom

6
@ Dolda2000 : " C의 부울 표현식은 항상 0 또는 1로 평가되도록 정의됩니다 ."-정확하게는 아닙니다. 사업자 수율 "논리적으로 부울"결과 (즉 !, <, >, <=, >=, ==, !=, &&, ||)는 항상 조건으로 사용될 수있다 결과를 얻을 수 있습니다 0 또는 1의 다른 표현을 얻을 수 있지만, 단지 제로 또는 비 - 제로입니다; 예를 들어, isdigit(c)여기서 c숫자는 0이 아닌 값을 생성 수 있습니다 (조건에서 true로 처리됨).
키이스 톰슨

256

:비트 필드입니다. 에 관해서 !!입니다, 논리적 이중 부정은 반환 그래서 0거짓 또는 1진실합니다. 그리고 -빼기 부호, 즉 산술 부정입니다.

컴파일러가 유효하지 않은 입력을 처리하는 것은 단지 속임수입니다.

고려하십시오 BUILD_BUG_ON_ZERO. 때 -!!(e)컴파일 오류가 발생 음의 값으로 평가한다. 그렇지 않으면 -!!(e)0으로 평가되고 폭이 0 인 비트 필드의 크기는 0입니다. 따라서 매크로는 size_t값이 0 인 평가됩니다 .

입력이 0 이 아닌 경우 실제로 빌드가 실패하기 때문에 이름이 약 합니다.

BUILD_BUG_ON_NULL매우 유사하지만 a 대신 포인터를 생성합니다 int.


14
되어 sizeof(struct { int:0; })엄격하게 준수?
ouah

7
결과는 왜 일반적 0일까요? struct빈 비트 필드 만있는 A 는 true이지만 크기가 0 인 구조체는 허용되지 않습니다. 예를 들어 해당 유형의 배열을 생성하는 경우 개별 배열 요소의 주소가 달라야합니까?
Jens Gustedt

2
그들은 실제로 GNU 확장을 사용할 때 신경 쓰지 않고 엄격한 앨리어싱 규칙을 비활성화하며 정수 오버플로를 UB로 간주하지 않습니다. 그러나 이것이 C를 엄격하게 준수하는지 궁금합니다.
ouah

3
익명 제로 길이의 비트 필드에 대한 @ouah 여기를 참조 : stackoverflow.com/questions/4297095/...
데이비드 헤퍼 넌

9
@DavidHeffernan 실제로 C는 너비의 비 암호 비트 필드를 허용 0하지만 구조에 다른 명명 된 멤버가 없으면 그렇지 않습니다. (C99, 6.7.2.1p2) "If the struct-declaration-list contains no named members, the behavior is undefined."따라서 예를 들어 sizeof (struct {int a:1; int:0;})엄격하게 준수하지만 sizeof(struct { int:0; })정의되지 않은 동작은 아닙니다.
ouah

168

어떤 사람들은이 매크로를와 혼동하는 것 같습니다 assert().

이 매크로는 컴파일 타임 테스트를 구현하지만 assert()런타임 테스트입니다.


52

글쎄, 나는이 구문에 대한 대안이 언급되지 않았다는 것에 상당히 놀랐다. 또 다른 일반적인 (그러나 오래된) 메커니즘은 정의되지 않은 함수를 호출하고 어설 션이 올바른 경우 최적화 프로그램을 사용하여 함수 호출을 컴파일 아웃하는 것입니다.

#define MY_COMPILETIME_ASSERT(test)              \
    do {                                         \
        extern void you_did_something_bad(void); \
        if (!(test))                             \
            you_did_something_bad(void);         \
    } while (0)

이 메커니즘이 작동하는 동안 (최적화를 사용하는 한) 링크 할 때까지 오류를보고하지 않는 단점이 있으며, 이때 you_did_something_bad () 함수에 대한 정의를 찾지 못합니다. 이것이 커널 개발자들이 음의 크기의 비트 필드 폭과 음의 크기의 배열 (나중에 GCC 4.4에서 빌드 중단을 중단 한)과 같은 트릭을 사용하기 시작한 이유입니다.

컴파일 타임 어설 션이 필요하다는 것에 동조하여 GCC 4.3 은이 오래된 개념을 확장 할 수 있는 error기능 속성 을 도입 했지만 더 이상 비밀스러운 "네거티브 크기의 배열이 아닌"사용자가 선택한 메시지로 컴파일 타임 오류를 생성합니다. "오류 메시지!

#define MAKE_SURE_THIS_IS_FIVE(number)                          \
    do {                                                        \
        extern void this_isnt_five(void) __attribute__((error(  \
                "I asked for five and you gave me " #number))); \
        if ((number) != 5)                                      \
            this_isnt_five();                                   \
    } while (0)

실제로 Linux 3.9부터는 compiletime_assert이 기능을 사용하는 매크로가 bug.h있으며 대부분의 매크로가 그에 따라 업데이트되었습니다. 여전히이 매크로는 이니셜 라이저로 사용할 수 없습니다. 그러나 by 문 표현 (다른 GCC C- 확장)을 사용하면 가능합니다!

#define ANY_NUMBER_BUT_FIVE(number)                           \
    ({                                                        \
        typeof(number) n = (number);                          \
        extern void this_number_is_five(void) __attribute__(( \
                error("I told you not to give me a five!"))); \
        if (n == 5)                                           \
            this_number_is_five();                            \
        n;                                                    \
    })

이 매크로는 매개 변수를 정확히 한 번만 평가하고 (부작용이있는 경우) "나에게 5를주지 말라고 알려주는 컴파일 타임 오류"를 생성합니다. 식이 5로 평가되거나 컴파일 타임 상수가 아닌 경우

왜 우리는 음의 크기의 비트 필드 대신 이것을 사용하지 않습니까? 아아, 현재 명령문 표현식이 자체적으로 일정하더라도 (즉, 열거 상수, 비트 필드 너비 등) 상수 이니셜 라이저로 사용하는 것을 포함하여 명령문 표현식 사용에 대한 많은 제한이 있습니다. 컴파일 타임에 그렇지 않으면 __builtin_constant_p()테스트를 통과합니다 ). 또한 기능 본체 외부에서는 사용할 수 없습니다.

다행스럽게도 GCC는 이러한 단점을 곧 수정하고 상수 명령문 표현식을 상수 이니셜 라이저로 사용할 수 있기를 바랍니다. 여기서 어려운 점은 올바른 상수 표현을 정의하는 언어 사양입니다. C ++ 11은이 유형 또는 사물에 대해서만 constexpr 키워드를 추가했지만 C11에는 대응하는 키워드가 없습니다. C11은이 문제의 일부를 해결하는 정적 어설 션을 얻었지만 이러한 단점을 모두 해결하지는 못합니다. 따라서 gcc가 -std = gnuc99 & -std = gnuc11 또는 그와 같은 일부를 통해 constexpr 기능을 확장 기능으로 사용할 수있게하고 명령문 표현 등에 사용할 수 있기를 바랍니다. 알.


6
모든 솔루션이 대안이 아닙니다. 매크로 위의 주석은 매우 명확합니다 " so the expression can be used e.g. in a structure initializer (or where-ever else comma expressions aren't permitted)."매크로는 형식의 표현을 반환합니다size_t
Wiz

3
@ 위즈 네, 알고 있습니다. 아마도 이것은 조금 장황하고 어쩌면 내 문구를 다시 방문해야 할 수도 있지만, 내 주장은 정적 어설 션을위한 다양한 메커니즘을 탐색하고 왜 우리가 여전히 음의 크기의 비트 필드를 사용하는지 보여 주어야했습니다. 간단히 말해서 상수 명령문 표현 메커니즘을 얻으면 다른 옵션을 열 수 있습니다.
다니엘 산토스

어쨌든 변수에 이러한 매크로를 사용할 수 없습니다. 권리? error: bit-field ‘<anonymous>’ width not an integer constant상수 만 허용합니다. 그래서, 용도는 무엇입니까?
Karthik Raj Palanichamy

1
@Karthik Linux 커널 소스를 검색하여 왜 사용되는지 확인하십시오.
Daniel Santos

@supercat 귀하의 의견이 전혀 관련이 있는지 모르겠습니다. 내용을 수정하거나 의미를 더 잘 설명하거나 제거 할 수 있습니까?
Daniel Santos

36

0조건이 거짓이면 크기 비트 필드를 만들고 조건이 참 / 제로가 아닌 경우 크기 -1( -!!1) 비트 필드를 만듭니다. 전자의 경우 오류가 없으며 구조체는 int 멤버로 초기화됩니다. 후자의 경우 컴파일 오류가 있습니다 ( -1물론 크기 비트 필드 와 같은 것은 생성 되지 않습니다 ).


3
실제로 size_t조건이 true 인 경우 값이 0 인 a 를 반환 합니다.
David Heffernan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.