메모리 | 들어갈 녀석들 |
---|---|
코드 영역 | 실행 할 코드가 저장되는 영역 |
데이터 영역 | 전역(global) / 정적(static) 변수 |
스택영역 | 지역 변수 / 매개 변수 |
힙 | ?? |
이런식으로 존나 크게 만들어 놓을 경우 한번 보자.
스택은 애초에 어마어마하게 많은 데이터를 저장하고 사용하는 용도가 아니라
함수들 끼리 인자를 좀 자연스럽게 주고받기 위해서 사용하는 메모리 영역이다.
저 코드가 실행이 되기는 하는데
문제점이 게임이 망해서 1명밖에 없더라도 500만 마리를 준비한 셈이 된다.
500만 마리를 데이터 영역에 만들면 프로그램 시작 500만마리 ~-> 프로그램 종료 까지 들고있어야됨.
이러한 메모리 없나? => 힙
malloc
free
new
delete
new[]
delete[]
이것들이 핵심적인 기능들이다.
운영체제 | 실행하는 것 |
---|---|
유저모드 | 사용자가 사용하는 곳, LOL, 유튜브 |
구분 | 구분 |
커널모드 | Windows or IOS 같은 운영체제 핵심 코드 |
이런 구조는 컴퓨터 뿐만 아니라 모바일 기기도 똑같다.
근데 이걸 왜 알아야하나?
LOL이라는 게임을 하다 "동적할당"이 필요한 상황이 발생한다면?
LOL이라는 프로그램에서 메모리를 만지고 조작하고 하다보면은
유튜브나 다른 프로그램에게도 영향을 줄 수도있다.
그래서 유저 영역에서 실행되는 프로그램들은
완전히 독립된 상태로 실행이 된다.
유저영역의 A라는 프로그램이 메모리가 추가적으로 필요해서
메모리를 추가적으로 사용해야 한다고 할 때
"메모리"라는 것 자체는 유저모드의 각기의 프로그램들이
"관리"하는게 아니라
"커널 영역"에다가 요청을 해가지고
"커널 영역"의 허락을 받아서 "메모리"를 받아 와야한다는 것이다.
1) 유저영역 :
운영체제 에서 제공하는 API(운영체제에서 제공하는 함수) 호출 -> 메모리를 달라고 요청
2) 커널영역 :
Windows와 같은 윤영체제관련 핵심 코드내에서
이런저런 검사를 해본 다음에 "메모리"를 적당히 할당해서 건내준다.
(실제 동작은 더 복잡한데 일단 정리하면 이런 느낌이다.)
3) 유정영역 :
ㄳㄳ 잘쓸게요~
컴퓨터는 지금 유저모드의 프로그램을 몇백개씩 관리를 하는데
롤에서 메모리 필요할 때마다 커널에다가 요청하면 귀찮다.
그래서
유저모드에서 메모리를 요청할때 한방에
큼지막하게 받아 준다.
[_ _ _ _ _ _ _ _ _ 힙 _ _ _ _ _ _ _ _ _ _ _ _ ]
그러면 이제 유저모드의 A라는 프로그램에서 메모리가 필요하다고
하면은
[_ _ _] _ _ _ _ _ _ 힙 _ _ _ _ _ _ _ _ _ _ _ _ ]
이만큼 찝어줘서 사용해~ 짤라서 쓰게된다.
이게 malloc이나 new이런 연산자와 관련이 있는 것이다.
C++ 에서 이런 힙메모리는
CRT : C런타임 라이브러리의 "힙 관리자" 를 통해 힙 영역을 사용한다.
단, 정말정말 원한다면 우리가 직접 운영체제 API를 통해
힙을 생성하고 관리할 수도있음.
-> MMORPG 서버 메모리 풀링(고급기법)
반환형이 void* 이고 인자타압이 size_t이다.
size_t정의 부분 잘라온것인데
typedef a, b하면
a라는 타입의 이름을 그냥 b로 하겠다.
unsigned __int64 를 size_t로 보겠다. 이말임.
그래서 우리가 실행하는 프로그램이 64비트이면
typedef unsigned __int64 size_t를 사용한다는 말이다.
두갈래길
unsigned 정수는 정수인데 무조건 양수만 받겠다.
그러면 이렇게 메모리를 할당받고 싶은 만큼 받아오면 (1000바이트)
알아서 잘 할당해준 다음
그 시작주소를 void형 포인터로 받아 올 수 있다.
malloc : 할당할 메모리의 크기를 매개변수로 넘겨준다 (바이트 단위)
메모리 할당후 시작 주소를 가르키는 void형 포인터로 반환해준다.
(만약 메모리가 부족하다면 NULL반환)
일단 주소를 담는 바구니? => OK
타고가면 void 아무것도 없나? => NO
타고가면 void 뭐가 있는지 모르겠으니까 니가 적당히 변환해서 사용해라? => OK
void* ptr = malloc(1000);
1000바이트를 사용한다고 했지만
어떻게 사용을 할지는 지정을 해준 상태는 아니기 때문에
// 이렇게 메모리 주소를 void* 로받고
void* ptr = malloc(1000);
Monster m1 = (Monster*)ptr;
m1->_hp = 100;
m1->_x = 1;
m1->_y = 2;
이런식으로 형변환을 통해서 동적할당하여 몬스터 만들 수 있다.
cdcdcdcd이런 이상한 값이 들어가있는데 (쓰레기 값)
그리고 이것을 Monster* 가정하고 값을 넣어서 사용하면은
// 이렇게 메모리 주소를 void* 로받고
void* ptr = malloc(1000);
Monster m1 = (Monster*)ptr;
m1->_hp = 100;
m1->_x = 1;
m1->_y = 2;
이런식으로 쓰레기 값에서 우리가 원하는 값으로 하나씩 변한다.
근데 지금 몬스터 멤머 변수 정수형 3개라 12바이트인데
1000바이트 할당? => 메모리 낭비
void* ptr = malloc(sizeof(Monster));
=> 굿.
macoll 혹은 기타 calloc, realloc 등을 통해서 할당된 영역을 해제를 할 때 사용한다.
힙관리자가 할당/미할당 여부를 구분해서 관리.
메모리를 실컷 사용하다가 필요없으면 이렇게 해제를 해준다.
이렇게 사용하다가 free해주면
디버그라 모드라 ddddddd로 밀어줌.
malloc같은 경우 12바이트라고 명시를 해주었는데
free는 그런인자 안받고 시작주소만 받고도 잘 해제를 한다.
자세히 보면은
c라고 12바이트라고 앞에 적혀있는데
malloc에 12바이트 넣어주면은 컴파일러가 컨닝 하기 위해서
[ [ 12 ] 사용하는곳 (12바이트) ] _ _ _ _ _ 힙 _ _ _ _ _ _ _ _ _ ]
힙관리자가 이렇게 헤더에 넣어 준 것이다.
유효한 힙 범위를 초과해서 사용할 경우
free안해주면 "메모리 누수" 발생한다.
=> 메모리 꽉차면 크래쉬 난다.
: 해제한거 다시 해제함 => 바로 클래쉬나서 찾기는 쉽다.
가장 끔찍한 상황이다.
지금 할당을 받아서 사용하다가 free로 해제해 주었는데
현재 void* pointer라는 녀석은 그 해제된 주소를 계속 가지고있다.
그렇다 보니까 pointer를 통해 접근이 가능함. 무섭단다 ㄷㄷ.
메모리가 날라갔지만 이렇게 또 건드릴 수 있다.
다른 유저의 골드나 경험치 같은 부분을 건들릴 수도있다.
바로 크래쉬가 안나고...
그래서 해제 한 뒤에는
이렇게 nullptr로 밀어줘야한다.
malloc / free 는 C/C++ 공용으로 사용가능.
이녀석 두개는 사실 "함수"이다 ❗❗❗
(고전적 방식)
C++에서 추가됨.
이녀석들은 "연산자"이다 ❗❗❗
뭐 사용방법은 비슷하고
double delete, Use-After-Feee 도 똑같이 발생할 수 있다.
배열 문법과 유사한 방식으로
Monster* m3 = new Monster[5];
// void* ptr = malloc(12 * 5); 랑 같다.
짝꿍을 잘 맞춰 줘야한다.
몬스터를 5마리 만들려고 new Monster[5]; 했었는데
Monster* m3 = new Monster[5];
m3->_hp = 100;
m3->_attakc = 200;
// 나머지 애들은 Monster자료형의 크기 만큼 한칸 건너뛰기위해서
Monster* m4 = (m3 + 1);
m4->_hp = 100;
m4->_attack = 300;
// 이런식으로 사용이 가능함.
또한 void* ptr = new Monster;
했을 때 처럼 메모를 들어가보면
표지판 처럼
크기를 나타내주는 부분이 있고 그다음 메모리들이 있음.
[ [ 12(Monster크기) ] hp, _attack, _defense ] _ _ _ _ _ _ 힙 _ _ _ _ _ _ _ _ _ ]
이런식으로'
Monster* m = new Monster[5];해도 똑같다.
사용 편의성 : new/delete 승!
타입에 상관없이 특정한 크기의 메모리 영역을 할당 받으려면? : malloc/free 승!
malloc/free : 함수
new/delete : 연산자
이런 차이점도 중요하지만
new/delete일 경우 생성타입이 클래스일 경우 생성자/소멸자를 호출해준다!
이녀석은 그냥 메모리만 할당할게~ 느낌이 강해서
생성자랑 소멸자 호출이 안되고
OOP와 직접적으로 연관된 new/delete사용하면
생상자와 소멸자가 호출이 된다.
근데 이게 당연한게
C언어에서는 클래스와 같은 객체 지향적인 그런게 없어서
호출이 안되는게 어떻게보면 당연하다.
그렇다고 malloc사용한다고해서 생성자랑 소멸자 사용할 수 없는 것은 아니다.
이렇게 여러개를 동적 할당 받아 만들었을 경우
delete만 해줄 경우 바로 크래쉬난다.
new / delete = 생성자 호출 / 소멸자 호출
이거를 짝을 맞춰 주어야한다.