c로 구조체를 포장하는 표준 방법이나 표준 대안이 있습니까?


13

CI로 프로그래밍 할 때 GCC __attribute__((__packed__))속성을 사용하여 구조체를 패킹하는 것이 중요하다는 것을 알았 으므로 버스를 통해 전송되거나 스토리지에 저장되거나 레지스터 블록에 적용되도록 휘발성 메모리의 구조화 된 청크를 바이트 배열로 쉽게 변환 할 수 있습니다. 패킹 된 구조체는 바이트 배열로 취급 될 때 패딩을 포함하지 않을 것임을 보장합니다. 패딩은 낭비적이고 보안 위험이 있으며 하드웨어와 인터페이스 할 때 호환되지 않을 수 있습니다.

모든 C 컴파일러에서 작동하는 구조체 패킹에 대한 표준이 없습니까? 그렇지 않다면 이것이 시스템 프로그래밍에 중요한 기능이라고 생각하는 데 이상적입니까? C 언어의 초기 사용자는 구조체를 포장 할 필요가 없었거나 대안이 있습니까?


컴파일 도메인에서 구조체를 사용하는 것은 특히 하드웨어 (다른 컴파일 도메인)를 가리키는 매우 나쁜 생각입니다. 패킷 구조는이 작업을 수행하는 데 필요한 한 가지 트릭이며 부작용이 많으므로 부작용이 적은 문제에 대한 다른 솔루션이 많으며 이식성이 더 좋습니다.
old_timer

답변:


12

구조체에서 중요한 것은 각 구조체 인스턴스의 주소에서 각 멤버의 오프셋입니다. 물건이 얼마나 단단히 포장되어 있는지는 중요하지 않습니다.

그러나 배열은 "패킹"방식에 중요합니다. C의 규칙은 각 배열 요소가 이전과 정확히 N 바이트라는 것입니다. 여기서 N은 해당 유형을 저장하는 데 사용되는 바이트 수입니다.

그러나 구조체를 사용하면 균일 성이 필요하지 않습니다.

이상한 포장 구성표의 예는 다음과 같습니다.

프리 스케일 (자동차 마이크로 컨트롤러)은 Time Processing Unit 코 프로세서 (eTPU 또는 TPU 용 Google)가있는 마이크로를 만듭니다. 기본 데이터 크기는 8 비트와 24 비트이며 정수만 처리합니다.

이 구조체 :

struct a
{
  U24 elementA;
  U24 elementB;
};

각 U24는 자체 32 비트 블록을 저장하지만 가장 높은 주소 영역에만 저장됩니다.

이:

struct b
{
  U24 elementA;
  U24 elementB;
  U8  elementC;
};

는 인접한 32 비트 블록에 두 개의 U24가 저장되고 U8은 첫 번째 U24 앞의 "홀"에 저장됩니다 elementA.

그러나 원하는 경우 컴파일러에게 모든 것을 자체 32 비트 블록으로 압축하도록 지시 할 수 있습니다. RAM이 비싸지 만 액세스 지침은 적습니다.

"packing"은 "빡빡하게 포장"을 의미하는 것이 아니라, 구조체의 요소를 오프셋으로 정렬하는 몇 가지 체계를 의미합니다.

일반적인 체계는 없으며 컴파일러 + 아키텍처에 따라 다릅니다.


1
TPU의 컴파일러 가 다른 요소보다 먼저 struct b이동하도록 재배 열하 elementC는 경우 적합한 C 컴파일러가 아닙니다. C
Bart van Ingen Schenau

흥미롭지 만 U24는 표준 C 유형 en.m.wikipedia.org/wiki/C_data_types 가 아니므로 컴파일러가 다소 이상한 방식으로 처리해야한다는 것은 놀라운 일이 아닙니다.
satur9nine

워드 크기가 32 비트 인 주 CPU 코어와 RAM을 공유합니다. 그러나이 프로세서에는 24 비트 또는 8 비트 만 처리하는 ALU가 있습니다. 따라서 32 비트 워드로 24 비트 숫자를 배치하는 체계가 있습니다. 비표준이지만 포장 및 정렬의 좋은 예입니다. 동의하며 매우 비표준 적입니다.
RichColours

6

CI로 프로그래밍 할 때 GCC를 사용하여 구조체를 패킹하는 것이 매우 중요하다는 것을 알게되었습니다 __attribute__((__packed__))...]

당신이 언급 했으므로 __attribute__((__packed__)), 당신의 의도는 struct( 모든 멤버가 1 바이트 정렬을 갖도록) 내의 모든 패딩을 제거하는 것이라고 가정합니다 .

모든 C 컴파일러에서 작동하는 구조체 패킹에 대한 표준이 없습니까?

... 그리고 대답은 "아니오"입니다. 구조체 (및 스택 또는 힙에 인접한 구조체 배열)와 관련된 패딩 및 데이터 정렬은 중요한 이유로 존재합니다. 많은 컴퓨터에서 정렬되지 않은 메모리 액세스는 잠재적으로 상당한 성능 저하를 초래할 수 있습니다 (일부 최신 하드웨어에서는 줄어듦). 드문 경우이지만 메모리 액세스가 잘못 정렬되면 복구 할 수없는 버스 오류가 발생합니다 (전체 운영 체제가 충돌 할 수도 있음).

C 표준은 이식성에 중점을두기 때문에 구조의 모든 패딩을 제거하고 임의의 필드가 잘못 정렬되도록 표준 방법을 사용하는 것은 의미가 없습니다. 그렇지 않으면 C 코드를 이식 할 수 없게 될 위험이 있습니다.

모든 패딩을 제거하는 방식으로 이러한 데이터를 외부 소스로 출력하는 가장 안전하고 이식성이 좋은 방법은의 원시 메모리 내용을 전송하려고하는 대신 바이트 스트림에서 / 스트림으로 직렬화하는 것 structs입니다. 또한이 직렬화 컨텍스트 외부에서 프로그램이 성능 저하를 겪는 것을 방지 struct하고 전체 소프트웨어를 버리고 번쩍이지 않고 자유롭게 새로운 필드를 추가 할 수 있습니다 . 또한 엔디안과 그와 같은 것들을 해결할 수있는 여지가 있습니다.

컴파일러 관련 지시문에 도달하지 않고 모든 패딩을 제거하는 한 가지 방법이 있지만 필드 간의 상대 순서가 중요하지 않은 경우에만 적용 할 수 있습니다. 이런 식으로 주어진 :

struct Foo
{
    double x;  // assume 8-byte alignment
    char y;    // assume 1-byte alignment
               // 7 bytes of padding for first field
};

... 다음과 같이 이러한 필드를 포함하는 구조의 주소와 관련하여 정렬 된 메모리 액세스를위한 패딩이 필요합니다.

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......

... 여기서 .패딩을 나타냅니다. 모든 x것은 성능 및 때로는 올바른 동작을 위해 8 바이트 경계에 맞춰야합니다.

SoA (어레이의 구조) 표현을 사용하여 패딩을 휴대용 방식으로 제거 할 수 있습니다 (8 개의 Foo인스턴스 가 필요하다고 가정 합니다).

struct Foos
{
   double x[8];
   char y[8];
};

우리는 효과적으로 구조를 철거했습니다. 이 경우 메모리 표현은 다음과 같습니다.

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______

... 이:

01234567
yyyyyyyy

... 더 이상 패딩 오버 헤드가없고 잘못 정렬 된 메모리 액세스가 필요하지 않기 때문에 더 이상 구조 주소의 오프셋으로 이러한 데이터 필드에 액세스하지 않고 대신 효과적으로 배열에 대한 기본 주소의 오프셋으로 액세스합니다.

또한 적은 데이터 소비 (머신의 관련 데이터 소비 속도를 늦추기 위해 더 이상 관련 패딩이 없음)와 컴파일러가 처리를 매우 사소하게 벡터화 할 수있는 가능성으로 인해 순차적 액세스 속도가 빨라집니다. .

단점은 코딩하는 것이 PITA라는 것입니다. 또한 AoS 또는 AoSoA 담당자가 더 잘 수행 할 수있는 필드 간 거리가 넓어 질수록 임의 액세스에있어 잠재적으로 효율성이 떨어집니다. 그러나 그것은 패딩을 제거하고 모든 것을 정렬하지 않고 가능한 한 단단히 포장하는 표준 방법입니다.


2
구조 레이아웃을 명시 적으로 지정할 수있는 방법이 있으면 이식성 이 크게 향상 될 것 입니다. 일부 레이아웃은 일부 시스템에서 매우 효율적인 코드를 생성하고 다른 시스템에서는 매우 비효율적 인 코드를 생성하지만 코드는 모든 시스템에서 작동하고 일부 시스템에서는 효율적입니다. 대조적으로, 그러한 기능이 없으면 모든 머신에서 코드 작업을 수행하는 유일한 방법은 모든 머신에서 비효율적이거나 매크로를 많이 사용하고 조건부 컴파일을 사용하여 빠른 비 이식성을 결합하는 것입니다 프로그램과 같은 소스의 느린 휴대용 프로그램.
supercat

개념적으로 그렇습니다. 비트 및 바이트 표현, 정렬 요구 사항, 엔디안 등으로 모든 것을 지정할 수 있고 선택적으로 기본 아키텍처와 더 이혼하면서 C에서 명시 적으로 제어 할 수있는 기능이 있다면 ...하지만 나는 단지 이야기하고있었습니다. ATM-현재 시리얼 라이저를위한 가장 휴대용 솔루션은 정확한 비트 및 바이트 표현 및 데이터 유형의 정렬에 의존하지 않는 방식으로 작성하는 것입니다. 불행히도 우리는 ATM이 효과적으로 (C에서) 효과적으로 수행 할 수단이 부족합니다.

5

모든 아키텍처가 동일하지는 않으며 한 모듈에서 32 비트 옵션을 켜고 동일한 소스 코드와 동일한 컴파일러를 사용할 때 어떤 일이 발생하는지 확인하십시오. 바이트 순서는 또 다른 잘 알려진 제한 사항입니다. 부동 소수점 표현을 던지면 문제가 악화됩니다. 패킹을 사용하여 이진 데이터를 전송할 수는 없습니다. 실제로 사용할 수 있도록 표준화하려면 C 언어 사양을 재정의해야합니다.

일반적으로 Pack을 사용하여 이진 데이터를 보내는 것은 데이터의 보안, 이식성 또는 수명을 원하는 경우 나쁜 생각입니다. 소스에서 프로그램으로 바이너리 Blob을 얼마나 자주 읽습니까? 해커 나 프로그램 변경이 데이터에 '가져 오지'않았는지 모든 값이 제정신인지 얼마나 자주 확인합니까? 점검 루틴을 코딩 할 때 가져 오기 및 내보내기 루틴을 사용할 수도 있습니다.


0

가장 일반적인 대안은 "named padding"입니다.

struct s {
  short s1;
  char  c2;
  char  reserved; // Padding
};

이것은 구조가 8 바이트로 채워지지 않는다고 가정합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.