임베디드 malloc 사용 주의점: 실패·누수·단편화·실시간성

카드뉴스

임베디드에서는 할당의 예측 가능성을 따져야 한다

`malloc()`은 필요한 크기의 메모리를 실행 중에 얻을 수 있어 편리하다. 하지만 RAM이 작은 MCU에서는 할당 성공 여부와 걸리는 시간이 그때의 힙 상태에 따라 달라진다. 장시간 재부팅 없이 동작하거나 정해진 deadline을 지켜야 하는 시스템이라면 평균적인 성공 사례보다 실패 조건과 최악 시간을 먼저 확인해야 한다.

동적 할당 자체가 언제나 금지되는 것은 아니다. 언제 호출하고, 얼마나 할당하며, 누가 해제하는지 정하지 않은 채 사용하는 것이 문제를 키운다.

NULL·누수·단편화는 각각 따로 확인한다

힙이 부족하거나 요청 크기의 연속 블록을 찾지 못하면 `malloc()`은 `NULL`을 반환할 수 있다. 반환값을 확인하지 않고 포인터를 사용하면 예외나 메모리 손상으로 이어진다. 실패 경로도 정상 코드와 마찬가지로 설계하고 시험해야 한다.

사용이 끝난 메모리를 `free()`하지 않으면 누수가 쌓인다. 한 번의 양이 작아도 반복되면 결국 힙을 소진한다. 단편화는 총 여유 공간이 남아 있는데도 빈 영역이 잘게 나뉘어 큰 연속 블록을 내주지 못하는 상태다. 그래서 free byte만 보고 다음 할당이 가능하다고 판단할 수는 없다.

실행 시간과 소유권이 버그의 모양을 바꾼다

allocator가 빈 블록을 찾고 정리하는 시간은 힙 배치에 따라 달라질 수 있다. 실시간 구간에서 사용한다면 최악 실행 시간이 deadline 안에 들어오는지 봐야 하며, ISR에서는 해당 구현이 명시적으로 허용하는 조건인지 먼저 확인한다.

여러 함수나 Task가 같은 포인터를 공유할 때는 소유자가 더 중요해진다. 누가 언제 해제하는지 정하지 않으면 해제된 메모리를 다시 쓰는 use-after-free나 두 번 해제하는 double free가 생긴다. `free(ptr)`를 호출해도 다른 포인터 복사본이 사라지거나 `ptr`이 자동으로 `NULL`이 되지는 않는다.

필요하다면 사용 범위와 관측 항목을 정한다

크기와 개수가 정해진 데이터는 static allocation이나 fixed-size·object pool이 더 예측하기 쉽다. 연속 데이터에는 ring buffer, 작업 단위로 한꺼번에 정리할 수 있는 데이터에는 arena 또는 region 방식도 선택지가 된다.

그래도 동적 할당을 쓴다면 startup에서만 허용하거나 호출 횟수와 최대 할당량을 제한한다. NULL 처리와 소유권을 인터페이스에 드러내고, 최소 잔여 힙과 실패 횟수를 모니터링한다. 반복 할당·해제와 메모리 부족을 포함한 worst-case test, static analysis까지 실행해야 사용 정책이 실제 조건에서도 유지되는지 확인할 수 있다.

댓글 남기기