임베디드 메모리 할당: Static vs Dynamic
EmbeddedMemory
임베디드 시스템에서 메모리를 어떻게 확보할지 결정하는 문제다. 정적 할당은 컴파일 타임에, 동적 할당은 런타임에 메모리를 확보한다.
정적 할당 (Static Allocation)
컴파일 타임에 크기와 위치가 결정된다.
static uint8_t rx_buffer[256];
static TaskHandle_t sensor_task;
// FreeRTOS 정적 태스크 생성
static StackType_t task_stack[512];
static StaticTask_t task_tcb;
xTaskCreateStatic(sensor_fn, "sensor", 512, NULL, 3, task_stack, &task_tcb);
- 메모리 누수, dangling pointer, 할당 실패가 구조적으로 발생하지 않는다
- 링커 맵에서 전체 메모리 사용량을 정확히 확인할 수 있다
- 최대 사용량 기준으로 항상 점유하므로 RAM 효율이 낮을 수 있다
- 재귀, 가변 길이 데이터 처리에 제약이 있다
동적 할당 (Dynamic Allocation)
런타임에 malloc/free 또는 RTOS API로 메모리를 확보한다.
uint8_t *buf = (uint8_t *)malloc(packet_len);
if (buf == NULL) {
// 할당 실패 처리
}
// 사용 후 반드시 해제
free(buf);
동적 할당의 위험 요소
단편화 (Fragmentation)
할당과 해제가 반복되면 연속된 빈 공간이 줄어든다. 총 여유 메모리가 충분해도 요청한 크기의 연속 블록을 찾지 못하면 할당이 실패한다.
[ used ][ free ][ used ][ free ][ used ][ free ]
32B 16B 24B
// 총 72B 여유지만 64B 연속 블록 할당 불가
비결정적 실행 시간
일반적인 malloc 구현은 free list를 순회하며 적합한 블록을 찾는다. 단편화 상태에 따라 탐색 시간이 달라지므로 실시간 데드라인을 보장할 수 없다.
메모리 누수
free를 누락하면 해당 블록은 프로그램 종료까지 회수되지 않는다. 데스크톱과 달리 임베디드는 재부팅 없이 수개월~수년 동작하므로 누적 효과가 치명적이다.
선택 기준
| 조건 | 권장 방식 |
|---|---|
| 안전 인증 필요 (MISRA, ISO 26262) | 정적 할당 |
| 24/7 장기 운용 | 정적 할당 |
| 데이터 크기가 컴파일 타임에 결정됨 | 정적 할당 |
| 런타임에 크기/개수가 변동 | 메모리 풀 또는 동적 할당 |
| 초기화 시에만 할당, 이후 해제 없음 | 동적 할당 허용 (할당 후 비활성화 패턴) |
할당 후 비활성화 패턴
초기화 단계에서만 동적 할당을 허용하고, 이후에는 할당 자체를 차단하는 방식이다.
#define POOL_SIZE 8192
static uint8_t pool[POOL_SIZE];
static size_t pool_offset = 0;
static bool alloc_enabled = true;
void *init_alloc(size_t size) {
assert(alloc_enabled);
assert(pool_offset + size <= POOL_SIZE);
void *ptr = &pool[pool_offset];
pool_offset += size;
return ptr;
}
void init_alloc_lock(void) {
alloc_enabled = false; // 이후 할당 시도 시 assert 실패
}
main()에서 모든 초기화가 끝난 뒤 init_alloc_lock()을 호출하면, 이후 실수로 할당을 시도해도 즉시 검출된다. 해제가 없으므로 단편화와 누수가 원천 차단된다.
메모
- FreeRTOS는
configSUPPORT_STATIC_ALLOCATION을 1로 설정하면 모든 커널 객체를 정적으로 생성할 수 있다 - MISRA C:2012 Rule 21.3은
malloc,calloc,realloc,free사용을 금지한다 - 동적 할당이 불가피하면 메모리 풀을 사용해 단편화와 실행 시간 문제를 해결한다