구조체 내 비트 필드의 순서는 플랫폼에 따라 다릅니다. 다른 컴파일러 별 패킹 옵션을 사용하면 데이터가 기록 될 때 적절한 순서로 저장된다는 보장이 있습니까? 예를 들면 :
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
그 뒤를 따릅니다. 다양한 컴파일러에 대해 동등한 구조체 패킹 옵션을 찾으면 이것이 크로스 플랫폼입니까?
답변
아니요, 완전히 휴대 할 수 없습니다. 구조체에 대한 패킹 옵션은 확장이며 그 자체로는 완전히 이식 할 수 없습니다. 또한 C99 §6.7.2.1, 단락 10에서는 “단위 내 비트 필드 할당 순서 (상위에서 하위 또는 하위에서 상위)는 구현에 따라 정의됩니다.”라고 말합니다.
예를 들어 단일 컴파일러조차도 대상 플랫폼의 엔디안성에 따라 비트 필드를 다르게 배치 할 수 있습니다.
답변
비트 필드는 컴파일러마다 매우 다양합니다. 죄송합니다.
GCC를 사용하면 빅 엔디안 머신은 비트 빅 엔드를 먼저 배치하고 리틀 엔디안 머신은 비트를 리틀 엔드 먼저 배치합니다.
K & R은 “구조의 인접한 [비트] 필드 멤버는 구현 종속적 인 방향으로 구현 종속적 인 저장 단위로 압축됩니다. 다른 필드를 따르는 필드가 맞지 않을 때 … 단위로 분할되거나 단위가 될 수 있습니다. 패딩 됨. 너비가 0 인 이름없는 필드는이 패딩을 강제합니다 … “
따라서 기계 독립적 인 바이너리 레이아웃이 필요한 경우 직접 수행해야합니다.
이 마지막 문장은 패딩으로 인해 비트 필드가 아닌 경우에도 적용됩니다. 그러나 이미 GCC에 대해 발견 한 것처럼 모든 컴파일러는 구조의 바이트 패킹을 강제하는 방법을 가지고있는 것 같습니다.
답변
비트 필드는 피해야합니다. 동일한 플랫폼에서도 컴파일러간에 이식성이 매우 낮습니다. C99 표준 6.7.2.1/10- “구조 및 공용체 지정자”(C90 표준에 유사한 용어가 있음) :
구현은 비트 필드를 보유하기에 충분히 큰 주소 지정 가능한 저장 장치를 할당 할 수 있습니다. 충분한 공간이 남아 있으면 구조에서 다른 비트 필드 바로 뒤에 오는 비트 필드는 동일한 단위의 인접한 비트로 패킹됩니다. 공간이 충분하지 않은 경우 적합하지 않은 비트 필드가 다음 유닛에 들어가거나 인접 유닛과 겹치는 지 여부는 구현에서 정의됩니다. 단위 내 비트 필드 할당 순서 (상위에서 하위로 또는 하위에서 상위로)는 구현에 따라 정의됩니다. 주소 지정 가능한 저장 장치의 정렬이 지정되지 않았습니다.
비트 필드가 int 경계를 ‘스팬’할지 여부를 보장 할 수 없으며 비트 필드가 int의 로우 엔드 또는 int의 하이 엔드에서 시작하는지 여부를 지정할 수 없습니다 (이는 프로세서가 빅 엔디안 또는 리틀 엔디안).
비트 마스크를 선호합니다. 인라인 (또는 매크로)을 사용하여 비트를 설정, 삭제 및 테스트합니다.
답변
엔디안은 비트 순서가 아닌 바이트 순서에 대해 이야기합니다. 요즘에는 비트 오더가 고정되어 있다고 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
답변
대부분의 경우 아마도 농장에 베팅하지 마십시오. 틀리면 큰 손실을 입을 것입니다.
정말로 동일한 이진 정보가 필요한 경우 비트 마스크를 사용하여 비트 필드를 만들어야합니다. 예를 들어 메시지에 대해 unsigned short (16 비트)를 사용한 다음 versionMask = 0xE000과 같은 것을 만들어 최상위 비트 3 개를 나타냅니다.
구조체 내 정렬과 비슷한 문제가 있습니다. 예를 들어, Sparc, PowerPC 및 680×0 CPU는 모두 빅 엔디안이며 Sparc 및 PowerPC 컴파일러의 일반적인 기본값은 구조체 멤버를 4 바이트 경계로 정렬하는 것입니다. 그러나 680×0에 사용했던 컴파일러는 2 바이트 경계에서만 정렬되었으며 정렬을 변경할 수있는 옵션이 없었습니다!
따라서 일부 구조체의 경우 Sparc 및 PowerPC의 크기는 동일하지만 680×0에서는 더 작고 일부 멤버는 구조체 내에서 다른 메모리 오프셋에 있습니다.
이것은 내가 작업 한 한 프로젝트의 문제였습니다. Sparc에서 실행되는 서버 프로세스가 클라이언트를 쿼리하고 그것이 big-endian임을 알아 내고 네트워크에서 바이너리 구조를 생성 할 수 있고 클라이언트가 대처할 수 있다고 가정했기 때문입니다. 그리고 그것은 PowerPC 클라이언트에서 잘 작동했고 680×0 클라이언트에서 큰 시간을 보냈습니다. 나는 코드를 작성하지 않았고 문제를 찾는 데 꽤 오랜 시간이 걸렸습니다. 하지만 한 번 수정하는 것은 쉬웠습니다.
답변
매우 유용한 댓글 시작에 대해 @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 에서 작동하도록 시도하는 것을 포기하고 있습니다.
답변
물론 가장 좋은 대답은 비트 필드를 스트림으로 읽고 쓰는 클래스를 사용하는 것입니다. C 비트 필드 구조를 사용하는 것은 보장되지 않습니다. 말할 것도없이 실제 코딩에서 이것을 사용하는 것은 비전문적 / 게으른 / 어리석은 것으로 간주됩니다.