C ++ 데이터 구성원 정렬 및 배열 포장
-
16-09-2019 - |
문제
코드 검토 중에 간단한 구조를 다음과 같이 정의하는 일부 코드를 발견했습니다.
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
}
다른 곳에서는 이러한 객체의 배열이 정의됩니다.
foo listOfFoos[SOME_NUM];
나중에 구조물은 버퍼로 생성됩니다.
memcpy(pBuff,listOfFoos,3*SOME_NUM);
이 코드는 다음과 같은 가정에 의존한다.
나는 두 개의 플랫폼 (Redhat 64B, Solaris 9)에서 GNU와 함께 시도했으며 두 가지 모두에서 작동했습니다.
위의 가정이 유효합니까? 그렇지 않은 경우, 어떤 조건 하에서 (예 : OS/컴파일러의 변경) 실패 할 수 있습니까?
해결책
객체 배열은 인접해야하므로 물체 사이에 패딩이 절대 패딩되지 않지만 패딩은 물체의 끝에 추가 될 수 있습니다 (거의 동일한 효과를 생성).
당신이 Char와 함께 일하고 있다는 점을 감안할 때 가정은 아마도 종종 옳지 않을 것입니다. 그러나 C ++ 표준은 확실히 그것을 보장하지 않습니다. 다른 컴파일러 또는 전류 컴파일러로 전달 된 플래그의 변화만으로 구조물의 요소 사이에 또는 구조물의 마지막 요소를 따르는 경우 패딩이 삽입 될 수 있습니다.
다른 팁
확실히 더 안전 할 것입니다.
sizeof(foo) * SOME_NUM
이렇게 배열을 복사하면 사용해야합니다.
memcpy(pBuff,listOfFoos,sizeof(listOfFoos));
이것은 Pbuff를 같은 크기로 할당하는 한 항상 작동합니다. 이런 식으로 당신은 패딩과 정렬에 대해 전혀 가정하지 않습니다.
대부분의 컴파일러는 구조물 또는 클래스를 포함 된 최대 유형의 필요한 정렬에 맞게 정렬합니다. 당신의 숯의 경우, 정렬 및 패딩을 의미하지는 않지만, 짧게 추가하면 클래스는 마지막 숯과 짧은 사이에 1 바이트의 패딩이 추가되면 6 바이트가 큰 6 바이트가됩니다.
구조의 모든 필드가 구조물을 정렬하는 숯이기 때문에 이것이 작동하는 이유는 생각합니다. 1을 정렬하지 않는 필드가 하나 이상있는 경우 구조/클래스의 정렬은 1이 아닙니다 (정렬은 필드 순서 및 정렬에 따라 다름).
몇 가지 예를 보자 :
#include <stdio.h>
#include <stddef.h>
typedef struct {
unsigned char a;
unsigned char b;
unsigned char c;
} Foo;
typedef struct {
서명되지 않은 짧은 i;
unsigned char a;
unsigned char b;
unsigned char c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;
#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )
int main(void) {
printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}
실행되면 결과는 다음과 같습니다.
Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2
Bar와 F_B가 정렬 2를 가지고있어 필드가 올바르게 정렬되도록합니다. 막대의 크기가 있음을 알 수 있습니다 6이 아닌 5. 마찬가지로 B_F (5의 5)의 크기는 30은 25가 아닙니다.
그래서 당신이 대신 하드 코드라면 sizeof(...)
, 당신은 여기서 문제가 생길 것입니다.
도움이 되었기를 바랍니다.
그것은 모두 메모리 정렬로 내려집니다. 일반적인 32 비트 기계는 시도 당 4 바이트의 메모리를 읽거나 씁니다. 이 구조는 혼란스러운 패딩 문제없이 쉽게 4 바이트에 해당하기 때문에 문제로부터 안전합니다.
이제 구조가 그렇게 된 경우 :
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
unsigned int i;
unsigned int j;
}
동료 논리가 아마도 이어질 것입니다
memcpy(pBuff,listOfFoos,11*SOME_NUM);
(3 char 's = 3 바이트, 2 int = 2*4 바이트, 따라서 3 + 8)
불행히도, 패딩으로 인해 구조는 실제로 12 바이트를 차지합니다. 이것은 당신이 4 개의 바이트 단어에 3 숯과 int를 맞출 수 없기 때문에, int를 자신의 단어로 밀어 넣는 패딩 된 공간의 바이트가 하나 있습니다. 이것은 데이터 유형이 더 다양할수록 점점 더 많은 문제가됩니다.
이와 같은 물건이 사용되는 상황에서는 피할 수없는 상황에서는 더 이상 추정이 유지되지 않을 때 편집을 중단하려고합니다. 나는 다음과 같은 것을 사용합니다 (또는 boost.staticassert 상황이 허용되는 경우) :
static_assert(sizeof(foo) <= 3);
// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp) static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line) static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}
나는 안전했고 Magic Number 3을 sizeof(foo)
나는 생각한다.
내 생각에 미래의 프로세서 아키텍처에 최적화 된 코드는 아마도 어떤 형태의 패딩을 소개 할 것입니다.
그리고 그런 종류의 버그를 추적하려고하는 것은 진정한 고통입니다!
다른 사람들이 말했듯이, Sizeof (foo)를 사용하는 것은 더 안전합니다. 일부 컴파일러 (특히 임베디드 세계의 난해한 컴파일러)는 4 바이트 헤더를 클래스에 추가합니다. 다른 사람들은 컴파일러 설정에 따라 펑키 한 메모리 정렬 트릭을 수행 할 수 있습니다.
주류 플랫폼의 경우 아마도 괜찮지 만 보장은 아닙니다.
두 컴퓨터 사이에 데이터를 전달할 때 Sizeof ()에 여전히 문제가있을 수 있습니다. 그중 하나에서 코드는 패딩으로 컴파일 할 수 있고 다른 하나는없는 경우 ()는 다른 결과를 제공합니다. 배열 데이터가 한 컴퓨터에서 다른 컴퓨터로 전달되면 배열 요소가 예상되는 곳에서 찾을 수 없으므로 잘못 해석됩니다. 한 가지 해결책은 #Pragma Pack (1)가 가능할 때마다 사용되지만 배열에는 충분하지 않을 수 있습니다. 가장 좋은 것은 문제를 예견하고 배열 요소 당 8 바이트의 배를 사용하는 것입니다.