본문 바로가기

컴퓨터 과학/OS (+약간의 컴퓨터 구조)

프로그램이(프로세스가) 실행 될 때, 메모리의 구조 (data, bss 영역)

2022.06.23 - [컴퓨터 과학/OS (+약간의 컴퓨터 구조)] - 프로그램이(프로세스가) 실행 될 때, 메모리의 구조 (code, text 영역)

 

프로그램의 동작을 위한 명령어가 기계어로 exe 파일에 저장됨을 이전 포스팅에서 다루었다. 하지만 프로그램이 동작하기 위해서는 명령어뿐만 아니라 데이터를 저장하는 공간 (= 변수)가 필요하다는 사실을 알고 있다. 이번에는 변수 중에서도 제약 없이 접근 가능한 변수인 전역 변수에 대하여, 전역 변수를 위한 메모리 공간에 대하여 적고자 한다.


 

위는 유명한 프로세스의 메모리 구조 이미지를 gif로 만든 것이다. 많은 문서들에서 전역 변수가 데이터 영역에 저장된다고 하는데 왜 전역 변수를 위한 공간을 따로 둔 것일까?

 

일반적인 지역변수는 이후 포스팅에서 설명할 함수의 스택 포인터인 esp (x64 환경 rsp) 레지스터의 값을 기준으로 한 상대 주소로 표현이 된다. 그렇기 때문에 함수 A 내부의 변수 a와, 함수 B 내부의 변수 a는 서로 다른 주소를 갖게 된다. (추가적인 내용은 메모리의 구조 stack 영역 포스팅을 확인하자.)

 

지역변수와 다르게 전역 변수는 어떤 함수에서든 접근이 가능해야 한다. 그러므로 호출된 순간마다 달라지는 값이 아닌, 변하지 않는 절대 주소를 전역 변수에게 물려주어야 한다. (진정한 의미의 변하지 않는 주소는 아니다, 프로세스가 실행될 때마다 바뀌지만 일단 한번 실행된 이후에는 다시 실행되기 전까지 바뀌지 않는다.)

 

아래의 예제를 보자.

 

#include <stdio.h>

int a;

int main()
{
	int b;
	a = 1;
	b = 1;

	printf("%d, %d", a, b);
	return 0;
}

 

상대주소로 접근하는 지역변수 b와 절대주소로 접근하는 전역변수 a

 

변하지 않는 주소를 물려주기 위하여 전역 변수는 스택 영역 내부에 존재하지 않고 외부에 존재하게 되는데, 그러한 전역 변수들이 모인 공간을 "데이터 영역"이라고 부르는 것이다.

 


전역 변수를 위한 공간인 데이터 영역이 왜 필요한지 알아보았다. (정확히는 데이터 영역을 따로 만들어두고 그곳에 전역 변수를 넣는다는 개념보다는, 전역변수를 어딘가에 모아두는데 그곳을 데이터 영역이라고 부르는 것에 가깝지만...)

 

그런데 전역 변수라고 해서 모두 다 같은 데이터 영역에 들어가는 게 아니다. 초기화된 전역 변수가 위치하는 공간과, 초기화 되지 않은 전역변수가 위치하는 공간이 서로 다르다.

 

아래의 예제를 보자.

 

#include <stdio.h>

int a1 = 10;
int a2;

float b1 = 10;
float b2;

double c1 = 10;
double c2;

char d1 = 'a';
char d2;


int main()
{
	int b;

	a1 = 1;
	a2 = 1;

	b1 = 1;
	b2 = 1;

	c1 = 1;
	c2 = 1;

	d1 = 'b';
	d2 = 'b';

	return 0;
}​


초기화된 전역변수 빨강이들과 초기화되지 않은 전역변수 노랑이들

 

변수의 선언은 초기화 여부와 관계없이 선언되었다. 하지만 실제 메모리들의 주소를 확인해보면

초기화된 전역 변수들 (빨강)은 0x07FF7E93030?? 대의 메모리를 가진다.

초기화되지 않은 전역 변수들은 0x07FF7E93036?? 대의 메모리를 가진다.

 

메모리 주소는 바이트를 16진수로 표현한 것 이기 때문에 주소상 600의 차이는 1536 바이트의 차이를 의미한다.

같은 색끼리 떨어져 있는 거리가 각 변수의 크기에 맞게 떨어져 있는 것에 비하면 초기화 유무에 의해 떨어지는 거리는 꽤나 멀다. 

 

거리뿐만 아니라 후술 할 특징 또한 매우 다르기 때문에 전역 변수가 위치하는 공간을 지칭하는 이름을 세분화하게 되었는데.

초기화된 전역 변수가 위치하는 공간을 data 영역이라 부르고, 초기화되지 않은 전역 변수가 위치하는 공간을 BSS(block started by symbol) 영역이라고 부르게 되었다.

 


초기화된 전역 변수와, 초기화되지 않은 전역 변수의 특징이 다르기 때문에 서로 다른 위치에 변수를 위치시킨다고 하였는데, 정확히 어떤 점이 다른 것 일까?

 

아래의 예시를 보자.

// program 1
#include <stdio.h>

int main()
{
	

	return 0;
}​

//program 2
#include <stdio.h>

int arr1[10000] = {
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 ,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1 , };

int main()
{
	

	return 0;
}

//program 3
#include <stdio.h>

int arr1[1000];

int main()
{
	

	return 0;
}


모두 동일하게 main 함수 내에서 아무것도 하지 않는다.
차이는 오직 전역 변수의 유무와, 초기화 유무이다.

세 프로그램을 빌드해보면 exe 파일의 크기 차이를 볼 수 있다.

 

초기화된 전역 변수 같은 경우 해당 초기화 값이 직접 exe 파일에 찍혀 나온다.

특히 위 예시처럼 10000개 배열을 만들고, 100개만 초기화하더라도 -1 100개와 0 9900개로 초기화된 배열의 전체가 모두 메모리에 박히게 된다. 그래서 메모리 차이가 39KB 씩이나 발생한 것이다.

 

하지만 초기화되지 않은 전역 변수 같은 경우 해당 동작이 없다.

10000개짜리 배열을 만들었으므로 0이 10000개인 배열이 exe 파일에 들어 있어야 한다고 생각할 수 있다. 하지만 그것은 0으로 초기화된 배열이지 초기화되지 않은 배열은 아니다. (실제로는 같게 나오지만 개념적으로는 분명 다르다.)

그렇기 때문에 프로그램의 크기 (= exe 파일의 크기)를 늘리지 않는다.

 

프로그램이 실행될 때엔 코드 영역과 data 영역을 올린 뒤, 초기화되지 않은 전역 변수를 위한 메모리를 추가로 올리기만 (= 커밋) 하면 공간의 확보는 끝난다. 초기화되지 않은 변수의 값은 실행 도중 채워질 것이므로 변수의 값을 따로 기억할 필요가 없다는 특징 차이가 있기 때문에 두 종류 전역 변수의 공간을 분리하게 된 것이고 이것이 data 영역과 BSS 영역으로 불리는 것이다.

 

 

 

 

 

(+) exe 파일을 열어 실제 data 영역 확인해보기 & const 값 확인

더보기

 

오른쪽은 data 영역이 따로 없는 program 3 이다.

왼쪽의 초기화된 영역의 값을 수정하면 실제 프로그램 실행 시에도 수정된 값으로 arr1 이 초기화되어 있다.

 

또한 상수 값도 data 영역에 저장이 되는데 상수값은 저장될 필요 있다는 특징은 초기화된 전역 변수와 특징을 공유하기 때문으로 생각된다.

 

다만 값을 변경할 수 없어야 한다는 특징 차이가 있는데, 이것을 왜 read only 보호 설정이 되어있는 code 영역에 두지 않았는지는 명확히 이해할 수 없다. 

const 값의 확인, 이 프로그램의 code 영역은 0x00000EE02 번지에서 끝나있었다

 

해당 내용은 이후 (알게 되면 & 기억나면) 조건하에 갱신하도록 하겠다.

 

(혹시 해당 답을 알려주실 수 있는 분이 계신다면 매우 감사하겠습니다.)