포인터
스택 메모리는 빠르지만 작다. 메모리가 자동으로 정리(해제) 된다.
힙 메모리는 크지만 느림. 메모리를 직접 해제해주어야 한다.
Stack vs Heap
Stack
very fast access
don’t have to explicitly de-allocate variables
space is managed efficiently by CPU, memory will not become fragmented
local variables only
limit on stack size(OS-dependent)
variables cannot be resized
Heap
variables can be accessed globally
no limit on memory size
(relatively) slower access
no guaranteed efficient use of space, memory may become fragmented over time as blocks of memory are allocated, then freed
you must manage memory(you’re in charge of allocating and freeing variables)
variables can be resized using realloc()
포인터 변수는 변수의 타입 중 하나
포인터 변수의 값은 메모리의 주소(memory address)
→ 지금까지 사용한 변수와 포인터 변수와의 차이 : 값을 저장하는 지, 주소를 저장하는지
포인터는 가리키는 주소에 저장된 데이터의 타입을 알아야 함
포인터를 쓰는 이유
동적 할당을 통해 힙 영역의 메모리를 사용
변수의 범위 문제로 접근할 수 없는 곳의 데이터를 사용(참조자와 유사한 목적)
배열의 효율적인 사용
다형성은 포인터를 기반으로 구현됨
시스템 응용 프로그램 / 임베디드 프로그래밍에서는 메모리에 직접 접근이 필요하다.
포인터의 정의
기존 변수 타입 뒤에 ‘*’를 붙여 포인터 변수 정의
→ int* 까지를 타입으로 생각
포인터의 정의 및 초기화
int* int_ptr;
double* double_ptr = nullptr;
초기화를 하지 않으면 쓰레기 값이 들어있는 상태이므로 방지가 필요
Nullptr은 nowhere의 개념
→ 임의의 메모리 주소를 가리키고 있는 상태가 아니라, 아무것도 가리키지 않는 상태를 의미
변수의 주소값 얻어오기
포인터 변수는 주소값을 저장하므로, 주소값을 얻어 올 수 있어야 함
이를 위해 주소 연산자 ‘&’를 사용
→ 연산자가 적용되는 피연산자의 주소값이 반환됨
→ 피연산자는 주소값을 얻을 수 있는 종류여야함(l - value)
int n = 10;
cout << "Value : " << n << endl; // 10
cout << "Address : " << &n << endl; // 0x61ff00
cout << "Address : " << &10 << endl; << //error
int* p;
cout << "Value : " << p << endl; //0x66ff60 <- garbage value
cout << "Address : " << &p << endl; // 0x60ff18
cout << "Size : " << sizeof(p) << endl; // 4 for address
p = nullptr;
cout << "Value : " << p << endl; // 0
주소값의 이해
포인터 변수의 크기와 포인터가 가리키고 있는 대상의 크기는 별개이다.
포인터 변수들은 모두 같은 크기
→ X86에서는 4바이트
→ 포인터는 주소값을 저장하기 때문
타입은 왜 필요할까?
→ 해당 주소의 값에 접근할 때 몇 바이트 크기인지 알아야한다.
포인터의 타입
컴파일러는 포인터가 가리키는 타입이 맞는지 확인함
→ int* int가 저장된 주소만, double*는 double이 저장된 주소만 가리킬 수 있다.
int score = 10;
double score_precise = 100.7;
int* score_ptr = nullptr;
score_ptr = &score;
score_ptr = &score_precise; // compiler error
포인터의 역참조 (Dereferencing a Pointer)
포인터의 주소에 저장된 데이터에 접근
* 연산자를 사용
int score = 10;
int* score_ptr = &score;
cout << *score_ptr << endl; // 10
*score_ptr = 20;
cout << *score_ptr << endl; // 20
cout << score << endl; // 20
double high_tmp = 100.7;
double low_tmp = 37.4;
double* tmp_ptr = &high_tmp;
cout << *tmp_ptr << endl; // 100.7
tmp_ptr = &low_tmp;
cout << *tmp_ptr << endl; //37.4
동적 메모리 할당(Dynamic Memory Allocation)
런타임에 힙 메모리를 할당
→ 프로그램의 실행 도중 얼마나 많은 메모리가 필요한지 미리 알수 없는 경우 사용
→ 사용자의 입력에 따라 크기가 바뀌는 경우
→ 파일을 선택하여 내용을 읽어오는 경우등
→ 큰 데이터를 저장해야 할 경우(Stack은 크기가 작음, 몇MB정도)
→ 객체의 생애주기(언제 메모리가 할당되고 해제되어야 할지)를 직접 제어해야 할 경우
힙 메모리는 스택과 달리 스스로 해제되지 않음
사용이 끝나고 해제하지 않으면 메모리 누수 발생
동적 메모리 할당
new 연산자 사용
new의 역할(C의 malloc)
Heap 메모리 공간에 int 하나를 담을 수 있는 메모리 주소를 찾고 주소값을 반환
int* int_ptr = nullptr;
int_ptr = new int; // allocate integer int heap
cout<< int_ptr << endl; //0x2345f40
cout << *int_ptr << endl; // garbage value
*int_ptr = 100;
cout << *int_ptr << endl; // 100
delete int_ptr; // free
int_ptr = nullptr;
delete의 역할(C의 free) 해당 메모리 공간을 반환
동적 할당을 이용한 배열
new[ ], delete[ ] 연산자 이용
int* a_ptr = nullptr;
int size = 0;
cout << "size or array?";
cin >> size;
a_ptr = new int[size];
a_ptr[0] = 10;
a_ptr[1] = 20;
a_ptr[2] = 30;
delete[] a_ptr;
배열과 포인터 (Arrays and Pointers)
배열의 이름은 배열의 첫 요소의 주소를 가리킨다
포인터 변수의 값은 주소값이다.
포인터 변수와 배열이 같은 주소를 가리킨다면, 포인터 변수와 배열은 (거의)동일하게 사용 가능하다.
차이점 : 배열은 주소값을 정의 이후 변경 불가, sizeof( ) 반환값이 다름
int socre[]{100, 95, 90};
cout << score << endl; // 00F3FF08
cout << *score << endl; // 100
int* score_ptr = score;
cout << score_ptr << endl; // 00F3FF08
cout << *score_ptr << endl; // 100
cout << sizeof(score) << endl; // 12
cout << sizeof(score_ptr) << endl; // 4
int score[]{10, 95, 90};
int* score_ptr = score;
cout << score_ptr << endl; // 0x123400
cout << (score_ptr + 1) << endl; // 0x123404
cout << (score_ptr + 2) << endl; // 0x123408
cout << *score_ptr << endl; // 100
cout << *(score_ptr + 1) << endl; // 95
cout << *(score_ptr + 2) << endl; //90
//score_ptr[1] = *(score_ptr +1)
Const and Pointers
const의 포인터(pointers to const)
데이터가 const (바꿀 수 없다) / 포인터는 다른 데이터를 가리킬수 있다.
int high_score = 100;
int low_score = 60;
const int* score_ptr = &high_score;
*score_ptr = 80; // error
score_ptr = &low_score; // ok
const인 포인터(const pointers)
포인터가 const / 데이터는 변할 수 있다.
int high_score = 100;
int low_score = 60;
int* const score_ptr = &high_score;
*score_ptr = 80; // ok
score_ptr = &low_score; // error
const의 const인 포인터(const pointers to const)
둘 다 const
int high_score = 100;
int low_score = 60;
const int* const score_ptr = &high_score;
*score_ptr = 80; // error
score_ptr = &low_score; // error
Passing Pointers to a Function
포인터를 함수의 인자로 전달
Pass-by-address / 변수의 주소를 전달
void double_data(int* int_ptr){
*int_ptr *= 2;
}
int main(){
int value = 10;
cout << value << endl; // 10
double_data(&value);
cout<< value << endl; // 20
포인터의 반환
인자로 전달된 데이터(포인터)를 반환 → ok
int* largest_int(int* int_ptr1, int* int_ptr2){
if(*int_ptr1 > *int_ptr2){
return int_ptr1;
else{
return int_ptr2;
}
함수 내부에서 동적으로 할당된 메모리의 주소를 반환 → ok
int* create_a(int size, int init_value = 0){
int* new_storage = nullptr;
new_storage = new int[size];
for(int i = 0 ; i < size; i++){
*(new_storage + i) = init_value;
return new_storage;
}
int main(){
int *my_a = nullptr;
my_a = create_a(100, 10);
delete[] my_a;
return 0;
}
(주의!) 지역 변수에 대한 포인터 반환 → 안됨
int* dont_do_this(){
int n = 10;
int* num_ptr = &n;
return num_ptr;
}
void main(){
int* a = nullptr;
a = dont_do_this();
cout << *a << endl;
}
Pointer Cautions
초기화의 필요성
int* int_ptr // anywhere, use nullptr
...
*int_ptr = 100; // in vs, compiler protects
허상 포인터(dangling pointer)
두 포인터가 동일 데이터를 가리키다, 하나의 포인터가 메모리를 해제할 경우
지역 변수를 참조하고, 호출 스택이 끝나는 경우
int main(){
int* val = new int;
*val = 10;
int* val2 = val;
delete val2;
val2 = nullptr;
std::cout << *val << std::endl; // 쓰레기값 출력
new의 실패
가끔 발생할 수있다. 이련 경우 예외 처리 필요
메모리 누수(memory leak)
동적 할당으로 사용한 메모리는 반드시 해제해야 한다.
'Language > C++' 카테고리의 다른 글
07.OOP (0) | 2023.12.22 |
---|---|
06.참조자 (0) | 2023.12.22 |
04.Function (1) | 2023.12.22 |
03.basic-syntax (1) | 2023.12.22 |
02. 변수와 상수 (1) | 2023.12.22 |
댓글