메모리 모델
메모리 모델 (Memory model)
C 프로그램이 다루는 메모리는 어떻게 정의되어 있을까요? C는 실행 환경의 실제 하드웨어와 분리된 추상 기계(abstract machine) 위에서 동작한다고 생각해요. 그리고 이 추상 기계가 메모리를 어떤 기준으로 바라보는지 정해 놓은 게 바로 메모리 모델(Memory model)이에요. 이 기준을 알면 멀티스레드 코드에서 왜 데이터 레이스(Data race)를 조심해야 하는지도 자연스럽게 이해할 수 있어요.
본문
C 프로그램이 사용할 수 있는 데이터 저장 공간(메모리)은 하나 이상의 연속적인 바이트(byte) 시퀀스로 구성돼요. 메모리에 있는 각 바이트는 고유한 **주소(address)**를 가져요.
바이트 (Byte)
바이트는 메모리에서 주소를 붙일 수 있는 가장 작은 단위예요. 기본 실행 문자 집합(기본 문자 집합의 단일 바이트로 표현돼야 하는 96개 문자)의 모든 요소를 담을 수 있을 만큼 큰 연속된 비트 시퀀스로 정의돼요. C는 8비트 이상 크기의 바이트를 지원해요.
char, unsigned char, signed char 타입은 저장과 값 표현 모두에 정확히 1바이트를 사용해요. 바이트의 비트 수는 CHAR_BIT로 조회할 수 있어요.
다른 기본 타입의 값을 표현하는 데 바이트가 어떻게 쓰이는지(빅엔디안·리틀엔디안 메모리 배치 포함)는 객체 표현(object representation)을 참고하세요.
메모리 위치 (Memory location)
**메모리 위치(Memory location)**는 다음 중 하나예요.
- 스칼라 타입(산술 타입, 포인터 타입, 열거형 타입)의 객체
- 길이가 0이 아닌 비트 필드들의 가장 큰 연속 시퀀스
struct S
{
char a; // memory location #1
int b : 5; // memory location #2
int c : 11, // memory location #2 (continued)
: 0,
d : 8; // memory location #3
struct
{
int ee : 8; // memory location #4
} e;
} obj; // The object 'obj' consists of 4 separate memory locations
스레드와 데이터 레이스
실행 스레드(thread of execution)는 thrd_create나 다른 방법으로 최상위 함수를 호출하면서 시작되는 프로그램 내부의 제어 흐름이에요.
어떤 스레드든 프로그램 안의 어떤 객체에든 접근할 수 있어요(자동·스레드 로컬 저장 수명을 가진 객체도 다른 스레드가 포인터를 통해 접근할 수 있어요).
서로 다른 실행 스레드가 다른 메모리 위치를 동시에 읽고 수정하는 것은 항상 허용돼요. 간섭도 없고 동기화 요구도 없어요. (단, 같은 구조체 안에서 두 개의 비-원자 비트 필드를 동시에 갱신하는 건 안전하지 않아요. 그 사이에 선언된 멤버가 모두 (길이가 0이 아닌) 비트 필드라면, 그 비트 필드들의 크기가 어떻든 마찬가지예요.)
어떤 식 평가가 메모리 위치에 쓰기를 하고, 다른 평가가 같은 메모리 위치를 읽거나 수정하면 그 두 식은 **충돌(conflict)**한다고 해요. 두 개의 충돌하는 평가가 있는 프로그램은 다음 중 하나가 성립하지 않는 한 **데이터 레이스(Data race)**를 가져요.
- 두 충돌 평가가 모두 **원자 연산(atomic operation)**인 경우
- 하나의 충돌 평가가 다른 평가에 happens-before 관계인 경우(
memory_order참고)
데이터 레이스가 발생하면 프로그램의 동작은 정의되지 않아요(undefined behavior).
(특히 mtx_unlock은 다른 스레드의 같은 뮤텍스에 대한 mtx_lock과 synchronized-with 관계이고, 따라서 happens-before 관계예요. 그 덕분에 뮤텍스 잠금을 써서 데이터 레이스를 막을 수 있어요.)
이 절은 불완전해요. 작은 예시 하나나 둘을 추가하면 좋겠어요.
메모리 순서 (Memory order)
스레드가 메모리 위치에서 값을 읽을 때, 초기 값, 같은 스레드가 쓴 값, 또는 다른 스레드가 쓴 값을 볼 수 있어요. 스레드가 쓴 값이 다른 스레드에게 어떤 순서로 보이는지에 대한 자세한 내용은 memory_order 문서를 참고하세요.
더 알아보기
- 메모리 위치와 데이터 레이스는 C11부터 표준에 포함된 동시성(concurrency) 개념의 기초예요.
- 원자 연산과 메모리 순서를 같이 보면 멀티스레드 프로그램을 더 안전하게 짤 수 있어요.
- cppreference의 Memory model 원문에서 표준 절 번호와 결함 보고 기록을 더 볼 수 있어요.