프로그램이 변수를 인스턴스화 할때 사용 가능한 메모리 주소가 변수에 자동으로 할당되고, 변수에 할당된 값은 이 메모리 주소에 저장된다.
int x;
CPU가 위 문장을 실행하면 RAM의 메모리 조각이 따로 설정됨.
예를 들어 변수 x에 메모리 위치 140이 할당 되었다고 가정하면, 프로그램에서 변수 x를 표현식 또는 명령문으로 접근할 때마다 값을 얻으려면 메모리 위치 140을 찾아야함. 변수의 좋은 점은 우리가 어떤 특정한 메모리 주소가 할당되는지 걱정할 필요가 없다는 것이다. 지정된 변수를 참조하면 컴파일러에서 이 이름이 할당된 메모리 주소로 반환한다.
주소 연산자(&) (The address-of operator(&))
주소 연산자 &를 사용하면 변수에 할당된 메모리 주소를 확인할 수있다.
#include <iostream>
int main()
{
int x=5;
cout<<x<<endl;
cout<<&x<<endl;
return 0;
}
역참조 연산자(*) (The dereference operator(*))
변수의 주소를 얻는 것 자체로는 그다지 유용하지 않다.
역참조 연산자(*)를 사용하면 특정 주소에서 값에 접근할 수있다.
int main()
{
int x = 5;
std::cout << x << '\n'; // print the value of variable x
std::cout << &x << '\n'; // print the memory address of variable x
std::cout << *&x << '\n'; /// print the value at the memory address of variable x
return 0;
}
// prints:
// 5
// 0027FEA0
// 5
코드 세그먼트 : 컴파일된 프로그램이 저장되는 영역, 일반적으로 read-only 속성이다.
데이터 세그먼트 : 전역 변수 및 정적 변수가 저장되는 영역
힙 세그먼트 : 동적으로 할당된 변수가 할당되는 영역
스택 세그먼트 : 함수 매개 변수, 지역 변수 및 기타 함수 관련 정보가 저장되는 영역.
힙 세그먼트(Heap segment)
힙 세그먼트는 동적 메모리 할당에 사용되는 메모리를 적재한다. C++에서 new 연산자를 사용해서 메모리를 할당하면 이 메모리는 응용 프로그램의 힙 세그먼트에 할당된다.
int main()
{
int* ptr = new int;//ptr은 힙에서 4바이트로 할당됨.
int* array = new int[10];//array는 힙에서 40바이트로 할당됨.
//차례로 선언했지만 순차적 메모리 주소를 갖는 것은 아님
int* ptr1 = new int;
int* ptr2 = new int;
}
힙의 장단점
힙에 메모리를 할당하는 것은 비교적 느리다.
할당된 메모리는 명시적으로 할당 해제하거나 (delete) 응용 프로그램이 종료될 때까지 유지된다.(메모리 릭 주의
동적으로 할당된 메모리는 포인터를 통해 접근한다: 포인터를 역참조하는 것은 변수에 직접 접근하는 것보다 느리다.
힙은 큰 메모리 풀이므로 큰 배열, 구조체 또는 클래스를 할당할 수 있다.
스택 세그먼트
스택 세그먼트(=콜스택)는 메인() 함수부터 현재 실행 지점까지의 모든 활성 함수를 추적하고 모든 함수 매개 변수와 지역 변수의 할당을 처리한다.
스택은 후입선출(LIFO) 자료구조이다. 즉, 함수 호출이 끝나고, 이전 함수로 돌아갈 때 이 함수의 바로 이전함수로 돌아가야한다. 그래서 컴퓨터는 내부적으로 스택 세그먼트를 스택 자료구조로 구현한다.(재귀)
콜 스택(call stack) 이란 컴퓨터 프로그램에서 현재 실행 중인 서브루틴(함수)에 관한 정보를 저장하는 스택 자료구조이다.
응용 프로그램이 시작되면 메민() 함수가 운영체제에 의해 호출 스택에 푸시됨.
프로그램 실행
함수호출이 발생하면 함수가 콜 스택에 푸시됨.
현재 함수가 끝나면 해당함수는 콜 스택에서 해제 됨.
따라서 콜 스택에 푸시된 함수를 살펴보면 현재 실행 지점에서 이동하기 위해 호출된 모든 함수를 볼 수 있다.
#include <iostream>
using namespace std;
void func()
{
cout << "func" << endl;
}
void func2()
{
cout << "func" << endl;
}
void func3()
{
cout << "func" << endl;
}
int main()
{
int* ptr = new int;//ptr은 힙에서 4바이트로 할당됨.
int* array = new int[10];//array는 힙에서 40바이트로 할당됨.
//차례로 선언했지만 순차적 메모리 주소를 갖는 것은 아님
int* ptr1 = new int;
int* ptr2 = new int;
//콜 스택 ex
func();
func2();
func3();
}
콜 스택 자체는 고정된 크기의 메모리 영역이다.
스택 프레임(stack frame) : 콜 스택에 넣고 빼는 데이터, 하나의 함수 호출과 관련된 모든 데이터를 추적
스택 포인터(stack pointer) : cpu의 작은 조각인 레지스터는 현재 호출 스택의 최상위 위치를 가리킨다.
콜 스택 발생 순서
프로그램에 함수 호출이 발생
스택 프레임이 생성되고 콜 스택에 푸시된다. 스택 프레임은 다음과 같이 구성된다.
함수가 종료되면 복귀할 주소
함수의 모든 매개변수
지역 변수
함수가 반환할 때 복원해야 하는 수정된 레지스터의 복사본
CPU가 함수의 시작점을 점프한다.
함수 내부의 명령어를 실행한다.
함수가 종료 되면 다음단계가 수행된다.
레지스터가 콜 스택에서 복원된다.
스택 프레임이 콜 스텍에서 튀어나온다. 이렇게 하면 모든 지역 변수와 매개 변수에 대한 메모리가 해제된다.
반환 값이 처리된다.
CPU는 반환 주소에서 실행을 재개한다.
일반적으로 콜 스택이 어떻게 동작하는지에 대한 모든 세부사항은 중요하지 않다. 그러나 함수가 호출될 때와 종료될 때 함수가 콜 스택에서 효과적으로 작동하다는 것을 이해하면 재귀를 이해할 때와 디버깅할 때 유용하다.
스택 오버플로 (stack overflow)
스택 세그먼트는 크기가 제한되어 있으므로 제한된 양의 데이터만 저장할 수 있다.
window 운영체제에서 기본 스택 세그먼트의 크기는 1MB다.
응용 프로그램이 스택 세그먼트에 너무 많은 정보를 담으려고 하면 스택 오버플로(stack overflow)가 발생한다.
스택 오버플로는 보통 스택 세그먼트에 1)너무 많은 변수 2)중첩된 함수 호출 이다.
ex)1) 너무 큰 배열 2)무한 루프
void foo()
{
foo(); //2) 무한 루프
}
int main()
{
int stack[100000000]; //1) 너무 많은 크기의 배열
foo();
}
스택에 메모리를 할당하는 것은 비교적 빠르다.
스택에 할당된 메모리는 스택 범위에 있을 때만 접근 할 수 있다.
스택에 할당된 모든 메모리는 컴파일 타임에 알려진다. 메모리는 변수를 통해 직접 접근 가능.
스택은 비교적 크기가 작으므로 스택 공간을 많이 차지하는 지역 변수를 만드는 것은 좋지 않다.