18.2.2.2 __attribute__((__packed__)) 컴파일러 지시어를 통한 메모리 패딩(Padding) 억제 원리
앞 18.2.2.1절에서 기본 자료형을 엄격하게 고정(Mapping)했음에도 불구하고, C++ 컴파일러는 본능적으로 구조체의 크기를 임의로 부풀리는 경향이 있다. 이를 메모리 패딩(Memory Padding) 현상이라고 한다.
uORB 헤더 파일을 생성하는 파이프라인은 이 치명적인 컴파일러의 자의적 행동을 억제하기 위해 가장 강력한 형태의 제어 권한을 행사한다. 그 핵심 무기가 바로 __attribute__((__packed__)) 컴파일러 지시어(Directive)이다.
1. 컴파일러의 자의적 패딩(Padding) 삽입 본능
모든 현대형 CPU 마이크로아키텍처는 메모리에서 데이터를 퍼올릴 때, 변수의 크기 단위에 맞게 주소가 배수(Alignment)로 떨어지는 것을 선호한다.
예를 들어 float32 (4바이트) 변수는 메모리 주소가 0, 4, 8, 12... 인 곳에 위치할 때 CPU 버스(Bus)가 단 한 번의 사이클로 데이터를 읽어올 수 있다. 만약 4바이트 변수가 홀수 주소인 1에 위치한다면, CPU는 주소 0에서 한 번, 주소 4에서 한 번 총 두 번을 읽어 조합해야 하는 비정렬 접근 페널티(Unaligned Access Penalty)를 맞게 된다.
C++ 컴파일러(GCC, Clang 등)는 이 페널티를 막기 위해 구조체 내부 변수들 사이에 의미 없는 “빈 공간(Padding Byte)“을 몰래 끼워 넣어 강제로 짝수 배수 주소로 밀어내는 최적화를 기본적으로 수행한다.
문제는 컴파일을 수행하는 플랫폼(x86, ARM32, ARM64)마다 이 빈 공간을 끼워 넣는 규칙이 제각각이라는 점이다. 똑같은 데이터를 가졌음에도 구조체의 덩치(sizeof())와 변수들의 오프셋(Offset)이 달라지게 되면, memcpy를 통한 제로-카피 통신망은 그 순간 쓰레기 데이터를 교환하는 시한폭탄으로 전락한다.
2. 절대적 억제 지시어: __packed__
PX4 빌드 시스템의 Jinja 템플릿 파일(msg.h.jinja)은 생성되는 모든 토픽 구조체 선언부 꼬리말에 예외 없이 이 지시어를 강제 주입한다.
struct __EXPORT sensor_accel_s {
uint64_t timestamp;
uint64_t timestamp_sample;
uint32_t device_id;
float x;
float y;
float z;
float temperature;
uint32_t error_count;
uint8_t clip_counter[3];
uint8_t samples;
// 패딩을 유발할 수 있는 변수 크기들이 혼재해 있음
} __attribute__((__packed__)); // <--- 핵심
__attribute__((__packed__)) 라는 주술이 붙은 구조체를 마주한 C++ 컴파일러는 기존의 모든 주소 정렬(Alignment) 최적화 욕구를 포기한다.
이 지시어는 컴파일러에게 “메모리 주소가 홀수가 되든 비정렬 페널티가 발생하든 상관하지 마라. 내가 .msg 파일에 적어놓은 순서 그대로, 단 1바이트의 빈 공간도 없이 구조체 변수들을 테트리스처럼 꽉꽉 눌러 담아라” 라고 강제 명령하는 것과 같다.
3. 패킹(Packing)의 부가적 이점과 딜레마
이 절대적인 명령 덕분에, uORB 구조체의 sizeof() 값은 수학적으로 내부 변수들의 바이트 사이즈를 모두 합친 값과 100% 한 치의 오차 없이 일치하게 된다. 빈 공간이 소거됨으로써 얻는 부가적인 이점은 다음과 같다.
- 플랫폼 간 무결성(Cross-Platform Integrity): STM32 마이크로컨트롤러, 라즈베리 파이, 그리고 MacOS 개발자 환경 등 어떤 아키텍처에서 빌드하더라도 메모리 맵(Memory Map)이 아날로그 레코드판처럼 완벽히 동일해진다.
- SRAM 트래픽의 극단적 절약: 쓸모없는 패딩 바이트가 사라짐으로써 통신 버퍼나 플래시 로깅 모듈(SD 카드 저장)에 버려지는 데이터 대역폭이 완전히 제로(0)가 된다.
그러나 물리학과 컴퓨팅 세계에 공짜 점심은 없다. 컴파일러의 최적화를 강제로 꺼버렸기 때문에, 필연적으로 CPU는 “정렬되지 않은 메모리(Unaligned Memory)“를 읽느라 사이클을 낭비하는 치명적인 속도 저하(Penalty)를 맞게 된다. PX4 코어 아키텍트들은 이 모순을 해결하기 위해 또 다른 차원의 메모리 재배치 마법을 파이프라인에 심어 두었는데, 이는 다음 절에서 상세히 파헤친다.