programing

C/C++: 강제 비트 필드 순서 및 정렬

newsource 2022. 8. 16. 23:26

C/C++: 강제 비트 필드 순서 및 정렬

구조체 내의 비트필드 순서는 플랫폼에 따라 다르다고 읽었습니다.다른 컴파일러 고유의 패킹 옵션을 사용하면 데이터가 기재된 대로 올바른 순서로 저장되는 것을 보증할 수 있습니까?예를 들어 다음과 같습니다.

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

GCC 컴파일러를 탑재한 인텔 프로세서의 경우 필드는 표시된 대로 메모리에 배치되어 있습니다. Message.version첫 와 3비트입니다.Message.type랐다 if 컴파일러에 대해 한 구조 크로스 입니까?다양한 컴파일러에서 동등한 구조 패킹 옵션을 찾을 수 있다면 크로스 플랫폼입니까?

아니요, 완전히 휴대할 수는 없습니다.구조물에 대한 패킹 옵션은 확장이며, 그 자체는 완전히 휴대할 수 없습니다.또, C99 § 6.7.2.1, 제10항에서는, 「유닛내의 비트 필드의 할당 순서(상위로부터 하위, 또는 하위로부터 상위)가 실장 정의된다」라고 하고 있다.

예를 들어 단일 컴파일러라도 타깃 플랫폼의 엔디안성에 따라 비트필드를 다르게 배치할 수 있습니다.

비트 필드는 컴파일러마다 매우 다양합니다.

GCC를 사용하면 큰 엔디언 기계는 큰 엔드를 먼저 배치하고 작은 엔디언 기계는 작은 엔드를 먼저 배치했습니다.

K&R은 다음과 같이 말합니다. "인접 [비트]필드 구성원은 구현에 의존하는 방향으로 구현에 의존하는 스토리지 유닛에 포함되어 있습니다.다른 필드 뒤에 오는 필드가 맞지 않는 경우...유닛 간에 분할하거나 패딩할 수 있습니다.이름 없는 필드 width 0은 이 패딩을 강제합니다.."

따라서 머신에 의존하지 않는 바이너리 레이아웃이 필요한 경우 직접 수행해야 합니다.

이 마지막 문장은 패딩으로 인해 비트필드가 아닌 경우에도 적용됩니다.그러나 GCC용으로 이미 발견된 것처럼 모든 컴파일러가 구조의 바이트 패킹을 강제하는 방법이 있는 것 같습니다.

비트필드는 피해야 합니다.같은 플랫폼이라도 컴파일러 간에 쉽게 이동할 수 없습니다.C99 표준 6.7.2.1/10 - "구조 및 유니언 지정자" (C90 표준에도 유사한 표현이 있음)

구현에서는 비트필드를 보유하기에 충분한 크기의 주소 지정 가능한 스토리지 유닛을 할당할 수 있습니다.충분한 공간이 남아 있을 경우 구조물 내의 다른 비트필드 바로 뒤에 있는 비트필드는 동일한 유닛의 인접 비트로 채워져야 한다.공간이 부족할 경우 맞지 않는 비트필드가 다음 유닛에 배치되거나 인접 유닛에 겹치는지 여부가 구현 정의됩니다.유닛내의 비트 필드의 할당 순서(상위부터 하위, 또는 하위에서 상위)는, 실장 정의됩니다.주소 지정 가능한 저장 장치의 정렬이 지정되지 않았습니다.

비트 필드가 int 경계를 '스팬'할지 여부를 보증할 수 없으며 비트 필드가 int의 로우엔드에서 시작되는지 int의 하이엔드에서 시작되는지 지정할 수 없습니다(이것은 프로세서가 빅엔디언인지 리틀엔디언인지에 관계없이 독립적입니다).

비트마스크를 선호합니다.인라인(또는 매크로)을 사용하여 비트를 설정, 클리어 및 테스트합니다.

@BenVoigt에게 매우 유용한 코멘트를 주셔서 감사합니다.

아니요, 메모리를 절약하기 위해 만들어졌습니다.

Linux 소스는 외부 구조와 일치하기 위해 비트 필드를 사용합니다. /usr/include/linux/ip.h는 IP 데이터그램의 첫 번째 바이트에 대해 이 코드를 가집니다.

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

하지만 당신의 코멘트를 고려하여 멀티바이트 비트필드 frag_off에서 이 기능을 수행하려고 하는 것을 포기합니다.

대부분 그렇겠지, 하지만 장담하진 마, 틀리면 큰 손해를 볼 테니까.

정말로 동일한 바이너리 정보가 필요한 경우 비트마스크를 사용하여 비트필드를 만들어야 합니다. 예를 들어 Message에 부호 없는 쇼트(16비트)를 사용한 다음 versionMask = 0xE000과 같이 최상위 3비트를 나타냅니다.

구조물 내 정렬에도 비슷한 문제가 있습니다.예를 들어 Sparc, PowerPC 및 680x0 CPU는 모두 빅엔디안이며 Sparc와 Power의 공통 디폴트입니다.PC 컴파일러는 구조 멤버를 4바이트 경계에 정렬합니다.단, 680x0에 사용한 컴파일러는 2바이트 경계에만 정렬되어 있어 정렬을 변경할 수 있는 옵션이 없었습니다.

따라서 일부 구조체에서는 Sparc와 PowerPC의 크기가 동일하지만 680x0에서는 더 작고 일부 구성원은 구조체 내에서 다른 메모리 오프셋에 있습니다.

이것은 제가 작업했던 프로젝트 중 하나였습니다.Sparc에서 실행되는 서버 프로세스가 클라이언트에 문의하여 빅엔디안임을 발견하고 네트워크상에 바이너리 구조를 분사하여 클라이언트가 대처할 수 있을 것으로 생각했기 때문입니다.그리고 그것은 전력으로 잘 작동했다.PC 클라이언트 및 680x0 클라이언트의 대규모 크래시.코드를 작성하지 않았고, 문제를 발견하는 데 상당한 시간이 걸렸습니다.하지만 한번 고치면 쉽게 고칠 수 있었다.

endianness는 비트 순서가 아니라 바이트 순서에 대해 말하고 있습니다.현재는 비트 순서가 고정되어 있는 것이 99% 확실합니다.단, 비트필드를 사용할 때는 endianness를 고려해야 합니다.아래 예를 참조하십시오.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a

물론 비트 필드를 읽고 쓰는 클래스를 스트림으로 사용하는 것이 가장 좋습니다.C 비트 필드 구조의 사용은 보증되지 않습니다.말할 것도 없이 이것을 실제 코딩에 사용하는 것은 프로답지 못하거나 게으름뱅이이거나 멍청하다고 여겨집니다.

언급URL : https://stackoverflow.com/questions/1490092/c-c-force-bit-field-order-and-alignment