01. Variables in Memory
# 스택 메모리와 변수
스택 메모리에 변수가 저장되는 방법은
스택 메모리의 가장 위 부분부터 데이터를 하나씩 쌓아가고
스택이 쌓이는 방향은 메모리의 주소가 감소하는 방향으로 쌓인다
# 예시
#include <iostream>
int main()
{
int a = 0;
int b = 0;
int c = a+b;
double d = 2.5;
std::cout << "a:" << (long)&a << std::endl;
std::cout << "b:" << (long)&b << std::endl;
std::cout << "c:" << (long)&c << std::endl;
std::cout << "d:" << (long)&d << std::endl;
}
실제로 메모리를 까보면 위 그림과 같이 저장된다
스택이 쌓일 때는 &a 부터 밑에서 위로 쌓이게 되고
메모리의 내부를 보았을 때는 &d가 먼저 보인다는 사실을 알 수 있다
02. Variable Types 1
# Fixed width integer types
컴파일러와 아키텍처에 따라서 int, unsigned int와 같은 변수들은
최소 16비트(2바이트)를 차지하기도 한다
이를 해결하는 방법은
1) assert를 거는 방법
#include <iostream>
int main()
{
static_assert(sizeof(int) == 4, "int is 4bytes");
int a = 0;
std::cout << sizeof(int) << std::endl;
std::cout << sizeof(a) << std::endl;
}
2) 고정적인 데이터 크기를 가지는 변수(Fixed width integer types)를 사용
#include <cstdint>
#include <iostream>
int main()
{
std::cout << sizeof(int8_t) << std::endl;
std::cout << sizeof(int64_t) << std::endl;
}
# array의 size
배열은 데이터의 개수만큼 데이터 크기가 증가한다
아래의 예시에서는 총 5 *4 바이트를 차지하게 된다
#include <array>
#include <cstdint>
#include <iostream>
int main()
{
//int a[10];
std::array<int, 5> b;
std::cout << sizeof(b) << std::endl;
}
# 포인터의 size
포인터는 주소를 담는 변수이고
주소는 크기가 변하지 않기 때문에
그 어떤 포인터라고 하더라도
운영체제에 따라서 4바이트 혹은 8바이트의 크기를 가진다
#include <cstdint>
#include <iostream>
int main()
{
uint64_t ui8;
uint64_t * ui64ptr = &ui8;
std::cout << sizeof(ui64ptr) << std::endl;
std::cout << (uint64_t)ui64ptr << std::endl;
}
03. Variable Types 2
# Struct의 size
구조체는 내부 변수들이 패딩되면서 차지하는 데이터 크기만큼 가진다
# Class의 size
클래스는 멤버 변수에가 차지하는 데이터 크기만큼 가진다
#include <iostream>
class Cat
{
public:
void printCat();
private:
int age;
int weight;
};
int main()
{
Cat cat1;
Cat cat2;
Cat * catPtr = &cat1;
std::cout << sizeof(catPtr) << std::endl;
return 0;
}
04. Stack Frame
# 스택 프레임
스택 메모리는 변수 단위로 쌓여가는 것이 아닌
스택 프레임 단위로 쌓여가고
이러한 스택 프레임은 function(함수) 단위가 된다
# 스택 프레임 예시 1
void foo()
{
int a;
int b;
};
void bar()
{
float c;
double d;
};
int main()
{
int a;
double d;
foo();
bar();
return 0;
}
# 스택 프레임 예시 2 - 클래스
스택 프레임이 쌓여가는 도중에
멤버 변수들을 접근 및 제어하기 위해서
클래스 멤버 함수는 해당 클래스를 가리키는 this라는 주소를 가지고 있다
class Cat
{
public:
Cat()
{
m_age = 1;
};
~Cat(){};
void addAge(int arg)
{
m_age += arg;
};
private:
int m_age;
};
int main()
{
Cat cat;
cat.addAge(10);
return 0;
}
05. Heap Intro
# 힙과 스택의 관계
int main()
{
int * ip;
ip = new int;
*ip = 100;
delete ip;
return 0;
}
# 힙이 필요한 이유
1) Life Cycle
스택은 스택프레임 단위로 계속 만들어지고 지워지기 때문에
Life Cycle 관리가 어렵다
반면, 힙은 직접 Life Cycle을 관리할 수 있다
2) Large Size
큰 사이즈의 데이터를 만드려고 한다면
스택에 큰 사이즈의 데이터를 만드는 것은 한계가 있다
반면, 힙을 이용해서
스택에는 힙의 포인터만 들고 있고
힙에 큰 사이즈의 데이터를 만드는 것은 가능하다
3) Dynamic (runtime)
스택 프레임은 크기가 컴파일 시에 모든 것이 결정된다
하지만 컴파일 시에 알 수 없는 정보를 사용할 때는
힙을 이용해서
런타임 중에 크기를 결정 및 관리할 수 있다
ex. 동적 배열 생성 가능
06. Heap Code
# c 스타일의 Heap 코드
c 스타일의 Heap 코드는 malloc과 free이다
이러한 코드는 class 객체 생성이 안 되는 문제점을 지니고 있어서 사용하지 않는 것이 좋다
# c++ 스타일의 Heap 코드
c++ 스타일의 Heap 코드는 new와 delete이다
이러한 코드는 delete를 작성해주지 않는 경우 발생하는 메모리 릭에 대처하기 번거롭다
# Safer c++ 스타일의 Heap 코드
따라서,
delete를 해주지 않아도 되는
Safer c++ 스타일의 코드를 사용하는 것이 좋다
Safer c++ 스타일의 코드는 스마트 포인터(변수) 혹은 벡터(배열)를 사용하는 것이다
벡터 자체는 스택 영역에 저장되고
힙 영역에서 할당된 메모리의 시작 주소를 저장하기 위한 포인터 (보통 4바이트)
벡터의 크기(Size)를 저장하기 위한 포인터 (보통 4바이트)
벡터의 용량(Capacity)을 저장하기 위한 포인터 (보통 4바이트)로 구성된다
그리고 벡터의 내부 객체들은 동적으로 할당된 힙 영역에 저장된다
#include <iostream>
#include <memory>
#include <vector>
class Cat
{
public:
Cat()
{
std::cout << "meow" << std::endl;
};
~Cat()
{
std::cout << "bye" << std::endl;
};
};
int main()
{
// Safer C++ style heap Class
std::unique_ptr<Cat> catp = std::make_unique<Cat>();
// Safer C++ style heap Class array
std::vector<Cat> cats(5);
// Safer C++ style heap int
std::unique_ptr<int> ip = std::make_unique<int>();
*ip = 100;
// Safer C++ style heap array
std::vector<int> ints(5);
ints[0]= 100;
}
06. Heap in Process
# Stack과 Heap의 내부
스택은 위에서 아래 방향으로 촘촘하게 내려온다
중간이 비어있지 않은 형태이다
힙은 아래에서 위 방향으로 여유롭게 올라간다
중간이 비어있는 형태이며
힙의 사용이 많아질수록 중간은 더 비어질 수도 있다
또한,
스택 프레임 내부의 순서는 컴파일러마다 다를 수 있다
힙 메모리의 공간 차이도 컴파일러 마다 다를 수 있다
07. Heap, Stack, Static
# 힙 vs. 스택
Stack
stack frame 단위로 저장
메모리 할당/해제 속도가 빠르다
큰 메모리 할당을 할 경우, stack overflow 발생한다
Heap
메모리 할당/해제 속도가 느리다
dynamic 할당이 가능하다 (런타임 중에 가능)
큰 메모리 할당이 가능하다
# 힙 vs. 스택 - 언제 사용하나
일반적인 경우는 Stack
크지 않은 메모리의 경우는 Stack
큰 메모리의 경우는 Heap
클래스의 경우는 일반적으로 Stack
클래스 배열의 경우는 vector를 사용해 Heap
메모리 할당/재할당이 빨라야 하는 클래스 배열의 경우는, array를 사용해 Stack
런타임 중에 메모리의 크기가 정해지는 경우에는 Heap
아래는 클래스 배열에서 vector 혹은 array를 사용하는 예제이다
#include <iostream>
#include <vector>
extern class Cat;
void foo(int num)// 할당/재할당 속도가 빠르지 않아도 되는 경우
{
std::vector<Cat> cats(num);
// do some computation over cats
};
bool bar(int num)// 할당/재할당 속도가 빨라야 하는 경우
{
if (1000 < num)
return false;
std::array<Cat, 1000> cats;
// do some computation over cats
};
int main()
{
int count;
std::cin >> count;
foo(count);
bar(count);
}
# Static(Global) 영역
전역 변수와 정적 변수는 Heap 아래 공간에 저장된다
08. Object Creation
Java에 익숙한 개발자는 new를 사용해서 객체를 생성한다
하지만 C++에서는
new를 사용하면, 메모리 릭 문제가 있고 allocation/disallocation이 느리다
따라서,
클래스를 생성할 때는
가급적 스택 위에 올리는 것이 좋고
힙에 올려야 한다면,
new를 쓰지 말고 스마트 포인터나 std::vector를 사용하는 것이 좋다
'C++ > [노코프] C++' 카테고리의 다른 글
[C++ NOTE] Smart Pointer (0) | 2024.03.29 |
---|---|
[C++ NOTE] Inheritance (0) | 2024.03.28 |
[C++ NOTE] OOP (0) | 2024.03.27 |
[C++ NOTE] Resource Move (0) | 2024.03.26 |
[C++ NOTE] Compile Process (0) | 2024.03.25 |