AI/Build Log

memory section이란 무엇인가? 개념부터 직접 만들기까지 (.text, .data, .bss, .rodata)

by 방구석 임베디드 2026. 3. 16.
반응형

이전 글에서 메모리의 기본 구조를 살펴봤습니다.

이번 글에서는 조금 더 깊이 들어가서, 메모리가 어떻게 나뉘어 있는지, 그리고 그 구역을 직접 만들 수도 있다는 것을 보여드리겠습니다.


section이 뭐예요?

section을 사전에서 찾아보면 '부분' 이라고 나옵니다.

맞습니다. 메모리를 용도별로 나눈 '구역' 이 바로 section입니다.

집에 비유하면 이렇습니다. 집 안에는 거실, 주방, 침실, 창고가 있습니다. 각각 용도가 다르죠. 메모리도 마찬가지입니다. 코드가 들어가는 구역, 초기값이 있는 변수 구역, 초기값이 없는 변수 구역... 이렇게 용도에 맞게 나뉘어 있습니다.


코드로 바로 보기

아래 코드를 보겠습니다.

int Test1;
int Test2 = 2;
const int Test3 = 3;

int main(void)
{
    int Test4 = 4;
    Test1++;
    Test2++;
    Test4++;

    return 0;
}

변수가 4개 있습니다. 이 4개가 각각 다른 section에 들어갑니다.

변수 특징 들어가는 section

Test1 초기값 없는 전역변수 .bss
Test2 초기값 있는 전역변수 .data
Test3 const (읽기 전용) .rodata
Test4 함수 안의 지역변수 Stack

왜 굳이 나눠놓은 걸까요?

"그냥 한 군데에 다 넣으면 안 되나요?"

안 됩니다. 용도가 다르기 때문입니다.

.bss — 초기값 없는 전역변수

Test1처럼 초기값이 없는 변수는 Flash(ROM)에 저장할 내용이 없습니다. 그냥 RAM 영역에 공간만 잡아주면 됩니다. start code에서 해당 RAM 영역의 범위만 할당해주면 끝입니다.

.data — 초기값 있는 전역변수

Test2는 초기값 2가 있습니다. 이 경우에는 Flash에 2라는 값이 저장되어 있어야 합니다. 그리고 프로그램이 시작될 때 start code가 Flash에서 그 값을 읽어서 RAM으로 복사합니다. 초기값이 있으니 Flash 어딘가에 그 값이 보관되어 있어야 하는 거죠.

.rodata — const 변수

Test3는 const입니다. 읽기만 하면 되는 데이터입니다. RAM에 올릴 필요가 없습니다. Flash 안에만 있으면 충분합니다. 그래서 start code에서 따로 RAM 영역을 할당하지 않습니다.

Stack — 지역변수

Test4는 함수 안에서 선언된 지역변수입니다. 함수가 호출될 때 Stack에 올라갔다가, 함수가 끝나면 사라집니다.


section을 나누면 어떤 이점이 있나요?

section별로 나뉘어 있어야 효율적인 메모리 사용이 가능합니다.

  • 초기화가 필요 없는 변수를 Flash에 저장할 필요가 없으니 → Flash 낭비 없음
  • 읽기 전용 데이터는 RAM에 복사하지 않아도 되니 → RAM 절약
  • 필요한 것만 필요한 곳에 → 시작 시간 단축

임베디드에서는 Flash와 RAM 용량이 모두 제한되어 있습니다. 이걸 잘 관리하는 것이 펌웨어 개발의 핵심 중 하나입니다.


컴파일러마다 section이 더 세분화되어 있다

지금까지 본 .text, .data, .bss, .rodata는 어느 MCU, 어느 컴파일러에서나 공통으로 쓰이는 기본 section입니다.

하지만 실제 개발 환경에서는 더 세분화되어 있습니다.

예를 들어 Aurix Development Studio에서 사용하는 TASKING 컴파일러를 보면 아래와 같이 다양한 section type이 있습니다.

Section type Name prefix Description

code .text program code
neardata .zdata initialized near data
fardata .data initialized far data
nearrom .zrodata constant near data
farrom .rodata constant far data
nearbss .zbss uninitialized near data (cleared)
farbss .bss uninitialized far data (cleared)
a0data .data_a0 initialized a0 data
a0rom .rodata_a0 constant a0 data
a0bss .bss_a0 uninitialized a0 data (cleared)

앞에 z가 붙거나 뒤에 a0, a1, a8, a9 등이 붙은 것들은 MCU 안에서 더 효율적인 메모리 관리와 속도를 위해 만들어진 MCU 전용 section이라고 생각하면 됩니다.


section을 직접 만들 수도 있다

여기서 중요한 포인트가 하나 있습니다.

"우리가 직접 section을 만들어서 원하는 위치에 배치할 수 있다"

TC275 프로젝트를 예로 들겠습니다. TASKING 컴파일러에서는 #pragma 를 사용해서 section을 만들 수 있습니다.

#pragma protect on
#pragma section code "test"   // "test"라는 이름의 code section 시작

void TestFunction(void)
{
    cnt++;
}

#pragma protect restore
#pragma section code restore   // section 종료, 원래대로 복구

이렇게 작성하면 TestFunction 함수가 .text.test라는 section에 들어갑니다.

컴파일 후 맵 파일을 확인해보면 .text.test section이 실제로 만들어진 것을 볼 수 있습니다.


만든 section을 원하는 주소에 배치하기

section을 만드는 것에서 끝이 아닙니다. 링커스크립트를 수정해서 원하는 메모리 주소에 배치할 수 있습니다.

예를 들어 TestFunction을 0x80001000 주소에 올리고 싶다면, 링커스크립트에 아래와 같이 추가합니다.

group (ordered, run_addr=0x80001000)
{
    select "*.test";
}

컴파일 후 맵 파일을 다시 확인하면 .text.test section이 0x80001000에 배치된 것을 확인할 수 있습니다.

원하는 영역에 배치가 된 것입니다.

함수뿐만 아니라 변수도 동일한 방식으로 원하는 위치에 배치할 수 있습니다.


정리

결국 메모리 section이라는 개념은 이 흐름으로 이해하면 됩니다.

  1. 컴파일러가 코드를 컴파일하면서 변수와 함수를 용도에 맞게 section으로 분류한다
  2. 링커스크립트가 그 section들을 실제 메모리 주소에 배치한다
  3. 필요하다면 직접 section을 만들어서 원하는 위치에 올릴 수 있다

이 모든 과정을 링커스크립트가 담당합니다.

메모리를 잘 관리하는 것, 그게 펌웨어 소프트웨어의 꽃이라고 생각합니다.

반응형

댓글