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


87

구조체 내 비트 필드의 순서는 플랫폼에 따라 다릅니다. 다른 컴파일러 별 패킹 옵션을 사용하면 데이터가 기록 될 때 적절한 순서로 저장된다는 보장이 있습니까? 예를 들면 :

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그 뒤를 따릅니다. 다양한 컴파일러에 대해 동등한 구조체 패킹 옵션을 찾으면 이것이 크로스 플랫폼입니까?


17
버퍼는 비트가 아니라 바이트 집합이므로 "버퍼의 처음 3 비트"는 정확한 개념이 아닙니다. 첫 번째 바이트의 최하위 비트 3 개를 처음 3 비트 또는 최상위 3 비트로 간주 하시겠습니까?
caf

2
네트워크에서 전환 할 때 "버퍼의 처음 3 비트"는 매우 잘 정의되어 있습니다.
Joshua

2
@Joshua IIRC, 이더넷은 각 바이트의 최하위 비트를 먼저 전송합니다 (이것이 브로드 캐스트 비트가있는 이유입니다).
tc.

"휴대용"과 "크로스 플랫폼"이란 무엇을 의미합니까? 실행 파일은 대상 OS에 관계없이 순서에 올바르게 액세스합니다. 또는 도구 체인에 관계없이 코드가 컴파일됩니까?
Garet Claborn

답변:


103

아니요, 완전히 휴대 할 수 없습니다. 구조체에 대한 패킹 옵션은 확장이며 그 자체로는 완전히 이식 할 수 없습니다. 또한 C99 §6.7.2.1, 단락 10에서는 "단위 내 비트 필드 할당 순서 (상위에서 하위 또는 하위에서 상위)는 구현에 따라 정의됩니다."라고 말합니다.

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


예를 들어 GCC는 특히 비트 필드가 구현이 아닌 ABI에 따라 정렬된다는 점을 지적합니다. 따라서 단일 컴파일러에 머무르는 것만으로는 순서를 보장 할 수 없습니다. 아키텍처도 확인해야합니다. 실제로 이식성에 대한 약간의 악몽.
underscore_d

10
C 표준이 비트 필드에 대한 순서를 보장하지 않은 이유는 무엇입니까?
Aaron Campbell

7
바이트 경계를 넘을 수있는 비트의 순서보다 바이트 내에서 비트의 "순서"를 일관되고 이식 가능하게 정의하는 것은 어렵습니다. 당신이 정한 정의는 상당한 양의 기존 관행과 일치하지 않을 것입니다.
Stephen Canon

2
Implementaiton-defined는 플랫폼 별 최적화를 허용합니다. 일부 플랫폼에서 비트 필드 사이의 패딩은 액세스를 향상시킬 수 있습니다. 32 비트 int에있는 4 개의 7 비트 필드를 상상해보십시오. 8 비트마다 정렬하는 것은 바이트 읽기가있는 플랫폼에서 상당한 개선입니다.
peterchen


45

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

GCC를 사용하면 빅 엔디안 머신은 비트 빅 엔드를 먼저 배치하고 리틀 엔디안 머신은 비트를 리틀 엔드 먼저 배치합니다.

K & R은 "구조의 인접한 [비트] 필드 멤버는 구현 종속적 인 방향으로 구현 종속적 인 저장 단위로 압축됩니다. 다른 필드를 따르는 필드가 맞지 않을 때 ... 단위로 분할되거나 단위가 될 수 있습니다. 패딩 됨. 너비가 0 인 이름없는 필드는이 패딩을 강제합니다 ... "

따라서 기계 독립적 인 바이너리 레이아웃이 필요한 경우 직접 수행해야합니다.

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


K & R이 사전 표준화 였고 많은 영역에서 대체되었을 가능성이 있다는 점을 감안할 때 실제로 유용한 참고 자료로 간주됩니까?
underscore_d

1
내 K & R은 ANSI 이후입니다.
여호수아

1
당황 스럽습니다. 저는 그들이 ANSI 이후 개정판을 발표했다는 것을 몰랐습니다. 내 잘못이야!
underscore_d

35

비트 필드는 피해야합니다. 동일한 플랫폼에서도 컴파일러간에 이식성이 매우 낮습니다. C99 표준 6.7.2.1/10- "구조 및 공용체 지정자"(C90 표준에 유사한 용어가 있음) :

구현은 비트 필드를 보유하기에 충분히 큰 주소 지정 가능한 저장 장치를 할당 할 수 있습니다. 충분한 공간이 남아 있으면 구조에서 다른 비트 필드 바로 뒤에 오는 비트 필드는 동일한 단위의 인접한 비트로 패킹됩니다. 공간이 충분하지 않은 경우 적합하지 않은 비트 필드가 다음 유닛에 들어가거나 인접 유닛과 겹치는 지 여부는 구현에서 정의됩니다. 단위 내 비트 필드 할당 순서 (상위에서 하위로 또는 하위에서 상위로)는 구현에 따라 정의됩니다. 주소 지정 가능한 저장 장치의 정렬이 지정되지 않았습니다.

비트 필드가 int 경계를 '스팬'할지 여부를 보장 할 수 없으며 비트 필드가 int의 로우 엔드 또는 int의 하이 엔드에서 시작하는지 여부를 지정할 수 없습니다 (이는 프로세서가 빅 엔디안 또는 리틀 엔디안).

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


2
비트 필드의 순서는 컴파일 타임에 결정될 수 있습니다.
Greg A. Woods

9
또한 프로그램 외부 (즉, 디스크 또는 레지스터 또는 다른 프로그램에서 액세스하는 메모리 등) 외부 표현이없는 비트 플래그를 처리 할 때 비트 필드가 매우 선호됩니다.
Greg A. Woods

1
@ GregA.Woods : 이것이 사실이라면 방법을 설명하는 답변을 제공해주세요. ... 난 아무것도 찾을 수 없습니다 그러나 당신의 의견은 인터넷 검색을 할 때
mozzbozz

1
@ GregA.Woods : 죄송합니다. 제가 언급 한 댓글을 작성 했어야합니다. 내 말은 : "비트 필드의 순서는 컴파일 타임에 결정될 수 있습니다."라고 말합니다. 나는 그것에 대해 아무것도 할 수 없으며 어떻게 할 수 있습니다.
mozzbozz

2
@mozzbozz 한 번 봐 가지고 planix.com/~woods/projects/wsg2000.c을 하고 정의를 검색하고의 사용 _BIT_FIELDS_LTOH_BIT_FIELDS_HTOL
그렉 A. 우즈

11

엔디안은 비트 순서가 아닌 바이트 순서에 대해 이야기합니다. 요즘에는 비트 오더가 고정되어 있다고 99 % 확신합니다. 그러나 비트 필드를 사용할 때는 엔디안을 고려해야합니다. 아래 예를 참조하십시오.

#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

6
a와 b의 출력은 엔디안이 여전히 비트 순서와 바이트 순서에 대해 이야기하고 있음을 나타냅니다.
Windows 프로그래머

비트 순서 및 바이트 순서 문제가있는 멋진 예
Jonathan

1
실제로 코드를 컴파일하고 실행 했습니까? "a"와 "b"의 값은 나에게 논리적이지 않은 것 같습니다. 기본적으로 컴파일러가 엔디안 (endianness)으로 인해 바이트 내에서 니블을 교체 할 것이라고 말하는 것입니다. "d"의 경우, 엔디안은 char 배열 내의 바이트 순서에 영향을주지 않아야합니다 (char 길이가 1 바이트라고 가정). 컴파일러가 그렇게했다면 포인터를 사용하여 배열을 반복 할 수 없습니다. 반면에 두 개의 16 비트 정수 배열을 사용한 경우 : uint16 data [] = {0x1234,0x5678}; 그러면 d는 리틀 엔디안 시스템에서 확실히 0x7856이됩니다.
Krauss 2017 년

6

대부분의 경우 아마도 농장에 베팅하지 마십시오. 틀리면 큰 손실을 입을 것입니다.

정말로 동일한 이진 정보가 필요한 경우 비트 마스크를 사용하여 비트 필드를 만들어야합니다. 예를 들어 메시지에 대해 unsigned short (16 비트)를 사용한 다음 versionMask = 0xE000과 같은 것을 만들어 최상위 비트 3 개를 나타냅니다.

구조체 내 정렬과 비슷한 문제가 있습니다. 예를 들어, Sparc, PowerPC 및 680x0 CPU는 모두 빅 엔디안이며 Sparc 및 PowerPC 컴파일러의 일반적인 기본값은 구조체 멤버를 4 바이트 경계로 정렬하는 것입니다. 그러나 680x0에 사용했던 컴파일러는 2 바이트 경계에서만 정렬되었으며 정렬을 변경할 수있는 옵션이 없었습니다!

따라서 일부 구조체의 경우 Sparc 및 PowerPC의 크기는 동일하지만 680x0에서는 더 작고 일부 멤버는 구조체 내에서 다른 메모리 오프셋에 있습니다.

이것은 내가 작업 한 한 프로젝트의 문제였습니다. Sparc에서 실행되는 서버 프로세스가 클라이언트를 쿼리하고 그것이 big-endian임을 알아 내고 네트워크에서 바이너리 구조를 생성 할 수 있고 클라이언트가 대처할 수 있다고 가정했기 때문입니다. 그리고 그것은 PowerPC 클라이언트에서 잘 작동했고 680x0 클라이언트에서 큰 시간을 보냈습니다. 나는 코드를 작성하지 않았고 문제를 찾는 데 꽤 오랜 시간이 걸렸습니다. 하지만 한 번 수정하는 것은 쉬웠습니다.


1

매우 유용한 댓글 시작에 대해 @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 에서 작동하도록 시도하는 것을 포기하고 있습니다.


-9

물론 가장 좋은 대답은 비트 필드를 스트림으로 읽고 쓰는 클래스를 사용하는 것입니다. C 비트 필드 구조를 사용하는 것은 보장되지 않습니다. 말할 것도없이 실제 코딩에서 이것을 사용하는 것은 비전문적 / 게으른 / 어리석은 것으로 간주됩니다.


5
나는이 모델에 생성 된 하드웨어 레지스터를 표현하는 매우 깨끗한 방법을 제공하기 때문에 비트 필드를 사용하는 바보입니다 상태로 잘못 생각, C.에
trondd

13
@trondd : 아니요, 메모리를 절약하기 위해 만들어졌습니다. 비트 필드는 메모리 매핑 된 하드웨어 레지스터, 네트워크 프로토콜 또는 파일 형식과 같은 외부 데이터 구조에 매핑하기위한 것이 아닙니다. 외부 데이터 구조에 매핑하려는 경우 패킹 순서가 표준화되었을 것입니다.
Ben Voigt 2013 년

2
비트를 사용하면 메모리가 절약됩니다. 비트 필드를 사용하면 가독성이 높아집니다. 적은 메모리를 사용하는 것이 더 빠릅니다. 비트를 사용하면 더 복잡한 원자 연산이 가능합니다. 실제 세계의 응용 프로그램에는 성능과 복잡한 원자 작업이 필요합니다. 이 대답은 우리에게 효과가 없습니다.
johnnycrash

@BenVoigt는 사실 일 수 있지만, 프로그래머가 컴파일러 / ABI의 순서가 필요한 것과 일치하는지 확인하고 그에 따라 빠른 이식성을 희생한다면 확실히 그 역할을 수행 할 수 있습니다 . 9 *의 경우, 어느 권위있는 "실제 코더"집단이 비트 필드의 모든 사용을 "비전문적 / 게으른 / 멍청한"것으로 간주하고 어디에서이를 언급 했습니까?
underscore_d

2
적은 메모리를 사용하는 것이 항상 빠른 것은 아닙니다. 더 많은 메모리를 사용하고 읽기 후 작업을 줄이는 것이 더 효율적인 경우가 많으며 프로세서 / 프로세서 모드를 사용하면 더욱 사실을 알 수 있습니다.
Dave Newton
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.