C ++ 표준은 초기화되지 않은 bool이 프로그램을 중단시킬 수 있습니까?
C ++ 의 "정의되지 않은 동작" 은 컴파일러가 원하는 모든 작업을 수행 할 수 있다는 것을 알고 있습니다. 그러나 코드가 충분히 안전하다고 가정하면서 충돌이 발생했습니다.
이 경우 실제 문제는 특정 컴파일러를 사용하는 특정 플랫폼에서만 최적화가 활성화 된 경우에만 발생했습니다.
문제를 재현하고 최대한 단순화하기 위해 여러 가지를 시도했습니다. 여기라는 함수의 추출물의 Serialize
부울 매개 변수를 사용하고 문자열을 복사 할, true
또는 false
기존 대상 버퍼가.
이 함수가 코드 검토에 포함되어 있습니까? 실제로 bool 매개 변수가 초기화되지 않은 값인 경우 충돌 할 수 있음을 알 수있는 방법이 없습니까?
// Zero-filled global buffer of 16 characters
char destBuffer[16];
void Serialize(bool boolValue) {
// Determine which string to print based on boolValue
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
const size_t len = strlen(whichString);
// Copy string into destination buffer, which is zero-filled (thus already null-terminated)
memcpy(destBuffer, whichString, len);
}
이 코드가 clang 5.0.0 + 최적화로 실행되면 충돌이 발생합니다.
예상되는 삼항 연산자 boolValue ? "true" : "false"
는 저에게 충분히 안전 해 보였습니다. "쓰레기 값이 무엇이든 boolValue
상관없이 그것이 참이나 거짓으로 평가되기 때문에 중요하지 않습니다."
분해의 문제를 보여주는 컴파일러 탐색기 예제 를 설정했습니다 . 여기에서 완전한 예제입니다. 참고 : 문제를 재현하기 위해 Clang 5.0.0을 -O2 최적화와 함께 사용했습니다.
#include <iostream>
#include <cstring>
// Simple struct, with an empty constructor that doesn't initialize anything
struct FStruct {
bool uninitializedBool;
__attribute__ ((noinline)) // Note: the constructor must be declared noinline to trigger the problem
FStruct() {};
};
char destBuffer[16];
// Small utility function that allocates and returns a string "true" or "false" depending on the value of the parameter
void Serialize(bool boolValue) {
// Determine which string to print depending if 'boolValue' is evaluated as true or false
const char* whichString = boolValue ? "true" : "false";
// Compute the length of the string we selected
size_t len = strlen(whichString);
memcpy(destBuffer, whichString, len);
}
int main()
{
// Locally construct an instance of our struct here on the stack. The bool member uninitializedBool is uninitialized.
FStruct structInstance;
// Output "true" or "false" to stdout
Serialize(structInstance.uninitializedBool);
return 0;
}
옵티 마이저로 인해 문제가 발생합니다. 문자열 "true"와 "false"의 길이가 1만큼만 다르다고 추론하기에 충분히 영리했습니다. 따라서 실제로 길이를 계산하는 대신 bool 자체의 값을 사용해야합니다. 기술적으로 0 또는 1이며 다음과 같습니다.
const size_t len = strlen(whichString); // original code
const size_t len = 5 - boolValue; // clang clever optimization
이 말하자면, "영리"하지만, 내 질문은 : 합니까는 C ++ 표준은 부울을 가정 할 수있는 컴파일러는 '0'또는 '1'의 내부 숫자 표현을 가지고 같은 방법으로 사용할 수 있도록?
또는 이것은 구현 정의의 경우입니까?이 경우 구현은 모든 부울에 0 또는 1 만 포함하고 다른 값은 정의되지 않은 동작 영역이라고 가정합니다.
예, ISO C ++은 이러한 선택을 구현하기 위해 구현을 허용하지만 필수는 아닙니다.
또한 ISO C ++을 사용하면 프로그램에서 UB를 발견 한 경우 (예 : 오류를 찾는 데 도움이 됨) 의도적으로 충돌하는 코드 (예 : 잘못된 명령으로)를 생성 할 수 있습니다. (또는 DeathStation 9000이기 때문에. C ++ 구현이 실제 목적에 유용하기에는 엄격하게 준수하는 것만으로는 충분하지 않습니다). 따라서 ISO C ++을 사용하면 컴파일러는 초기화되지 않은을 읽는 비슷한 코드에서도 (다른 이유로) 충돌 한 asm을 만들 수 uint32_t
있습니다. 비록 트랩 표현이없는 고정 레이아웃 유형이어야합니다.
실제 구현이 어떻게 작동하는지에 대한 흥미로운 질문이지만 대답이 다르더라도 현대 C ++은 이식 가능한 버전의 어셈블리 언어가 아니기 때문에 코드가 여전히 안전하지 않다는 것을 기억하십시오.
x86-64 System V ABI를 컴파일하고 있는데 bool
, 레지스터의 함수 arg가 비트 패턴 false=0
과true=1
레지스터 1 의 하위 8 비트로 표시되도록 지정합니다 . 메모리에서 bool
1 바이트 유형은 다시 0 또는 1의 정수 값을 가져야합니다.
(ABI는 동일한 플랫폼의 컴파일러가 동의하는 일련의 구현 선택 사항이므로 유형 크기, 구조체 레이아웃 규칙 및 호출 규칙을 포함하여 서로의 기능을 호출하는 코드를 만들 수 있습니다.)
ISO C ++에서는이를 지정하지 않지만이 ABI 결정은 bool-> int 변환을 저렴하게하기 때문에 널리 사용됩니다 (확장자 없음) . 컴파일러가 bool
x86이 아닌 모든 아키텍처에 대해 0 또는 1을 가정하지 못하게하는 ABI는 알지 못합니다. 이것은 최적화 등 허용 !mybool
으로는 xor eax,1
: 낮은 비트 플립 단일 CPU 인스트럭션에 0과 1 사이의 비트 / 정수 / BOOL 플립 수있는 모든 가능한 코드 . 또는 유형 a&&b
에 대해 비트 단위 AND로 컴파일 합니다 bool
. 일부 컴파일러는 실제로 컴파일러에서 부울 값을 8 비트로 사용합니다. 그들에 대한 작업이 비효율적입니까? .
일반적으로 as-if 규칙을 사용하면 컴파일러 가 컴파일 할 대상 플랫폼에서 사실 을 활용할 수 있습니다. 최종 결과는 C ++ 소스와 동일한 외부에서 볼 수있는 동작을 구현하는 실행 가능한 코드이기 때문입니다. (정의되지 않은 동작이 실제로 "외부 적으로 볼 수있는"항목에 적용되는 모든 제한 사항 : 디버거가 아니라 올바르게 구성된 올바른 C ++ 프로그램의 다른 스레드에서 발생합니다.)
컴파일러는 코드 생성에서 ABI 보증을 최대한 활용하고 찾은 것과 같은 코드를 최적화 strlen(whichString)
합니다
5U - boolValue
. (BTW,이 최적화는 일종의 영리하지만 memcpy
즉각적인 데이터 저장소로 분기 및 인라인에 비해 근시안적 일 수 있습니다.)
또는 컴파일러가 포인터 테이블을 만들고 bool
0 또는 1이라고 가정 하고 정수 값으로 색인을 생성 할 수 있습니다 ( 이 가능성은 @Barmar의 답변이 제안한 것 입니다).
귀하의 __attribute((noinline))
최적화를 생성자로 사용할 스택에서 바이트를로드 단지 그 소리에지도 활성화 uninitializedBool
. 또한 상기 목적을위한 공간을 만들어 main
으로 push rax
(효율적인로서 대해 작고 다양한 이유로있는 sub rsp, 8
어떤 정도로 쓰레기가 항목을 AL에 있었다) main
가 사용되는 값이다 uninitializedBool
. 이것이 실제로 당신이 아닌 값을 얻은 이유 0
입니다.
5U - random garbage
부호없는 큰 값으로 쉽게 줄 바꿈하여 memcpy가 매핑되지 않은 메모리로 이동할 수 있습니다. 대상이 스택이 아닌 정적 저장소에 있으므로 반환 주소 나 무언가를 덮어 쓰지 않습니다.
다른 구현은 다른 선택을 할 수 있습니다 (예 : false=0
및) true=any non-zero value
. 그리고 아마 그 소리하는 코드를하지 것이라고에 대한 충돌 이 UB의 특정 인스턴스입니다. (하지만 원하는 경우 여전히 허용됩니다.) x86-64가 수행하는 다른 작업을 선택하는 구현에 대해서는 알지 bool
못하지만 C ++ 표준은 아무도하지 않거나 원하지 않는 많은 일을 허용합니다. 현재 CPU와 같은 하드웨어.
ISO C ++에서는의 객체 표현을 검사하거나 수정할 때 찾을 수있는 내용을 지정하지 않은 상태로 둡니다bool
. (예를 들어 ,에 별명을 지정할 수 memcpy
있으므로 bool
into unsigned char
를 사용하면 패딩 비트가 보장되지 않으므로 C ++ 표준은 공식적으로 UB없이 객체 표현을 16 진수 덤프 할 수 있습니다. 객체를 복사하기위한 포인터 캐스팅 표현은 물론 을 할당하는 것과 다르 므로 부울 화를 0 또는 1로 지정하지 않으면 원시 객체 표현을 얻을 수 있습니다.)char*
unsigned char
char foo = my_bool
당신은 한 부분적 으로 컴파일러에서이 실행 경로에 UB을 "숨겨진"noinline
. 인라인이 아니더라도 절차 간 최적화는 여전히 다른 함수의 정의에 의존하는 함수의 버전을 만들 수 있습니다. (먼저 clang은 기호 삽입이 발생할 수있는 Unix 공유 라이브러리가 아닌 실행 파일을 만들고 있습니다. 둘째, 정의 내부의 class{}
정의이므로 모든 번역 단위는 동일한 정의를 가져야합니다. inline
키워드 와 마찬가지로 )
따라서 컴파일러는 의 정의로 ret
또는 ud2
(불법 명령)을 내보낼 수 있습니다. main
실행 경로가 main
불가피하게 정의되지 않은 동작에 직면 하기 때문 입니다. (인라인이 아닌 생성자를 통해 경로를 따르기로 결정한 경우 컴파일러가 컴파일 타임에 볼 수있는 동안)
UB를 만나는 모든 프로그램은 전체 존재에 대해 완전히 정의되지 않았습니다. 그러나 if()
실제로 실행되지 않는 함수 또는 분기 내부의 UB 는 나머지 프로그램을 손상시키지 않습니다. 실제로 이것은 ret
컴파일 타임에 UB를 포함하거나 이끌어 낼 수있는 전체 기본 블록에 대해 컴파일러가 잘못된 명령 또는을 방출하거나 아무것도 방출하지 않고 다음 블록 / 함수에 빠질 수 있음을 의미합니다.
실제로 GCC와 Clang은 실제로 는ud2
말이되지 않는 실행 경로에 대한 코드를 생성하는 대신 실제로 UB에서 방출 합니다. 또는 비 void
기능 종료에서 벗어나는 경우 gcc는 때때로 ret
명령어를 생략합니다 . "내 기능이 RAX에있는 쓰레기와 함께 반환 될 것"이라고 생각했다면, 잘못 알고있는 것입니다. 최신 C ++ 컴파일러는 더 이상 언어를 이식 가능한 어셈블리 언어처럼 취급하지 않습니다. 독립형 비 인라인 버전의 함수가 asm으로 보이는 방식에 대한 가정없이 프로그램이 실제로 유효한 C ++이어야합니다.
또 다른 재미있는 예는 AMD64에서 mmap'ed 메모리에 대한 정렬되지 않은 액세스가 때때로 segfault 인 이유 는 무엇입니까? . x86은 정렬되지 않은 정수에서 오류가 발생하지 않습니다. 왜 잘못 정렬 된 uint16_t*
것이 문제가 될까요? 왜냐하면 alignof(uint16_t) == 2
SSE2로 자동 벡터화 할 때이 가정을 위반하면 segfault가 발생하기 때문입니다.
clang 개발자의 기사 인 정의되지 않은 동작 # 1 / 3에 대해 모든 C 프로그래머가 알아야 할 사항 도 참조하십시오 .
요점 : 컴파일러는 컴파일시 UB를 발견하면, 그것은 수있는 "휴식"원인 심지어 어떤 비트 패턴에 대한 유효한 객체 표현 인 ABI를 대상으로하는 경우 UB 당신의 코드를 통해 경로를 (놀라운 ASM을 방출) bool
.
프로그래머가 많은 실수, 특히 현대 컴파일러가 경고하는 것에 대한 적대감이 예상됩니다. 따라서 -Wall
경고를 사용 하고 수정 해야합니다 . C ++는 사용자에게 친숙한 언어가 아니며 컴파일하려는 대상에 대해 asm에서 안전하더라도 C ++의 일부는 안전하지 않을 수 있습니다. (예를 들어 부호있는 오버플로는 C ++에서 UB이며 컴파일러는를 사용하지 않으면 2의 보수 x86을 컴파일 할 때도 발생하지 않는다고 가정합니다 clang/gcc -fwrapv
.)
컴파일 타임에 표시되는 UB는 항상 위험하므로 링크 타임 최적화를 통해 컴파일러에서 UB를 실제로 숨겨서 어떤 종류의 asm이 생성되는지에 대해 추론 할 수는 없습니다.
지나치게 과격하지 않아야합니다. 종종 컴파일러는 무언가를 피하고 UB 일 때도 예상대로 코드를 내 보냅니다. 그러나 컴파일러가 값 범위에 대해 더 많은 정보를 얻는 최적화를 구현하면 미래에 문제가 될 수 있습니다 (예 : 변수가 음수가 아닌 경우 x86에서 0 확장을 해제하도록 부호 확장을 최적화 할 수 있음) 64). 예를 들어, 현재 gcc 및 clang에서 수행 tmp = a+INT_MIN
은 a<0
항상 거짓으로 최적화되지 않으며 tmp
항상 음수입니다. ( 이 2의 보수 목표에서 INT_MIN
+ a=INT_MAX
는 음수 이므로 a
그보다 높을 수 없습니다.)
따라서 gcc / clang은 현재 계산 된 입력에 대한 범위 정보를 도출하기 위해 역 추적하지 않으며 부호있는 오버플로가 없다는 가정 ( 예 : Godbolt)을 기반으로 한 결과 에 대해서만 추적 합니다. 이것이 최적화인지 사용자 친화의 이름으로 의도적으로 "누락 된"것인지 또는 무엇인지 모르겠습니다.
또한 구현 (일명 컴파일러)은 ISO C ++가 정의되지 않은 동작을 정의 할 수 있습니다 . 예를 들어, Intel의 내장 함수 ( _mm_add_ps(__m128, __m128)
수동 SIMD 벡터화 와 같은) 를 지원하는 모든 컴파일러는 잘못 정렬 된 포인터를 형성 할 수 있어야합니다.이 포인터는 역 참조 하지 않더라도 C ++에서 UB입니다 . __m128i _mm_loadu_si128(const __m128i *)
잘못 정렬 복용하여 정렬되지 않은로드를 수행 __m128i*
하지 않는, 인수를 void*
하거나 char*
. 하드웨어 벡터 포인터와 해당 유형 사이의`reinterpret_cast`는 정의되지 않은 동작입니까?
GNU C / C ++는 -fwrapv
일반적인 부호있는 오버플로 UB 규칙과 별도로 음의 부호있는 숫자 (조차없는 ) 를 왼쪽으로 이동시키는 동작을 정의합니다 . ( 이것은 ISO C ++에서 UB 이며 부호있는 숫자의 오른쪽 시프트는 구현 정의 (논리 대 산술)입니다. 좋은 품질 구현은 HW에서 산술 오른쪽 시프트가있는 산술을 선택하지만 ISO C ++은 지정하지 않습니다). 이 내용은 G 표준 매뉴얼의 정수 섹션에 문서화되어 있으며 C 표준은 구현 방식에 따라 구현 방법이 필요합니다.
컴파일러 개발자들이 염려하는 구현 품질 문제는 분명히 있습니다. 그들은 일반적으로 의도적으로 적대적인 컴파일러를 만들 려고 시도 하지 않지만 C ++의 모든 UB 움푹 들어간 곳 (그들이 정의한 것을 제외하고)을 활용하여 더 잘 최적화하는 것은 때때로 거의 구별 할 수 없습니다.
각주 1 : 상위 56 비트는 일반적으로 레지스터보다 좁은 유형의 경우 수신자가 무시해야하는 가비지 일 수 있습니다.
( 다른 ABI 는 여기서 다르게 선택합니다 . 일부는 MIPS64 및 PowerPC64와 같이 함수로 전달되거나 함수에서 반환 될 때 레지스터를 채우기 위해 좁은 정수 유형을 0 또는 부호 확장해야합니다. 이 x86-64 답변 의 마지막 섹션을 참조하십시오. 이전 ISA와 비교 한 것 입니다.)
예를 들어, 호출자는 a & 0x01010101
호출하기 전에 RDI에서 계산 하여 다른 용도로 사용 했을 수 있습니다 bool_func(a&1)
. 호출자는의 &1
일부로 이미 하위 바이트를 수행했기 때문에 최적화 할 수 and edi, 0x01010101
있으며, 수신자가 상위 바이트를 무시해야한다는 것을 알고 있습니다.
또는 부울이 3 번째 인수로 전달되면 코드 크기를 최적화하는 호출자가 mov dl, [mem]
대신 대신 로드하여 movzx edx, [mem]
RDX의 이전 값 (또는 다른 부분 레지스터 효과에 따라 잘못된 의존성으로 1 바이트를 절약 할 수 있음) CPU 모델). 또는 어쨌든 REX 접두사가 필요하기 때문에 , mov dil, byte [r10]
대신 첫 번째 인수에 대해 movzx edi, byte [r10]
.
이것은 왜 그 소리를 방출이다 movzx eax, dil
에서 Serialize
대신, sub eax, edi
. 정수 인수의 경우 clang은 문서화되지 않은 gcc 및 clang의 동작에 따라 좁은 정수를 32 비트로 0 또는 부호 확장하거나 32 비트 오프셋을 포인터에 추가 할 때 부호 또는 0 확장이 필요합니다. x86-64 ABI? 그래서 나는 그것이 같은 일을하지 않는다는 것을 알고 싶어했습니다 bool
.)
각주 2 : 분기 후에는 4 바이트 mov
즉석 또는 4 바이트 + 1 바이트 저장소 만 있으면 됩니다. 길이는 상점 너비 + 오프셋에 내재되어 있습니다.
OTOH, glibc memcpy는 길이에 따라 겹치는 두 개의 4 바이트로드 / 스토어를 수행하므로 결국 부울의 모든 조건부 분기가 없어집니다. glibc의 memcpy / memmove에 있는 L(between_4_7):
블록 을 보십시오 . 또는 적어도 memcpy의 분기에서 부울이 청크 크기를 선택하는 것과 동일한 방식으로 진행하십시오.
인라인 인 경우 2x mov
-immediate + cmov
와 조건부 오프셋을 사용하거나 문자열 데이터를 메모리에 남겨 둘 수 있습니다.
또는 Intel Ice Lake ( Fast Short REP MOV 기능 사용 )를 튜닝 하면 실제 성능 rep movsb
이 최적 일 수 있습니다. glibc memcpy
는 rep movsb
이 기능을 갖춘 CPU에서 작은 크기로 사용 하기 시작하여 많은 분기를 절약 할 수 있습니다.
UB 감지 및 초기화되지 않은 값 사용을위한 도구
gcc 및 clang에서는 -fsanitize=undefined
런타임에 발생하는 UB에서 경고 또는 오류가 발생하는 런타임 계측을 추가하기 위해 컴파일 할 수 있습니다 . 그러나 그것은 단일화 된 변수를 잡을 수 없습니다. "초기화되지 않은"비트를위한 공간을 만들기 위해 유형 크기를 늘리지 않기 때문입니다.
참조 https://developers.redhat.com/blog/2014/10/16/gcc-undefined-behavior-sanitizer-ubsan/를
초기화되지 않은 데이터의 사용법을 찾으려면 clang / LLVM에 Address Sanitizer 및 Memory Sanitizer가 있습니다. https://github.com/google/sanitizers/wiki/MemorySanitizer 는 clang -fsanitize=memory -fPIE -pie
초기화되지 않은 메모리 읽기 를 감지하는 예를 보여줍니다 . 최적화 없이 컴파일하면 가장 잘 작동 할 수 있으므로 모든 변수 읽기는 실제로 asm의 메모리에서로드됩니다. -O2
부하가 최적화되지 않는 경우 에 사용됨을 보여줍니다 . 나는 그것을 직접 시도하지 않았다. (어떤 경우, 배열을 합치기 전에 누산기를 초기화하지 않으면 clang -O3는 초기화되지 않은 벡터 레지스터에 합산되는 코드를 생성합니다. 따라서 최적화를 통해 UB와 관련된 메모리 읽기가없는 경우가 있습니다 하지만-fsanitize=memory
생성 된 asm을 변경하면이를 확인할 수 있습니다.)
초기화되지 않은 메모리의 복사와 간단한 논리 및 산술 연산을 허용합니다. 일반적으로 MemorySanitizer는 메모리에 초기화되지 않은 데이터의 확산을 자동으로 추적하고 초기화되지 않은 값에 따라 코드 분기를 수행하거나 수행하지 않을 때 경고를보고합니다.
MemorySanitizer는 Valgrind (Memcheck 도구)에있는 일부 기능을 구현합니다.
초기화되지 않은 메모리에서 계산 된 glibc memcpy
를 호출 length
하면 (라이브러리 내부)에 기반한 분기가 발생 하기 때문에이 경우에는 작동합니다 length
. 방금 cmov
, 인덱싱 및 두 개의 저장소를 사용한 완전히 분기없는 버전을 인라인 한 경우 작동하지 않았을 수 있습니다.
Valgrindmemcheck
는 또한 이런 종류의 문제를 찾아 프로그램이 초기화되지 않은 데이터를 단순히 복사하는지에 대해 불평하지 않습니다. 그러나 그것은 초기화되지 않은 데이터에 의존하는 외부에서 보이는 행동을 포착하기 위해 "조건부 점프 또는 이동이 초기화되지 않은 값에 의존하는 경우"를 감지 할 것이라고 말합니다.
아마도로드에 플래그를 지정하지 않는 배후의 아이디어는 구조체에 패딩이있을 수 있으며 개별 구조체가 한 번에 하나씩 만 작성된 경우에도 전체 벡터로드 / 스토어로 전체 구조체 (패딩 포함)를 복사하는 것은 오류가 아니라는 것입니다. asm 레벨에서 패딩 된 내용과 실제로 값의 일부에 대한 정보가 손실되었습니다.
컴파일러는 인수로 전달 부울 값이 유효한 부울 값 (초기화하거나 변환 된 즉, 하나 있다고 가정 할 수있다 true
또는 false
). true
, 참으로 여러 가지의 표현이있을 수 있습니다 - 값은 정수 1과 동일하지 않아도 true
및 false
-하지만 매개 변수는 "올바른 표현은"구현 - 인 두 값 중 하나의 일부 유효한 표현해야합니다 한정된.
따라서을 초기화하지 bool
않거나 다른 유형의 포인터를 통해 덮어 쓰면 컴파일러의 가정이 잘못되고 정의되지 않은 동작이 발생합니다. 경고를 받았습니다 :
50) 초기화되지 않은 자동 객체의 값을 검사하는 것과 같이이 국제 표준에서 "정의되지 않음"으로 설명 된 방식으로 부울 값을 사용하면 마치 true 또는 false가 아닌 것처럼 동작 할 수 있습니다. (기본 유형 §6.9.1의 6 항에 대한 각주)
함수 자체는 정확하지만 테스트 프로그램에서 함수를 호출하는 명령문은 초기화되지 않은 변수의 값을 사용하여 정의되지 않은 동작을 유발합니다.
버그는 호출 함수에 있으며 코드 검토 또는 호출 함수의 정적 분석에 의해 감지 될 수 있습니다. gcc 8.2 컴파일러는 컴파일러 탐색기 링크를 사용하여 버그를 감지합니다. (아마도 문제를 찾지 못한다는 버그에 대해 버그 보고서를 제출할 수 있습니다).
정의되지 않은 동작은 정의되지 않은 동작 을 트리거 한 이벤트 후 몇 줄이 충돌하는 프로그램을 포함하여 모든 일이 발생할 수 있음을 의미 합니다.
NB. "정의되지 않은 동작으로 인해 _____가 발생할 수 있습니까?" 항상 "예"입니다. 그것은 말 그대로 정의되지 않은 행동의 정의입니다.
부울은 true
and에 내부적으로 사용되는 구현 종속 값만 보유 false
할 수 있으며 생성 된 코드는이 두 값 중 하나만 보유한다고 가정 할 수 있습니다.
일반적으로, 구현은 정수를 사용 0
하기위한 false
과 1
대한 true
사이의 전환을 단순화하기 위해, bool
그리고 int
및 확인 if (boolvar)
과 같은 코드를 생성 if (intvar)
. 이 경우 할당에서 삼항에 대해 생성 된 코드가 값을 두 문자열에 대한 포인터 배열의 인덱스로 사용한다고 상상할 수 있습니다. 즉, 다음과 같이 변환 될 수 있습니다.
// the compile could make asm that "looks" like this, from your source
const static char *strings[] = {"false", "true"};
const char *whichString = strings[boolValue];
boolValue
초기화되지 않은 경우 실제로는 정수 값을 보유 할 수 있으며 이로 인해 strings
배열 경계 외부에서 액세스 할 수 있습니다 .
질문을 많이 요약하면 C ++ 표준을 사용하면 컴파일러에서 bool
내부 숫자 표현이 '0'또는 '1'인 것으로 가정하여 그러한 방식으로 사용할 수 있습니까?
표준은의 내부 표현에 대해서는 아무 것도 말하지 않습니다 bool
. a bool
로 캐스팅 할 때 발생하는 상황 만 정의 합니다 int
(또는 그 반대). 대부분 이러한 통합 변환 (및 사람들이 그에 크게 의존한다는 사실) 때문에 컴파일러는 0과 1을 사용하지만 반드시 사용할 필요는 없습니다 (하지만 사용하는 하위 레벨 ABI의 제약 조건을 존중해야 함) ).
따라서 컴파일러는 a를 볼 때 ' '또는 ' '비트 패턴 중 하나를 포함 하고 느낌이 드는 것을 수행 할 것을 bool
고려할 자격이 bool
있습니다 . 따라서 및 의 값이 각각 1과 0 인 경우 컴파일러는에 최적화 할 수 있습니다. 다른 재미있는 행동이 가능합니다!true
false
true
false
strlen
5 - <boolean value>
여기에 반복적으로 언급 된 것처럼 정의되지 않은 동작은 정의되지 않은 결과를 갖습니다. 포함하지만 이에 국한되지는 않습니다
- 예상대로 작동하는 코드
- 임의의 시간에 코드 실패
- 코드가 전혀 실행되지 않습니다.
정의되지 않은 동작에 대해 모든 프로그래머가 알아야 할 사항 보기
'Programing' 카테고리의 다른 글
json.net을 사용하여 null 인 경우 클래스의 속성을 무시하는 방법 (0) | 2020.02.14 |
---|---|
C #에서 가장 좋아하는 확장 방법은 무엇입니까? (0) | 2020.02.14 |
자바 스크립트 : Class.method vs. Class.prototype.method (0) | 2020.02.14 |
R 세션에서 사용 가능한 메모리를 관리하기위한 요령 (0) | 2020.02.14 |
주어진 문자열 패턴을 포함하지 않는 파일은 어떻게 찾습니까? (0) | 2020.02.14 |