개요
실행 파일은 로더에 의해 RAM에 로드 된 이후 CPU가 읽어 실행된다고 이전에 블로그에 올렸다.
RAM에 올려진 실행파일은 과연 어떤 구조일지 알아봤다.
메모리 구조
섹션
코드(Code)섹션 (.text 섹션)
프로그램의 실행 코드가 저장되는 영역
- 용도
- 함수, 명령어와 같은 실행 가능한 코드가 위치
- Read-Only로 설정되어 코드 수정이 방지
- 특징
- 여러 프로세스가 동일한 프로그램 실행 시 코드섹션 영역을 공유
- 프로그램의 컴파일된 기계어 코드가 저장되어 있는 영역
- 권한(R-X)
- R(read) : CPU가 코드를 읽어 실행해야 하므로 허용
- X(execution) : 명령어 실행을 위해 허용
데이터(DATA) 섹션
전역 변수 및 정적 변수들이 저장되는 영역
- 용도
- 초기화된 전역 / 정적 변수가 저장됨.
- 특징
- 읽기/쓰기가 가능
- 데이터 크기가 고정되어 있음.
- 프로그램 시작과 함께 할당, 프로그램 종료시 소멸
- 권한(RW-)
- R(read) : 변수 값을 읽어야 하므로 허용
- W(write) : 변수 값이 수정될 수 있으므로 허용
BSS(Block Started by Symbol) 섹션
초기화되지 않은 전역/정적변수가 저장되는 영역으로, DATA 영역과 같이 있음.
용도
- 초기화되지 않은 데이터((ex) int x;)가 저장되며, 실행시점에 0으로 초기화됨.
특징
- 실행 파일에 포함되지 않고 실행 시점에 할당.
권한(RW-)
- R(read) : 변수 값을 읽어야 하므로 허용
- W(write) : 변수 값이 수정될 수 있으므로 허용
.RODATA 섹션
읽기 전용 데이터를 저장하는 영역
- 용도
- 문자열 상수, 상수 배열, 변경되지 않는 상수 값들이 저장
- 특징
- 실행 파일에서 데이터로 간주됨
- ex) "hello world", "%d" 등
- 권한(R--)
- R(read) : 상수 값을 읽어야 하므로 허용
힙(Heap)
동적 메모리 할당을 위한 영역
- 용도
- malloc, calloc, new 등을 통해 런타임 시 동적으로 할당된 데이터 저장
- 특징
- 크기가 동적으로 증가하거나, 감소
- 메모리 누수(해제되지 않은 메모리) 위험
- 권한(RW-)
- R(read) : 동적으로 할당된 값을 읽어야 하므로 허용
- W(write) : 할당된 값의 변경을 위해 허용
스택
함수 호출 시 지역변수, 매개변수, 복귀주소 등이 저장되는 영역
- 용도
- 함수 실행 중 사용되는 임시 데이터를 저장
- 지역변수 등
- 특징
- 함수 호출 시 메모리가 할당되고, 함수 종료 시 해제됨
- LIFO(Last In First Out, 후입선출) 구조
- 권한(RW-)
- R(read) : 변수 값을 읽어야 하므로 허용
- W(write) : 변수 값이 수정될 수 있으므로 허용
실습
정확한 확인을 위해 실습을 진행했다.
총 3개의 코드가 있고, a.c파일은 정적변수와 초기화되지 않은 정적 변수가 있다.
b.c 파일은 초기화 된 정적변수만 있다.
c.c 파일은 정적변수가 없다.
이것에 대해 어셈블리어에서의 DATA 영역의 변화를 간접적으로 확인해 볼 것이다.
//a.c
#include<stdio.h>
int a = 0;
int b;
int main(){
printf("hello, world!");
int test = 1;
printf("%d",test);
return 0;
}
//a.s
.file "a.c"
.text
.globl a
.bss
.align 4
.type a, @object
.size a, 4
a:
.zero 4
.comm b,4,4
.section .rodata
.LC0:
.string "hello, world!"
.LC1:
.string "%d"
.text
.globl main
.type main, @function
//b.c
#include<stdio.h>
int a = 0;
int main(){
printf("hello, world!");
int test = 1;
printf("%d",test);
return 0;
}
//b.s
.file "b.c"
.text
.globl a
.bss
.align 4
.type a, @object
.size a, 4
a:
.zero 4
.section .rodata
.LC0:
.string "hello, world!"
.LC1:
.string "%d"
.text
.globl main
.type main, @function
//c.c
#include<stdio.h>
int main(){
printf("hello, world!");
int test = 1;
printf("%d",test);
return 0;
}
//c.s
.file "c.c"
.text
.section .rodata
.LC0:
.string "hello, world!"
// 문자열은 기본적으로 .rodata에 있음.
.LC1:
.string "%d"
.text
.globl main
.type main, @function
보아하니 어셈블리코드에서 차이는 아래와 같다.
.file "a.c"
.text // .text 섹션
.globl a // 전역변수 a 선언
.bss // .bss 섹션
.align 4
.type a, @object
.size a, 4
a: // 초기화 되지 않은 변수 a를
.zero 4 // 0으로 초기화
.comm b,4,4 // b라는 심볼을 4byte 크기로 선언
.section .rodata // 읽기 전용 데이터 섹션
변수 a를 0이 아닌 1로 초기화 했을때는 다음과 같이 초기화가 된다.
a:
.long 1 // 초기화된 전역변수 .data 섹션에 저장
앞서 말한 것과 같이 .bss 영역의 변수들은 0으로 초기화된다 했는데 왜 어셈블리에는 없냐 하면은, 운영체제 실행 시 자동으로 0으로 초기화를 해주기 때문에 명시적으로 초기화를 하지 않아도 된다.
궁금한점
Q. BSS 영역에서 어차피 실행 시점에 초기화를 0으로 할 것이면, 굳이 초기화를 안해도 되지 않을까?
맞다. 오히려 초기화 코드를 추가하면 컴파일러가 이를 별도의 작업으로 처리하기 때문에 불필요한 코드가 생길 수 있고, 이 때문에 파일 크기가 커질 수 있다.
하지만 가독성이나 0이 아닌 초기값을 사용할 경우, 혹은 지역변수 일경우에는 초기화를 해야한다.
특히 지역변수는 전역변수처럼 자동으로 초기화되지 않고 쓰레기 값을 가지기 때문에 꼭 하는 것이 좋다.
Q. 실습에서 전역변수 a를 0으로 초기화 했는데 왜 .data 섹션이 아닌 .rodata 섹션만 있는 거지?
명확하게 말하자면, 컴파일러나, 빌드 옵션에 따라 다를 수 있다.
일반적으로는 초기화 된 값은 .data섹션, 초기화하지 않은 값은 .bss 섹션인데, 컴파일러나 빌드 옵션이 최적화나 크기 절감을 우선으로 한다면, 초기값이 0일 경우에도 .bss로 넣는 경우가 있다.
여기서 사용한 컴파일러는
int a = 0;은 초기화된 전역변수이지만 값이 0으로 초기화 되었다.
0으로 초기화된 전역변수는 bss섹션에 배치된다.
실행 시 운영체제가 자동으로 0으로 초기화 하기 때문에, 실행파일의 크기를 줄이기 위해 .data 섹션이 아닌 .bss 섹션에 배치된다.
즉, int a = 0;으로 초기화 하는거나, int a;로 초기화 하지 않고 선언하는거나 똑같은 결과이기 때문에 실행파일의 크기를 줄이기 위해서라도, 똑같이 간주하고 bss 영역에 저장한다는 것이다.
끝.
'STUDY' 카테고리의 다른 글
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 포켓몬 게임 제작 Project (0) | 2024.12.22 |
---|---|
[3주차 TIL] KnockOn Bootcamp Pre.Rev : ELF 파일 구조 (3) | 2024.12.22 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 링커와 로더 (0) | 2024.12.21 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 컴파일러 (0) | 2024.12.21 |
[3주차 TIL] KnockOn Bootcamp Pre.Rev : 아키텍쳐 (0) | 2024.12.21 |