포스트

임베디드 스터디 - 데이터 타입과 메모리 레이아웃

임베디드 스터디 - 데이터 타입과 메모리 레이아웃

이번 글 참고자료:
EmbeddedInterviewlab

메모리 맵

  • C의 변수는 메모리에서 5개의 구역 중 하나에 배치된다.
    • stack : 함수 내 지역 변수
    • heap : 동적할당 변수
    • .rodata : const 전역 변수, const static 지역 변수, flash 메모리에 저장
    • .data : 초기화된 전역 변수, 프로그램 실행 시 flash 메모리에서 RAM으로 초기화값을 복사한다.
    • .bss : 초기화되지 않은 전역 변수, static 지역 변수, RAM에서 0 값으로 초기화된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// .rodata (flash) -- const + global scope = read-only data in flash
const uint32_t FIRMWARE_VERSION = 0x0102;

// .data (RAM, copied from flash at boot) -- initialized global
uint32_t sensor_count = 5;

// .bss (RAM, zeroed at boot) -- uninitialized global (zero by default)
uint32_t error_count;

// .data -- static global, same as non-static for memory purposes
static uint32_t module_id = 42;

// .bss -- uninitialized static global
static uint32_t call_count;

void read_sensor(uint16_t channel) {
    // Stack -- automatic storage, NOT initialized, destroyed on return
    uint8_t buffer[64];

    // .bss -- static LOCAL, persists between calls, zero-initialized once
    static uint32_t invocation_count;
    invocation_count++;

    // .rodata -- static const local, stored in flash (no RAM cost)
    static const uint16_t lookup[] = {0, 100, 200, 400, 800};
}
  • C로 개발한 프로그램은 링커 스크립트의 내용에 따라 변수의 저장 위치가 달라진다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    FLASH                                      RAM
            ┌─────────────────────┐               ┌─────────────────────┐ ← _estack
            │                     │               │  Stack  ↓           │   (initial SP)
            │  .text              │               │  local vars, args,  │
            │  (machine code:     │               │  return addresses   │
            │   main, ISRs)       │               │         ...         │
            │                     │               │                     │
            ├─────────────────────┤               │  (free space)       │
            │  .rodata            │               │                     │
            │  (const globals,    │               │         ...         │
            │   string literals,  │               │                     │
            │   lookup tables)    │               │  Heap   ↑           │
            ├─────────────────────┤               │  malloc'd memory    │
            │  .data init values  │ ──copy at──▶  ├─────────────────────┤
            │  (source for RAM    │    boot       │  .bss               │
            │   .data section)    │               │  (zeroed at boot)   │
            └─────────────────────┘               ├─────────────────────┤
          0x0800_0000 (STM32)                     │  .data              │
                                                  │  (copied from flash)│
                                                  └─────────────────────┘
                                                0x2000_0000 (STM32)
    

Int 변수 선언

  • 각 CPU의 스펙에 따라 int, long의 크기 달라지므로, 명확하게 데이터의 크기를 지정해야한다.
    • stdint.h 라이브러리를 통해 각 데이터의 명확한 크기를 정의하여 변수를 사용한다.
TypeBitsRange (unsigned)Range (signed)Use Case
uint8_t80 - 255-128 to 127Register bytes, flags, small counters
uint16_t160 - 65,535-32,768 to 32,767ADC values, sensor data, PWM
uint32_t320 - 4.29B-2.14B to 2.14BTimestamps, addresses, large counters
uint64_t640 - 18.4E-9.2E to 9.2ECryptography, precise time (avoid on 8/16-bit MCUs)
  • Implicit signed/unsigned conversion (usual arithmetic conversions)으로 인해 Int와 Uint 사이에 비교 연산자를 사용하는 경우 결과가 의도하지 않은 형태로 발생한다.(Int의 -1은 Uint의 UINT_MAX 값이 나온다.)
    • 비교 연산자 사용 시, Unsigned 끼리, Signed 끼리만 비교한다.
    • 변수 타입 변환 시 해당 변수에 명시한다. if ((int)y > x)
    • 컴파일러의 경고를 활용한다. -Wsign-compare, -Wconversion
    • MISRA C에서는 변수 타입 변환 시 모두 명시할 것을 요구하고 있다.

Stack vs Static vs Heap

  • Stack, Static, Heap 메모리는 각각의 용도에 맞춰 사용하는 것이 효율적이다.
    • Static은 보통 100Byte 이상의 데이터를 저장하는게 좋다.
    • Stack에는 작은 지역 변수나 함수 호출 오버헤드를 위해 남겨두는 것이 좋다.
    • Heap은 메모리 파편화, Stack-Heap 충돌 문제를 일으킬 수 있으므로, 특수 상황이 아니면 안 쓰는 것이 오류를 피하는 지름길이다.
CriteriaStack (auto)Static / GlobalHeap (malloc)
LifetimeFunction scope onlyEntire programUntil free()
InitializationNOT initialized.bss zeroed, .data from flashNOT initialized
Size limitSmall (1-8 KB total)Limited by RAMLimited by RAM
Allocation speedInstant (just move SP)Zero (placed at link time)Slow (search free list)
DeterministicYesYesNo (fragmentation)
ReentrantYesNo (shared state)Yes (if per-thread)
Best forSmall temps, loop varsConfig, LUTs, buffers, stateRarely used in embedded

프로그램 스타트업 절차

  • 디바이스는 아래 절차를 통해 프로그램을 실행한다.
    1. Power on : CPU가 실행되면 reset vector에서부터 메모리를 읽는다.
    2. Reset_Handler : 스택 포인터를 초기화하고, C runtime initialization을 수행한다.
    3. .data 복사 : 초기화된 전역, static 변수의 데이터를 flash에서 RAM으로 복사한다.
    4. .bss 초기화 : .bss 섹션을 모두 0으로 초기화한다.
    5. main() 함수 실행
  • 함수의 지역 변수는 함수가 호출된 후에 생성되므로 변수 초기화가 자동으로 이루어지지 않는다.
  • .data 섹션의 데이터량과 부팅시간은 비례한다.
    • 따라서 적절하게 변수를 선언하여 .data 섹션을 사용하는 것이 중요하다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.