Development Tip

char *와 std :: uint8_t * 사이의 reinterpret_cast-안전합니까?

yourdevel 2020. 12. 31. 23:00
반응형

char *와 std :: uint8_t * 사이의 reinterpret_cast-안전합니까?


이제 우리 모두는 때때로 이진 데이터로 작업해야합니다. C ++에서 우리는 일련의 바이트로 작업하며 처음부터 char우리의 빌딩 블록이었습니다. sizeof1로 정의되어 있는 바이트입니다. 그리고 모든 라이브러리 I / O 기능 char은 기본적으로 사용 됩니다. 모든 것이 좋지만 항상 약간의 우려가 있었고 일부 사람들을 괴롭히는 약간의 이상한 점이있었습니다. 바이트의 비트 수는 구현에 따라 정의됩니다.

따라서 C99에서는 개발자가 고정 너비 정수 유형 인 자신을 쉽게 표현할 수 있도록 여러 typedef를 도입하기로 결정했습니다. 물론 이식성을 손상시키고 싶지 않기 때문에 선택 사항입니다. 그중 에서 고정 너비 8 비트 부호없는 정수 유형 인 uint8_tC ++ 11로 마이그레이션 std::uint8_t된은 실제로 8 비트 바이트로 작업하려는 사람들에게 완벽한 선택이었습니다.

그래서, 개발자들은 같은 8 비트 바이트 시퀀스에 동의 함을 표정 상태라는 새로운 도구와 구축을 시작 라이브러리를 받아 std::uint8_t*, std::vector<std::uint8_t>또는 그렇지.

그러나 아마도 매우 깊은 생각으로 표준화위원회는 std::char_traits<std::uint8_t>개발자가 s를 이진 데이터로 std::basic_fstream<std::uint8_t>쉽고 이식 가능하게 인스턴스화 하고 쉽게 읽을 수 없도록 하는 구현을 요구하지 않기로 결정했습니다 std::uint8_t. 또는 우리 중 일부는 바이트의 비트 수에 관심이없고 만족할 수도 있습니다.

그러나 불행하게도, 두 세계가 충돌하고 때로는 같은 데이터를 가지고 가야 char*하고 기대하는 도서관에 전달 std::uint8_t*. 그러나 잠깐, char가변 비트 가 아니고 std::uint8_t8로 고정되어 있습니까? 데이터 손실이 발생합니까?

음, 이것에 대한 흥미로운 Standardese가 있습니다. char정확히 하나의 바이트를 유지하도록 정의 바이트의 메모리가 낮은 어드레싱 청크, 그래서보다 적은 폭 비트 형태가 될 수 없다 char. 다음으로 UTF-8 코드 단위를 보유 할 수 있도록 정의됩니다. 이것은 우리에게 최소 8 비트를 제공합니다. 이제 우리는 8 비트 너비가 필요한 typedef와 최소 8 비트 너비의 유형이 있습니다. 그러나 대안이 있습니까? 예, unsigned char. 의 서명 char은 구현에 따라 정의됩니다. 다른 유형 은요? 고맙게도 아닙니다. 다른 모든 정수 유형에는 8 비트를 벗어나는 필수 범위가 있습니다.

마지막으로은 std::uint8_t선택 사항입니다. 즉,이 유형을 사용하는 라이브러리는 정의되지 않은 경우 컴파일되지 않습니다. 하지만 컴파일되면 어떨까요? 나는 이것이 우리가 8 비트 바이트와 CHAR_BIT == 8.

이 지식이 있으면 또는 std::uint8_t로 구현되는 8 비트 바이트가 있다는 사실을 알게 되면 from to 또는 그 반대로 할 수 있다고 가정 할 수 있습니까? 휴대용입니까?charunsigned charreinterpret_castchar*std::uint8_t*

이것이 나의 Standardese 읽기 능력이 저를 실패하는 곳입니다. 안전하게 파생 된 포인터 ( [basic.stc.dynamic.safety]) 에 대해 읽었 으며 내가 이해하는 한 다음을 읽었습니다 .

std::uint8_t* buffer = /* ... */ ;
char* buffer2 = reinterpret_cast<char*>(buffer);
std::uint8_t buffer3 = reinterpret_cast<std::uint8_t*>(buffer2);

만지지 않으면 안전합니다 buffer2. 틀 렸으면 말해줘.

따라서 다음과 같은 전제 조건이 주어집니다.

  • CHAR_BIT == 8
  • std::uint8_t 정의됩니다.

우리가 바이너리 데이터로 작업하고 있고 잠재적 인 부호 부족이 중요하지 않다고 가정 할 때 캐스트 char*std::uint8_t*앞뒤로 이동하는 것이 이식 가능하고 안전 char합니까?

설명과 함께 표준에 대한 참조를 부탁드립니다.

편집 : 감사합니다, Jerry Coffin. 표준 ([basic.lval], §3.10 / 10)의 인용문을 추가하겠습니다.

프로그램이 다음 유형 중 하나가 아닌 다른 glvalue를 통해 객체의 저장된 값에 액세스하려고하면 동작이 정의되지 않습니다.

...

— char 또는 unsigned char 유형.

EDIT2 : 좋아, 더 깊이 들어가. std::uint8_ttypedef가라는 보장은 없습니다 unsigned char. 확장 된 부호없는 정수 유형 으로 구현할 수 있으며 확장 된 부호없는 정수 유형은 §3.10 / 10에 포함되지 않습니다. 이제 뭐야?


좋아, 진정으로 현학 해보자. , thisthis를 읽은 후 두 표준의 의도를 이해한다고 확신합니다.

따라서 결과 포인터 reinterpret_caststd::uint8_t*to 에서 char*역 참조하는 것은 안전 하고 이식 가능하며 [basic.lval]에 의해 명시 적으로 허용됩니다 .

그러나 일을 reinterpret_cast에서 char*에게 std::uint8_t*다음 결과 포인터를 역 참조하는 것은 위반입니다 엄격한 앨리어싱 규칙 이고 정의되지 않은 동작을 하는 경우 std::uint8_t로 구현 확장 된 부호없는 정수 타입 .

그러나 다음과 같은 두 가지 가능한 해결 방법이 있습니다.

static_assert(std::is_same_v<std::uint8_t, char> ||
    std::is_same_v<std::uint8_t, unsigned char>,
    "This library requires std::uint8_t to be implemented as char or unsigned char.");

이 어설 션을 사용하면 코드가 정의되지 않은 동작이 발생하는 플랫폼에서 컴파일되지 않습니다.

둘째:

std::memcpy(uint8buffer, charbuffer, size);

Cppreferencestd::memcpy객체를 배열로 액세스 unsigned char하므로 안전 하고 이식 가능 하다고 말합니다 .

투자 의견에, 위해 할 수있는 reinterpret_cast사이 char*std::uint8_t*작업 포인터를 결과와 이식안전 100 % 표준 부합하는 방법으로, 다음 조건이 충족되어야합니다 :

  • CHAR_BIT == 8.
  • std::uint8_t 정의됩니다.
  • std::uint8_tchar또는 로 구현됩니다 unsigned char.

실질적으로 위의 조건은 플랫폼의 99 %에서 참이며 첫 번째 두 조건이 참이고 세 번째 조건이 거짓 인 플랫폼이 없을 가능성이 높습니다.


uint8_t존재하는 경우 본질적으로 유일한 선택은 typedef unsigned char(또는 char서명되지 않은 경우)입니다. 어떤 것도 (비트 필드를 제외하고) a보다 적은 저장 공간을 나타낼 char수 없으며 8 비트만큼 작을 수있는 유일한 다른 유형은 bool. 다음으로 가장 작은 일반 정수 유형은이며 short, 이는 16 비트 이상이어야합니다.

경우에 따라서, uint8_t모든 존재, 당신은 정말 단 두 가지 가능성이있다 : 당신이 중 하나를 캐스팅하고 unsigned charunsigned char, 또는 주조 signed charunsigned char.

전자는 신원 변환이므로 분명히 안전합니다. 후자는 §3.10 / 10의 char 또는 unsigned char 시퀀스로 다른 유형에 액세스하기 위해 제공된 "특별한 경륜"에 속하므로 정의 된 동작도 제공합니다.

Since that includes both char and unsigned char, a cast to access it as a sequence of char also gives defined behavior.

Edit: As far as Luc's mention of extended integer types goes, I'm not sure how you'd manage to apply it to get a difference in this case. C++ refers to the C99 standard for the definitions of uint8_t and such, so the quotes throughout the remainder of this come from C99.

§6.2.6.1/3 specifies that unsigned char shall use a pure binary representation, with no padding bits. Padding bits are only allowed in 6.2.6.2/1, which specifically excludes unsigned char. That section, however, describes a pure binary representation in detail -- literally to the bit. Therefore, unsigned char and uint8_t (if it exists) must be represented identically at the bit level.

To see a difference between the two, we have to assert that some particular bits when viewed as one would produce results different from when viewed as the other -- despite the fact that the two must have identical representations at the bit level.

To put it more directly: a difference in result between the two requires that they interpret bits differently -- despite a direct requirement that they interpret bits identically.

Even on a purely theoretical level, this appears difficult to achieve. On anything approaching a practical level, it's obviously ridiculous.

ReferenceURL : https://stackoverflow.com/questions/16260033/reinterpret-cast-between-char-and-stduint8-t-safe

반응형