그라운드 임베디드 EN 유튜브 구독

노트 / C 언어

volatile, const, static

최적화를 막는 volatile, 값을 고정하는 const, 범위와 수명을 바꾸는 static. 임베디드에서 가장 자주 묻는 키워드 세 개.

GROUND EMBEDDED · 정리 노트C 언어 10volatile, const, static 1volatile uint8_t rx_done = 0;// ISR에서 1로… 2while (!rx_done) { }// volatile…

volatile

while (!rx_done)컴파일러루프 안에서 안 바뀜한 번만 읽음while (1)영원히 못 나옴
volatile이 없으면 컴파일러는 값을 한 번만 읽고 루프를 무한 반복으로 바꿉니다.

컴파일러는 "이 변수는 내 코드 말고는 아무도 안 바꾼다"고 가정하고 읽기를 생략합니다. 하드웨어 레지스터나 인터럽트에서 바꾸는 플래그는 그 가정이 틀립니다.

volatile uint8_t rx_done = 0;   // ISR에서 1로 바뀜
while (!rx_done) { }            // volatile이 없으면 무한 루프가 될 수 있음

volatile은 매번 메모리에서 읽게 할 뿐, 여러 바이트 값을 한 번에 읽는 원자성까지 보장하지는 않습니다.

const

값을 바꿀 수 없다고 약속합니다. 큰 룩업 테이블에 const를 붙이면 RAM이 아니라 Flash에 놓여 RAM을 아낍니다. 함수 인자에 붙이면 "이 버퍼는 읽기만 한다"는 문서 역할도 합니다.

static

  • 함수 밖 변수와 함수에 붙이면: 이 파일 안에서만 보입니다. 모듈 내부를 숨기는 도구입니다.
  • 함수 안 변수에 붙이면: 함수가 끝나도 값이 남습니다. 호출 횟수를 세거나 이전 값을 기억할 때 씁니다.

volatile이 없을 때 벌어지는 일

인터럽트에서 깃발을 세우고 메인 루프에서 기다리는 흔한 코드입니다.

uint8_t rx_done = 0;              // volatile 없음

void USART1_IRQHandler(void) { rx_done = 1; }

int main(void) {
    while (!rx_done) { }          // 최적화하면 영원히 빠져나오지 못함
    process();
}

컴파일러 눈에는 루프 안에서 rx_done을 바꾸는 코드가 없습니다. 그래서 값을 한 번만 읽고 while (1)로 바꿔 버립니다. 디버그 빌드(-O0)에서는 잘 돌다가 릴리스 빌드(-O2)에서만 멈추는 버그의 대표적인 원인입니다. volatile uint8_t rx_done으로 선언하면 매번 메모리에서 다시 읽습니다.

volatile은 원자성을 보장하지 않는다

volatile은 "매번 읽고 써라"일 뿐입니다. 32비트 MCU에서 64비트 변수를 읽거나, 8비트 MCU에서 16비트 카운터를 읽는 도중 인터럽트가 값을 바꾸면 절반만 바뀐 값을 읽습니다. 이런 변수는 읽는 동안 인터럽트를 잠깐 막거나, 두 번 읽어 같은지 확인합니다.

무료 전자책이 주제를 한 권으로 이어 읽기 (PDF 3권)→

const는 어디에 놓이는가

Flashconst 표 · 초깃값 원본RAMconst 없는 표의 복사본
const를 붙인 표는 Flash에만 놓입니다. 붙이지 않으면 Flash에도 있고 RAM에도 복사됩니다.
const uint16_t sine_table[256] = { /* ... */ };   // Flash에 놓임, RAM 0바이트
uint16_t       sine_copy[256]  = { /* ... */ };   // Flash + RAM 512바이트

const를 붙이면 링커가 표를 Flash(.rodata)에만 둡니다. RAM이 몇 KB뿐인 MCU에서 룩업 테이블에 const를 빠뜨리면 그만큼 RAM이 사라집니다.

포인터에서는 위치에 따라 뜻이 달라집니다.

선언 바꿀 수 없는 것
const uint8_t *p p가 가리키는 값
uint8_t * const p p 자체(주소)
const uint8_t * const p 둘 다

static의 세 가지 쓰임

  • 함수 안: 값이 함수 호출 사이에 유지됩니다. 디바운스 카운터처럼 그 함수만 쓰는 상태에 알맞습니다.
  • 파일 범위 변수: 다른 파일에서 보이지 않습니다. 모듈의 내부 상태를 감출 때 씁니다.
  • 파일 범위 함수: 그 파일 전용 함수가 됩니다. 이름 충돌이 줄고 컴파일러가 인라인하기 쉬워집니다.

스스로 확인

  • 하드웨어 상태 레지스터를 가리키는 포인터에 const volatile을 함께 쓰는 이유는 무엇일까요?
  • 함수 안의 static 변수는 스택, 힙, 데이터 영역 중 어디에 놓일까요?
영상으로 이어 보기공부법과 진로 시리즈 15편2025 임베디드 프로그래밍 공부 순서 | 공부 방법 | 로드맵PROJECT COURSEC 문법을 실제 펌웨어 코드로 써 보고 싶다면밸런싱 로봇 하나를 요구사항, 회로와 PCB, 펌웨어, 앱까지 실무 순서대로 만드는 강의입니다. 10파트 165강.강의 살펴보기 →FREE PDF무료 전자책 3권 받기이 주제를 한 권으로 이어서 읽고 싶다면. BLDC 모터 제어, 직렬통신, 센서를 책 분량으로 묶었습니다. 이메일만 남기면 바로 받습니다.무료로 받기 →