본문 바로가기
Embedded System/소프트웨어 (C,C++)

[F/W] 메모리 영역 (스택, 힙)

by MachineJW 2023. 8. 24.

보통은 MCU 제조사에서 제공하는 통합 개발 환경 (IDE)툴에서는 기본 코드의 기본값(Default)으로 되어 있는 스택의 크기와 힙 영역의 크기를 변경하지 않고 사용한다.

하지만, 펌웨어의 크기가 커질수록 또는 최적화해야 할 필요가 있다면, 자신의 시스템에 맞는 스택(Stack)과

힙(Heap)의 크기를 결정해야한다.

1. 메모리

복습의 차원에서 메모리는 여러 종류로 나뉠 수 있지만, ROM과 RAM의 차이 정도만 알아도 될 것 같다.

ROM의 경우 비휘발성, 전원이 꺼져도 사라지지 않는 데이터가 사라지지 않는 메모리.

RAM은 전원이 꺼지면 데이터가 사라지는 메모리이다.

그래서 보통 부트로더, 프로그램 코드 데이터는 플래시 메모리에 저장되고, 프로그램 사용시에

SRAM을 사용한다.

뭐 당연하게도 MCU마다 어떤 메모리를 사용하고 어떤 메모리의 영역을 사용하는지 다 다르지만, ROM과 RAM의 영역은 확실히 구분할 수 있다.

ESP32-WROOM-32의 경우 CPU 내부의 메모리가 있는데

448kB의 ROM은 부팅(부트로더)과 코어 함수(이는 아마도 HAL 일 것이다.)를 위한 메모리

520kB의 SRAM은 데이터를 위한 메모리

8kB의 SARM은 딥슬립모드 (MCU의 초절전 모드)에 사용할 메모리

등등 영역이 많이 나뉘어져 있다.

그리고 실제로 프로그램 코드와 SPIFFS(파일 시스템이라고 보면 됨)등은 CPU 외부 플래시메모리에 저장된다. (MCU가 아니라 CPU이다... ESP32-WROOM-32 외부에 있다가 아니라는 것이다.)

용량은 4MB (최대 16MB 까지 주문생산 가능하다고 한다...)

4MB에서도 OTA 활성화를 위한 영역, 어플리케이션 영역, 파일시스템을 위한 영역등 복잡하게 또 나뉜다.

Arduino에서 스케치로 올릴 수 있는 프로그램(이진 데이터)는 최대 1.3MB라고 하는 것 같은데 확실치는 않다.

나중에 확실하게 알아봐야 겠다.

2. 프로그램 실행 시 사용되는 메모리 영역

메모리의 용량이 어떻건 간에 프로그램은 실행될 때 일정한 메모리 영역을 사용하게 되어있다.

항상 그렇듯 정해진 것은 아니고 시스템을 하드웨어와 소프트웨어로 나눌 수 있듯이 가장 크게

힙, 데이터, 스택, 코드 영역으로 나눌 수 있다.

 

힙(heap) - 동적 할당 영역

데이터(Data) - 전역변수, 정적변수 영역

스택(Stack) - 지역변수, 함수를 호출할때도 사용됨

코드(Code) - 실행파일을 위한 영역

 

힙에 할당된 저장 공간은 지역변수와 마찬가지로 쓰레기 값이 있다. 그러나 지역변수와는 다르게 프로그램이 종료 될 때까지 메모리에 존재한다. 따라서 주소만 알고 있다면 특정 함수에 구애 받지 않고 사용이 가능하다.

지역변수와는 다르게 힙 영역에 동적 할당 된 저장공간은 함수가 반환되어도 메모리가 회수 되지 않기 때문에 세심하게 사용해야한다. (동적 할당 함수를 호출한 뒤에는 반드시 반환 값을 검사하여 메모리의 할당여부를 확인해야한다.)

3. Overflow (메모리 누수)

힙(heap)은 LILO(Last Input Last Outout, 후입후출 )

스택(Stack)은 LIFO(Last Input First Output, 후입선출)

즉, 힙의 영역은 메모리의 낮은 주소에서 부터 높은 주소로 할당되고 스택의 영역은 메모리 높은 주소부터 낮은 주소로 할당되는 방식을 갖는다. 이러한 구조 때문에 데이터를 많이 사용하다보면 스택 영역과 힙 영역이 만나게 되어 메모리 충돌을 일으키는데

스택이 힙의 영역을 침범하는경우 스택 오버 플로우라 하고, 힙이 스택 영역을 침범하는 경우는 힙 오버 플로우라 한다.

4. malloc(), free() 동적할당 사용 (힙영역의 사용)

힙의 메모리 영역은 malloc(), alloc() 함수와 같이 메모리를 동적으로 할당하는 영역으로 사용한다. 따라서

펌웨어에서 사용되는 동적할당 함수에 대해 적당한 크기의 영역을 할당하도록 한다.

만약 해체 시켜 주지 않고 계속해서 malloc()을 사용한다면, 오버플로우가 발생해 펌웨어는 오동작또는 정지 될 것이다. (용량이 넉넉한 현시대에서는 찾기 힘든 버그중에 하나이지만, 용량이 일반 PC에 비해 매우 적은 MCU의 메모리는 주의가 필요하다.)

#include <stdio.h>
#include <stdlib.h>

int main(){
	int *pi;
	double *pd;
	
	pi = (int *)malloc(sizeof(int));
	if (pi == NULL){
		printf("# 메모리가 부족\n");
		exit(1); // 프로그램 종료 
	}
	pd = (double *)malloc(sizeof(double));
	
	*pi = 10;
	*pd = 3.4;	
	printf("%d\n", *pi); // 동적할당된 영역에 저장된 값을 출력해보기 
	printf("%.1lf", *pd);
    
    /*꼭 free 함수로 동적 할당된 영역을 반환하는 습관을 가지자*/
	free(pi); // 동적 할당영역 반환 
	free(pd); 
	return 0;
}