programing

엔디안 불가지론자 C/C++ 코드를 작성하는 방법은?

css3 2023. 9. 2. 08:45

엔디안 불가지론자 C/C++ 코드를 작성하는 방법은?

저는 구글 검색을 좀 해봤는데 이 질문에 대한 좋은 기사를 찾을 수 없었습니다.엔디안 애그노스틱이 되고 싶은 앱을 구현할 때 주의해야 할 사항은 무엇입니까?

엔디언에 대해 신경을 써야 하는 유일한 경우는 엔디언에 민감한 이진 데이터(즉, 텍스트가 아님)를 동일한 엔디언을 가지지 않을 수 있는 시스템 간에 전송할 때입니다.일반적인 솔루션은 "네트워크 바이트 순서"(일명 빅 엔디언)를 사용하여 데이터를 전송한 다음 필요한 경우 다른 쪽에서 바이트를 스위즐하는 것입니다.

호스트에서 네트워크 바이트 순서로 변환하려면 다음을 사용합니다.htons(3)그리고.htonl(3)하려면 다시변려면하환, 용사용을 합니다.ntohl(3)그리고.ntohs(3)당신이 알아야 할 모든 것을 위해 man 페이지를 확인하세요.64비트 데이터의 경우 이 질문과 답변이 도움이 될 것입니다.

엔디안 애그노스틱이 되고 싶은 앱을 구현할 때 주의해야 할 사항은 무엇입니까?

먼저 엔디안이 문제가 될 때를 인식해야 합니다.파일에서 데이터를 읽거나 컴퓨터 간의 네트워크 통신을 수행하는 등 외부에서 데이터를 읽거나 써야 하는 경우가 대부분 문제가 됩니다.

이러한 경우에는 정수가 다른 플랫폼에 의해 메모리에서 다르게 표현되기 때문에 바이트보다 큰 정수에 대한 엔디엔시가 중요합니다.즉, 외부 데이터를 읽거나 써야 할 때마다 프로그램의 메모리를 버리거나 데이터를 직접 변수로 읽는 것 이상의 작업을 수행해야 합니다.

예: 다음 코드 조각이 있는 경우:

unsigned int var = ...;
write(fd, &var, sizeof var);

당신은 당신의 기억 내용을 직접적으로 적는 것입니다.var즉, 데이터가 사용자 컴퓨터의 메모리에 표시되는 것처럼 데이터가 어디로 가든 표시됩니다.

이 데이터를 파일에 쓰면 빅 엔디안에서 프로그램을 실행하든 작은 엔디안 컴퓨터에서 실행하든 파일 내용이 달라집니다.그래서 그 코드는 엔디안 불가지론자가 아닙니다. 그리고 여러분은 이런 것들을 피하고 싶을 것입니다.

대신 데이터 형식에 초점을 맞춥니다.데이터를 읽고 쓸 때는 항상 데이터 형식을 먼저 결정한 다음 이를 처리할 코드를 작성합니다.기존의 잘 정의된 파일 형식을 읽거나 기존의 네트워크 프로토콜을 구현해야 하는 경우 이미 결정되었을 수 있습니다.

일단 데이터 형식을 알게 되면, 예를 들어 int 변수를 직접 버리는 대신 코드는 다음을 수행합니다.

uint32_t i = ...;
uint8_t buf[4];
buf[0] = (i&0xff000000) >> 24;
buf[1] = (i&0x00ff0000) >> 16;
buf[2] = (i&0x0000ff00) >> 8;
buf[3] = (i&0x000000ff);
write(fd, buf, sizeof buf);

이제 가장 중요한 바이트를 선택하여 버퍼의 첫 번째 바이트로 배치하고 버퍼의 끝에 가장 중요하지 않은 바이트로 배치했습니다.로 빅 엔디안 형식으로 표현됩니다.buf호스트의 엔디안에 관계없이 이 코드는 엔디안 애그노스틱입니다.

이 데이터의 소비자는 데이터가 빅 엔디안 형식으로 표시된다는 것을 알아야 합니다.프로그램이 실행되는 호스트에 관계없이 이 코드는 해당 데이터를 올바르게 읽을 수 있습니다.

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[0] << 24;
i |= (uint32_t)buf[1] << 16;
i |= (uint32_t)buf[2] << 8;
i |= (uint32_t)buf[3];

반대로, 당신이 읽어야 할 데이터가 약간의 엔디안 형식인 것으로 알려진다면 엔디안 불가지론 코드는 그냥 할 것입니다.

uint32_t i ;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i  = (uint32_t)buf[3] << 24;
i |= (uint32_t)buf[2] << 16;
i |= (uint32_t)buf[1] << 8;
i |= (uint32_t)buf[0];

필요한 2,4,8 바이트 정수 유형을 모두 랩하고 풀 수 있는 멋진 인라인 함수나 매크로를 만들 수 있습니다. 그리고 만약 당신이 그것들을 사용하고 당신이 실행하는 프로세서의 엔디안이 아닌 데이터 형식에 관심이 있다면, 당신의 코드는 그것이 실행되는 엔디안에 의존하지 않을 것입니다.

이는 다른 많은 솔루션보다 더 많은 코드입니다. 이 추가 작업이 1Gbps 이상의 데이터를 이동하더라도 성능에 의미 있는 영향을 미치는 프로그램을 아직 작성하지 못했습니다.

또한 예를 들어 쉽게 얻을 수 있는 잘못 정렬된 메모리 액세스를 방지합니다.

uint32_t i;
uint8_t buf[4];
read(fd, buf, sizeof buf);
i = ntohl(*(uint32_t)buf));

이는 기껏해야 성능 저하(일부에서는 상당한 수준, 다른 일부에서는 상당한 규모)를 초래할 수 있으며, 정수에 대한 비정렬 액세스를 수행할 수 없는 플랫폼에서는 더 심각한 충돌을 초래할 수 있습니다.

이 기사는 다음과 같은 내용을 읽어 보시기 바랍니다.바이트 순서 오류

컴퓨터의 바이트 순서는 레지스터 조각에 매핑된 메모리의 바이트 할당에 대해 까다로운 컴파일러 작성자 등을 제외하고는 전혀 중요하지 않습니다.당신은 컴파일러 작성자가 아니기 때문에 컴퓨터의 바이트 순서는 조금도 문제가 되지 않습니다.

"컴퓨터의 바이트 순서"라는 문구를 주의하십시오.중요한 것은 주변 장치 또는 인코딩된 데이터 스트림의 바이트 순서이지만, 처리를 수행하는 컴퓨터의 바이트 순서는 데이터 자체의 처리와 무관합니다.데이터 스트림이 바이트 순서 B로 값을 인코딩하는 경우, 바이트 순서 C로 컴퓨터에서 값을 디코딩하는 알고리즘은 B와 C 사이의 관계가 아니라 B에 관한 것이어야 합니다.

여러 답변에서 파일 IO에 대해 다루었는데, 이는 확실히 가장 일반적인 엔드포인트 문제입니다.아직 언급되지 않은 것을 언급하겠습니다.조합.

다음 조합은 SIMD/SSE 프로그래밍에서 일반적인 도구이며 엔디안 친화적이지 않습니다.

union uint128_t {
    _m128i      dq;
    uint64_t    dd[2];
    uint32_t    dw[4];
    uint16_t    dh[8];
    uint8_t     db[16];
};

dd/dw/dh/db 형식에 액세스하는 모든 코드는 엔디언별 방식으로 액세스합니다.32비트 CPU에서는 64비트 산술을 32비트 부분으로 더 쉽게 분할할 수 있는 단순한 조합을 보는 것이 일반적입니다.

union u64_parts {
    uint64_t    dd;
    uint32_t    dw[2];
};

이 사용 사례에서는 조합의 각 요소에 대해 반복하고 싶은 경우가 드물기 때문에 다음과 같은 조합을 작성하는 것이 좋습니다.

union u64_parts {
    uint64_t dd;
    struct {
#ifdef BIG_ENDIAN
        uint32_t dw2, dw1;
#else
        uint32_t dw1, dw2;
#endif
    }
};

그 결과 dw1/dw2에 직접 액세스하는 모든 코드에 대해 암묵적인 endian-swap이 발생합니다.위의 128비트 SIMD 데이터 유형에도 동일한 설계 접근 방식을 사용할 수 있지만, 결과적으로 훨씬 더 장황합니다.

고지 사항:유니온 사용은 구조물 패딩과 정렬에 대한 느슨한 표준 정의 때문에 종종 눈살을 찌푸리게 됩니다.저는 조합이 매우 유용하고 광범위하게 사용되어 왔으며, 오랜 기간(15년 이상) 동안 상호 호환성 문제가 발생하지 않았습니다.유니온 패딩/얼라인먼트는 x86, ARM 또는 PowerPC를 대상으로 하는 모든 현재 컴파일러에 대해 예상되고 일관된 방식으로 작동합니다.

코드 내부에서는 거의 무시할 수 있습니다. 모든 것이 취소됩니다.

디스크 또는 네트워크 사용자에게 데이터를 읽고 쓰는 경우

이것은 분명히 꽤 논란이 많은 주제입니다.

일반적인 접근 방식은 코드의 입력 섹션과 출력 섹션의 작은 부분인 바이트 순서에만 관심을 갖도록 응용 프로그램을 설계하는 것입니다.

다른 곳에서는 기본 바이트 순서를 사용해야 합니다.

대부분의 컴퓨터는 동일한 방식으로 이 작업을 수행하지만 부동 소수점과 정수 데이터가 동일한 방식으로 저장되지는 않으므로 모든 작업이 올바르게 작동하려면 크기뿐만 아니라 정수인지 부동 소수점인지도 알아야 합니다.

다른 대안은 텍스트 형식의 데이터만 소비하고 생성하는 것입니다.이는 거의 구현하기 쉬우며, 거의 처리하지 않고 애플리케이션 내부/외부에서 데이터를 처리하는 비율이 매우 높은 경우가 아니라면 성능에 거의 차이가 없을 것입니다.또한 코드에 오류가 있을 때 출력에 있는 바이트 51213498-51213501의 값을 디코딩하는 대신 텍스트 편집기에서 입력 및 출력 데이터를 읽을 수 있다는 이점이 있습니다.

2, 4 또는 8바이트 정수 유형과 바이트 인덱스 배열(또는 그 반대)을 재해석해야 하는 경우 엔디언을 알아야 합니다.

이 문제는 암호화 알고리즘 구현, 직렬화 애플리케이션(네트워크 프로토콜, 파일 시스템 또는 데이터베이스 백엔드 등), 운영 체제 커널 및 드라이버에서 자주 발생합니다.

보통 ENDIAN 같은 매크로에 의해 감지됩니다.

예:

uint32 x = ...;
uint8* p = (uint8*) &x;

p는 BE 기계의 높은 바이트와 LE 기계의 낮은 바이트를 가리킵니다.

매크로를 사용하여 다음을 작성할 수 있습니다.

uint32 x = ...;

#ifdef LITTLE_ENDIAN
    uint8* p = (uint8*) &x + 3;
#else // BIG_ENDIAN
    uint8* p = (uint8*) &x;
#endif

예를 들어 항상 높은 바이트를 얻습니다.

여기서 매크로를 정의하는 방법이 있습니다. C 매크로 정의를 통해엔디안 또는 리틀 엔디안 기계를 결정할 수 있습니까?툴체인이 제공하지 않는 경우.

언급URL : https://stackoverflow.com/questions/13994674/how-to-write-endian-agnostic-c-c-code