카테고리 없음

C언어 __attribute__(pack), 구조체 정렬

songye 2024. 12. 23. 18:09

바이트 패딩과 attribute((packed))에 대한 이해

C언어에서 구조체를 다룰 때

구조체의 크기(Size)는 단순히 각 멤버 변수들의 크기를 더한 값으로 결정되지 않습니다.

이는 바이트 패딩이라는 개념 때문입니다.

 

이번 글에서는 바이트 패딩과 이를 피할 수 있는 방법인 attribute((packed))에 대해 알아보겠습니다.

바이트 패딩이란?

구조체를 구성하는 멤버 변수들은 메모리에 연속적으로 저장됩니다.

그러나 컴파일러는 사용자 환경(32비트, 64비트)에 따라 성능 최적화를 위해

멤버 변수들 사이에 추가적인 빈 공간(패딩)을 삽입할 수 있습니다.

 

이 패딩은 데이터 접근 속도를 높이기 위한 것으로

1바이트씩 계산하는 것보다 4바이트(32비트 환경), 8바이트(64비트 환경) 단위로 처리하는 것이

컴파일러 입장에서 더 효율적이기 때문입니다.

 

이를 비유하자면, 타코야키를 구울 때 1구짜리 철판 대신 4구짜리 철판을 사용하는 것과 같습니다. 1개만 만들더라도 4구짜리를 사용한다면 나머지 공간이 비어도 어차피 4구짜리를 사용하는 셈이죠.

예시 코드

#include <stdio.h>

struct TEST {
    char a;    // 1바이트
    int b;     // 4바이트
};

int main() {
    printf("Size of TEST: %lu\n", sizeof(struct TEST));
    return 0;
}

위 코드를 실행하면 struct TEST의 크기는 5바이트가 아닌 8바이트로 출력됩니다.

이는 char aint b 사이에 3바이트의 패딩이 삽입되기 때문입니다.

 

패딩 메커니즘

컴파일러는 구조체의 멤버 변수를 메모리에 배치할 때, 멤버 변수의 크기를 기준으로 정렬합니다. 아래는 struct TEST의 메모리 배치를 설명한 것입니다:

  1. char a: 1바이트 차지, 이후 남은 공간은 3바이트 패딩.
  2. int b: 4바이트 차지, 주소가 4의 배수로 정렬됨.

이로 인해 총 크기는 8바이트가 됩니다. 이러한 구조체 패딩은 CPU가 데이터를 효율적으로 읽고 쓰는 데 도움을 줍니다.

attribute((packed))의 사용

구조체 크기를 최소화해야 하는 상황에서는 패딩을 제거할 수 있습니다.

이를 위해 __attribute__((packed))를 사용합니다.

이 속성은 컴파일러에게 멤버 변수를 빈 공간 없이 연속적으로 배치하라고 지시합니다.

특히, 통신 프로토콜에서 데이터가 바이트 단위로 정확히 정렬되어야 할 때 유용합니다. (일반적으로 하나하나 짤라쓰니깐)

#include <stdio.h>

struct TEST {
    char a;    // 1바이트
    int b;     // 4바이트
} __attribute__((packed));

int main() {
    printf("Size of packed TEST: %lu\n", sizeof(struct TEST));
    return 0;
}

위 코드에서 __attribute__((packed))를 사용하면 구조체의 크기는 5바이트로 출력됩니다.

패딩이 제거되었기 때문입니다.

 

 

하지만 용량을 줄인다고 해서 마냥 좋은 것은 아닙니다.

위에서 바이트패딩을 컴파일러가 하는 이유는 속도적인 측면에서 느려지기 때문인데

 

패딩을 제거하면 메모리 접근 시 비정렬 데이터로 인해 성능이 저하될 수 있습니다.

CPU는 정렬되지 않은 데이터를 처리하기 위해 추가 작업을 수행해야 할 수 있습니다.

 

이번에 이 글을 정리하게 된 것도 모듈 간 선언된 헤더에서 한 쪽은 __attribute__((pack))이 되어 있지 않아

특정 헤더 부분이 짤려서 생긴 이슈가 있었기 때문입니다.

 

이렇듯 바이트 패딩으로 인해 프로토콜 헤더의 위치가 바뀔 수도 쓰레기 값이 들어갈 수도 있음을 잘 생각하며

필요에 따라 어트리뷰트를 사용해야 합니다.