STUDY

[3주차 TIL] KnockOn Bootcamp Pre.Rev : 메모리 구조와 매핑

da1seun9 2024. 12. 21. 23:09

개요

실행 파일은 로더에 의해 RAM에 로드 된 이후 CPU가 읽어 실행된다고 이전에 블로그에 올렸다.

RAM에 올려진 실행파일은 과연 어떤 구조일지 알아봤다.

메모리 구조

img

섹션

코드(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 영역에 저장한다는 것이다.

끝.