유형 펀닝

Type punning

컴퓨터 과학에서 유형 펀칭은 형식 언어의 범위 내에서 달성하기 어렵거나 불가능할 수 있는 효과를 얻기 위해 프로그래밍 언어유형 시스템을 전복하거나 우회하는 프로그래밍 기법이다.null

C 및 C++에서 포인터 유형 변환union— C++는 기준 유형 변환을 추가하며reinterpret_cast이 목록에는 — 비록 어떤 종류는 실제로 표준 언어에 의해 지원되지 않지만, 많은 종류의 펀닝을 허용하기 위해 제공된다.null

Pascal 프로그래밍 언어에서, 변형있는 레코드의 사용은 둘 이상의 방식으로 또는 일반적으로 허용되지 않는 방식으로 특정 데이터 형식을 처리하는 데 사용될 수 있다.null

소켓 예제

버클리 소켓 인터페이스에서 펀닝 유형의 고전적인 예가 발견된다.개방되었지만 초기화되지 않은 소켓을 IP 주소에 바인딩하는 기능은 다음과 같이 선언된다.

인트로 제본하다(인트로 양말의, 구조상의 삭카드르 *my_addr, socklen_t 부속의); 

bind함수는 보통 다음과 같이 부른다.

구조상의 삭카드르_인 sa = {0}; 인트로 양말의 = ...; sa.sin_가족 = AF_INET; sa.sin_port = 깡패들(입항하다); 제본하다(양말의, (구조상의 삭카드르 *)&sa, 의 크기 sa); 

버클리 소켓 라이브러리는 기본적으로 C에서 에 대한 포인터가struct sockaddr_in자유롭게 에 대한 포인터로 전환할 수 있다.struct sockaddr또한, 두 구조 유형이 동일한 메모리 레이아웃을 공유한다.따라서, 구조물 필드에 대한 참조my_addr->sin_family(어디서)my_addr형식이다.struct sockaddr*)은 실제로 필드를 가리킨다.sa.sin_family(어디서)sa형식이다.struct sockaddr_in즉, 소켓 라이브러리는 다형성 또는 상속의 초보적인 형태를 구현하기 위해 유형 펀닝을 사용한다.null

프로그래밍 세계에서 흔히 볼 수 있는 것은 사실상 동일한 스토리지 공간에서 다양한 종류의 값을 저장할 수 있도록 "패딩된" 데이터 구조를 사용하는 것이다.이는 최적화를 위해 상호 배타성에 두 구조물을 사용할 때 종종 나타난다.null

부동 소수점 예제

이전의 사례처럼, 유형 펀닝의 모든 예가 구조를 포함하는 것은 아니다.부동 소수점 번호가 음수인지 여부를 확인하려고 한다고 가정합시다.우리는 다음과 같이 쓸 수 있다.

바가지 긁다 is_negative(둥둥 뜨다 x) {     돌아오다 x < 0.0; } 

그러나 부동소수점 비교가 비용이 많이 든다고 가정하고, 또한 이를 가정한다.floatIEEE 부동 소수점 표준에 따라 표시되며 정수는 32비트 폭이며, 정수 연산만을 사용하여 부동 소수점 숫자의 기호 비트를 추출하기 위해 유형 펀닝을 할 수 있다.

바가지 긁다 is_negative(둥둥 뜨다 x) {     서명이 없는 인트로 *ui = (서명이 없는 인트로 *)&x;     돌아오다 *ui & 0x80000000; } 

동작이 정확히 동일하지는 않을 것이라는 점에 유의하십시오.x번째 구현 결과인 마이너스 영(0)이 됨false두 번째가 되는 동안에true. 또한, 첫 실행은 돌아올 것이다.false모든 NaN 값에 대해, 그러나 후자는 반환될 수 있다.true부호 비트가 설정된 NaN 값의 경우null

이런 종류의 말장난은 대부분의 사람들보다 더 위험하다.전자의 예는 구조 배치와 포인터 변환성에 관한 C 프로그래밍 언어에 의해서 만들어진 보증에만 의존한 반면, 후자의 예는 특정 시스템의 하드웨어에 관한 가정에 의존한다.컴파일러가 최적화하지 못하는 시간 임계 코드와 같은 일부 상황은 위험한 코드를 요구할 수 있다.이러한 경우, 그러한 가정을 모두 코멘트로 문서화하고, 이식성 기대치를 검증하기 위한 정적 주장을 도입하는 것은 코드를 유지할 수 있도록 유지하는 데 도움이 된다.null

부동소수점 펀닝의 실제 예로는 쯔진3에 의해 대중화된 빠른 역제곱근, 정수로써 빠른 FP [1]비교, 정수로 증분하여 근접한 값 찾기(이행) 등이 있다.nextafter).[2]

언어별

C와 C++

부동 소수점의 비트 표시에 대한 가정 외에도, 위의 부동 소수점 유형 쌍 구성 예제는 또한 개체의 접근 방식에 대한 C 언어의 제약 조건, [3]즉 선언된 유형의 제한 사항을 위반한다.x이다float그러나 그것은 활자의 표현을 통해 읽힌다.unsigned int많은 공통 플랫폼에서 포인터 펀칭의 이러한 사용은 서로 다른 포인터가 기계 특정 방식으로 정렬될 경우 문제를 일으킬 수 있다.또한 크기가 다른 포인터는 동일한 메모리에 대한 액세스에 별칭을 붙일 수 있어 컴파일러가 확인하지 않은 문제를 일으킬 수 있다.null

포인터의 사용

다음과 같은 포인터를 사용하여 순진한 유형 연산을 시도할 수 있다.

둥둥 뜨다 파이 = 3.14159; uint32_t piAsRawData = *(uint32_t*)&파이; 

C 표준에 따르면, 이 코드는 컴파일하지 않아야 한다(또는 컴파일하지 않아도 된다). 단, 컴파일할 경우, 컴파일하지 않아야 한다.piAsRawData전형적으로 파이 원비트를 포함한다.null

의 사용union

a를 사용하여 유형 패닝을 고치려 하는 것은 흔히 있는 실수다.union(다음 예에서는 부동 소수점 유형에 대해 IEEE-754 비트 표현을 추가로 가정한다.)null

바가지 긁다 is_negative(둥둥 뜨다 x) {     결합하다 {         서명이 없는 인트로 ui;         둥둥 뜨다 d;     } my_union = { .d = x };     돌아오다 my_union.ui & 0x80000000; } 

액세스my_union.ui다른 멤버를 초기화한 후my_union.d는 여전히 C에서 타입-퍼닝의[4] 한 형태고 그 결과는 불특정 행동[5](그리고 C++ 에서는 정의되지 않은 행동)이다.null

§ 6.5/7의[3] 언어는 대체 조합원 읽기가 허용된다는 것을 암시하기 위해 잘못 읽힐 수 있다.그러나 본문에는 "개체는 오직…에 의해서만 저장된 값에 접근해야 한다."라고 적혀 있다.어느 것이 마지막으로 저장되었든 간에 가능한 모든 조합원들에게 접근할 수 있다는 말이 아니라 제한적인 표현이다.그래서, 그 사용법은union단순히 포인터를 직접 펀칭하는 것만으로 문제가 발생하지 않도록 한다.null

컴파일러는 유형 펀닝을 지원하지 않을 경우 경고나 오류를 보고할 가능성이 낮기 때문에 포인터를 사용한 유형 펀칭보다 덜 안전한 것으로 간주될 수 있다.null

GCC와 같은 컴파일러는 위의 예와 같은 별칭 가능한 값 액세스를 언어 확장으로 지원한다.[7]그러한 확장자가 없는 컴파일러에서 엄격한 별칭 규칙은 명시적인 memcpy에 의해서만 또는 "중간인"으로 문자 포인터를 사용함으로써 깨진다(이들은 자유롭게 별칭이 가능하기 때문이다).null

펀칭 유형의 다른 예는 배열의 Stride를 참조하십시오.null

파스칼

변종 기록은 어떤 변종이 참조되고 있는지에 따라 데이터 유형을 여러 종류의 데이터로 취급하는 것을 허용한다.다음 예에서 정수는 16비트로 추정되고, 긴트실제는 32비트로 추정되며, 문자는 8비트로 추정된다.

타자를 치다     변형 레코드 = 기록하다         케이스 recType : 롱인트              1: (I : 배열하다[1..2]  정수);  (* 여기에 표시되지 않음: 변종 레코드의 사례 문 *에 여러 변수가 있을 수 있음)             2: (L : 롱인트               );             3: (R : 진짜                  );             4: (C : 배열하다[1..4]  차르   );         종지부를 찍다;  시합을 하다     V  : 변형 레코드;     K  : 정수;     LA : 롱인트;     RA : 진짜;     Ch : 캐릭터;   V.I[1] := 1; Ch     := V.C[1];  (* 이렇게 하면 V의 첫 번째 바이트를 추출할 수 있다.I *) V.R    := 8.3;    LA     := V.L;     (* 이렇게 하면 Real이 정수에 저장됨 *) 

Pascal에서 실제를 정수로 복사하면 잘린 값으로 변환된다.이 방법은 부동 소수점 숫자의 이진수 값을 긴 정수(32비트)로 변환하는데, 이는 동일하지 않고 일부 시스템의 긴 정수 값과 호환되지 않을 수 있다.null

이러한 예는 이상한 변환을 만드는 데 사용될 수 있지만, 어떤 경우에는 특정 데이터 조각의 위치를 결정하는 것과 같은 이러한 유형의 구성에 대해 합법적인 용도가 있을 수 있다.다음 예에서 포인터와 긴장은 모두 32비트로 가정한다.

타자를 치다     PA = ^아렉;      아렉 = 기록하다         케이스 RT : 롱인트              1: (P : PA     );             2: (L : 롱인트);         종지부를 찍다;  시합을 하다     PP : PA;     K  : 롱인트;   새로 만들기(PP); PP^.P := PP; WriteLn('변수 PP는 주소 '에 위치한다., 육각(PP^.L)); 

여기서 "new"는 포인터의 메모리를 할당하기 위한 Pascal의 표준 루틴이고, "hex"는 아마도 정수의 값을 설명하는 16진수 문자열을 인쇄하는 루틴일 것이다.이렇게 하면 일반적으로 허용되지 않는 포인터 주소를 표시할 수 있다. (점수는 읽거나 쓸 수 없고, 할당만 된다.)포인터의 정수 변형에 값을 할당하면 시스템 메모리의 모든 위치에 검사하거나 쓸 수 있다.

PP^.L := 0; PP    := PP^.P;  (* PP는 이제 0을 가리킨다 *) K     := PP^.L;  (* K는 워드 0의 값을 포함함 *) WriteLn('이 기계의 워드 0은 '을 포함한다., K); 

이 구조는 프로그램이 실행 중인 컴퓨터 또는 프로그램이 실행 중인 운영 체제에서 주소 0이 판독되지 않도록 보호되는 경우 프로그램 검사 또는 보호 위반을 유발할 수 있다.null

C/C++의 재해석된 캐스팅 기법도 파스칼에서 통한다.이것은 바이트 스트림에서 dword를 읽을 때 유용할 수 있으며, 우리는 그것들을 부유물로 취급하기를 원한다.여기에 드워드를 플로트에 재해석하는 작업 예가 있다.

타자를 치다     pReal = ^진짜;  시합을 하다     DW : DWord;     F  : 진짜;  F := pReal(@DW)^; 

C#

C#(및 기타).NET 언어), 유형 펀칭은 유형 시스템 때문에 달성하기가 조금 어렵지만, 그럼에도 불구하고 포인터나 구조 결합을 사용하여 할 수 있다.null

포인터

C#는 소위 네이티브 유형, 즉 원시 유형에만 포인터를 허용한다(예: 제외).string(), 다른 네이티브 유형으로만 구성된 열거형, 배열 또는 구조체.포인터는 '안전하지 않음'으로 표시된 코드 블록에서만 허용된다는 점에 유의하십시오.null

둥둥 뜨다 파이 = 3.14159; 유인트 piAsRawData = *(유인트*)&파이; 

구조조합

구조 조합은 '안전하지 않은' 코드라는 개념 없이 허용되지만, 새로운 유형의 정의를 필요로 한다.null

[StructLayout(LayoutKind).명시적)] 구조상의 플로트앤드UIntUntUnion { [FieldOffset(0)]     공중의 둥둥 뜨다 DataAsFlat;  [FieldOffset(0)]     공중의 유인트 DataAsUInt; }  // ...  플로트앤드UIntUntUnion 결합하다; 결합하다.DataAsFlat = 3.14159; 유인트 piAsRawData = 결합하다.DataAsUInt; 

원시 CIL 코드

C# 대신 원시 CIL을 사용할 수 있는데, 이는 대부분의 유형 제한이 없기 때문이다.이를 통해 예를 들어 일반 유형의 두 가지 열거값을 조합할 수 있다.

TEnum a = ...; TEnum b = ...; TEnum 합쳐진 = a   b; // 불법 

이는 다음 CIL 코드로 우회할 수 있다.

.방법 공중의 정태의 숨바꼭질     !!TEnum 콤바인이넘<밸류티프 .콕콕 찌르다 ([말괄량이]시스템.ValueType) TEnum>(         !!TEnum a,         !!TEnum b     ) 꼬드기다 관리되는. {     .맥스택 2      ldarg..0      ldarg..1     또는  // a와 b는 유형이 같고 따라서 크기가 같기 때문에 오버플로를 일으키지 않는다.     되받아치다 } 

cpblkCIL opcode는 구조체를 바이트 배열로 변환하는 것과 같은 몇 가지 다른 트릭을 허용한다.

.방법 공중의 정태의 숨바꼭질     uint8[] ToByteArray<밸류티프 .콕콕 찌르다 ([말괄량이]시스템.ValueType) T>(         !!T& v // C#의 'ref T'     ) 꼬드기다 관리되는. {     .현지인 초기화하다 ( [0] uint8[]     )      .맥스택 3      // 길이 크기(T)로 새 바이트 배열을 만들어 로컬 0에 저장     의 크기 !!T     뉴어러 uint8     속이다           // 나중에 사용할 수 있도록 복사본을 스택에 보관(1)     장골의.0      ldc.i4.0     이델레마 uint8      // memcpy(로컬 0, &v, sizeof(T));     // <배열이 여전히 스택에 있음, (1) 참조>     ldarg..0 // 'v'의 *address*인데, 그 유형은 '!!T&'     의 크기 !!T     cpblk      ldloc.0     되받아치다 } 

참조

  1. ^ Herf, Michael (December 2001). "radix tricks". stereopsis : graphics.
  2. ^ "Stupid Float Tricks". Random ASCII - tech blog of Bruce Dawson. 24 January 2012.
  3. ^ a b ISO/IEC 9899:1999 s6.5/7
  4. ^ "§ 6.5.2.3/3, footnote 97", ISO/IEC 9899:2018 (PDF), 2018, p. 59, archived from the original (PDF) on 2018-12-30, If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called “type punning”). This might be a trap representation.
  5. ^ "§ J.1/1, bullet 11", ISO/IEC 9899:2018 (PDF), 2018, p. 403, archived from the original (PDF) on 2018-12-30, The following are unspecified: … The values of bytes that correspond to union members other than the one last stored into (6.2.6.1).
  6. ^ ISO/IEC 14882:2011 섹션 9.5
  7. ^ GCC: 비벅스

외부 링크

  • GCC 매뉴얼 섹션: 일부 유형 펀닝을 삭제함
  • C99 표준에 대한 결함 보고서 257, 우연히 다음과 같은 측면에서 "형식 펀닝"을 정의함union, 그리고 위의 마지막 예제의 구현 정의 동작과 관련된 문제에 대해 논의한다.
  • 유형 펀닝을 위한 유니언 사용에 관한 결함 보고서 283