2017-11-16 3 views
4

다음 예제에서 uint32_t의 값 표현은 uint8_t 배열로 복사됩니다. 이것은 std::memcpy에 의해 수행됩니다. C++ 표준을 이해함에 따라 이것은 완전히 합법적입니다. 에 캐스팅 된 T*을 통해 T 유형의 객체에 액세스하고 있습니다. 앨리어싱 문제가없고 정렬 문제가 없습니다.std :: memcpy 또는 명시 적 char 값 할당 - C++에서 동일하고 유효합니다.

다른 방법으로는 덜 명확합니다. unsigned char*을 통해 T의 객체 표현에 액세스하고 있습니다. 이는 유효합니다. 그러나 에 액세스 할 때이 변경되면이 포함됩니까?

물론 앨리어싱과 앨리어스 문제는 없습니다. 버퍼 s의 값이 외국 출처 인 경우에는 문제가 있습니다. 올바른 엔디안을 보장하고 트랩 표현을 생략해야합니다. 오른쪽 엔디안을 확인할 수 있으므로이를 해결할 수 있습니다. 그러나 함정 표현은 어떨까요? 우리는 어떻게 그것을 피할 수 있습니까? 또는 uint 유형에 double과 반대되는 트랩 표현이 없습니까?

uint_t 개체로 uint8_t 값을 이동하는 방법이 더 적합하다는 것을 알고 있습니다. 우리는 여전히 엔디안에 복종해야하지만 이것은 함정 표현을 안전하게 생략해야합니다.

그러나 작은 μC (8 비트)에서 큰 유형의 시프트는 매우 비쌀 수 있습니다!

두 번째 시도 (코드의 아래 참조)가 적법성 및 기능과 관련하여 memcpy 접근 방식과 동일한 지 여부가 다음 질문입니다. 글쎄, 마치 memcpy 버전이 더 최적화 된 옵티 마이저 인 것처럼 보입니다.

#include <cstdint> 
#include <cstring> 
#include <cassert> 

typedef uint32_t utype; 

constexpr utype value = 0x01020304; 

int main() { 
    utype a{value}; 
    utype b{0}; 
    uint8_t s[sizeof(utype)]{}; 

    // first  
    std::memcpy(s, &a, sizeof(utype)); 
    assert(s[0] == (value & 0xff)); 

    std::memcpy(&b, s, sizeof(utype)); 
    assert(b == value); 

    // second  

    const uint8_t* ap = reinterpret_cast<const uint8_t*>(&a); 
    s[0] = ap[0]; // explicitly legal in C++ 
    s[1] = ap[1]; 
    s[2] = ap[2]; 
    s[3] = ap[3]; 
    assert(s[0] == (value & 0xff)); 

    uint8_t* bp = reinterpret_cast<uint8_t*>(&b); 
    bp[0] = s[0]; // same as memcpy or ist this UB ? 
    bp[1] = s[1]; 
    bp[2] = s[2]; 
    bp[3] = s[3]; 
    assert(b == value); 
} 
+0

'std :: memcpy'를 할 때 다른 타입을 가리키는 포인터로 객체에 접근하지 않고 포인터의 지적 타입의 객체에 접근하고 있지만 소스 객체와 같은 값 표현을 가지고 있습니다. –

+3

'typedef uint32_t utype;을 사용하지 말아주십시오. 그러면 코드가 읽기 쉽게됩니다. 나는 다른 사람들의 이름으로 말하고 싶지 않기 때문에, 나는 자신을 위해 말할 것이지만, 나는 그것이 더 많은 프로그래머에게 적용된다고 생각한다. 내가'utype a '를 볼 때,'utype'이 무엇인지 알아내는 추가인지 단계가 있습니다. 그리고 내 마음 속의 전체 코드를 읽는 동안 나는 "utype"이'std :: uint32_t'이고,'utype'은'std :: uint32_t'; .... "가 반복되는 백그라운드 프로세스를 가져야한다. 그러나'std :: uint32_t a; '를 보면 그것에 대해서 생각조차하지 않습니다. 나는 즉시, 거의 본능적으로, 그것이 무엇인지를 안다. – bolov

+1

* access *는 읽거나 쓰는 것을 의미합니다 –

답변

3

그러나 용어 액세스는 변경 포함되어 있습니까?

예.

참고 : 실제로 memcpy가 개념적으로하는 것입니다. 바이트가 좁은 문자 객체 인 것처럼 바이트를 수정합니다. 이것이 가능하지 않은 경우 memcpy를 표준 C++로 구현할 수 없습니다.

하지만 트랩 표현은 어떻게됩니까? 우리는 어떻게 그것을 피할 수 있습니까?

이것은 매우 까다 롭습니다. 트랩 표현을 알고 있다면 트랩 표현이있는 유형의 값을 사용하기 전에 객체의 좁은 문자보기를 사용하여 테스트해야합니다. 함정 표현을 다루는 표준 방법이 있는지 나는 모른다.

이 문제를 해결하기 위해 std::is_trap<T>(void*) 특성이 있어야 할 수도 있지만, 내가 아는 한 멀리 있지는 않습니다.

또 다른 (더 준수하는?) 방법은 uint8_t 값을 uint_t 객체로 이동하는 것입니다. 우리는 여전히 엔디안에 복종해야하지만 이것은 함정 표현을 안전하게 생략해야합니다.

외부 값이 트랩 표현 인 경우 그 값은 어쨌든 나타낼 수 없기 때문에이 경우에는 오버 플로우와 같은 다른 문제가있을 수 있습니다.

시프 팅과 memcpy의 차이점은 소스 코드가 이미 원시 엔디안을 가지고있을 때 memcpy가 작동하는 동안 시프 팅은 알려진 엔디 언을 자연어 엔디안으로 변환 할 수 있다는 것입니다.

uint8_t는 다음 두 번째 조각이 잘 정의 될 수 및 방어 적이기 기능적으로 동일 unsigned char의 별칭이라고 보장된다면

. 그것이 보장되는지 나는 모른다. 그러나 그것은 확실히 일반적이다. 좁은 문자 유형에만 포인터 앨리어싱 규칙에 대한 예외가 있습니다.


assert(s[0] == (value & 0xff)); 

이 어설는 CPU의 엔디안에 의존한다.

+0

memcpy를 표준 C++로 구현할 수 있는지 여부는 관련이 없습니다 (표준에서는 그런 것이 필요하지 않습니다). –

+0

@ M.M 그리고 그런 것이 필요하다는 것을 의미하지는 않습니다. 가치가있는 것을 위해, 나는 memcpy가 C++에서 구현 될 수있는 좋은 설계 선택이라고 생각한다. – user2079303

+0

필자는 문장을 별도의 노트로 분리하여 인수와 혼동하지 않도록했습니다. – user2079303

관련 문제