c++ 포인터 참조자 (1) 개요
Today Study
◆ 포인터
◆ 참조자
(Summary) Pointers
◆ 포인터 개요
◆ 포인터의 정의
◆ 주소로의 접근
◆ 역참조
◆ 동적 메모리 할당
◆ 포인터와 배열
◆ 포인터와 const
◆ 포인터의 pass-by-address
◆ 주의사항
Pointers
stack 메모리는 자동으로 정리(해제) 된다.
함수가 끝나면 함수 구분선과 지역변수의 메모리가 해제된다.
Heap 메모리는 직접 해제해주어야 한다.
우측에 있는 스택메모리는 1 byte가 아니라
만일 지역 변수가 int 일 경우 4byte로 걸쳐서 데이터가 저장되는 것이다.
각 칸의 주소는 따로 따로 있으나 우측 하단에 주소값을 기입하였다.
int x 는 stack 메모리 4칸 중 첫번째 칸이 예를 들어 0x1000이라는 주소값을 가지고 있다면
두번째 변수 int y는 4개의 칸 중 첫번째 칸이 0x1000 중 4칸이 이미 할당되었기 때문에 0x1000 - 4로 표기 하였다.
알아두면 좋을 고급 내용
★★★
Stack
- 매우 빠른 접근 속도
- 변수를 명시적으로 해제할 필요가 없음
- 공간이 CPU에 의해 효율적으로 관리되며, 메모리가 조각화되지 않음
- 지역 변수만 사용 가능
- 스택 크기에 제한이 있음 (운영체제에 따라 다름)
- 변수를 크기 조정할 수 없음
Heap
- 변수를 전역적으로 접근할 수 있음
- 메모리 크기에 제한이 없음
- (상대적으로) 느린 접근 속도
- 메모리 공간이 효율적으로 사용된다는 보장이 없으며, 메모리 블록이 할당되었다가 해제되면서 조각화될 수 있음
- 메모리를 직접 관리해야 하며(변수 할당 및 해제를 직접 처리), 실수할 가능성이 있음
- 변수를 realloc()을 사용해 크기 조정 가능
C++ 메모리 관리의 Stack과 Heap에 대한 차이점을 분석한 이미지로
Stack은 빠르지만 제한적이고, Heap은 유연하지만 관리가 복잡하다는것을 강조한다.
Pointer
중요한 Part 이다. ★★★★★
◆ 포인터 변수는 변수의 타입 중 하나
● 포인터 변수의 값은 메모리의 주소(memory address)
☞ 지금까지 사용한 변수와 포인터 변수와의 차이 : 값을 저장하는지 vs 주소를 저장하는지
● 포인터는 가리키는 주소에 저장된 데이터의 타입을 알아야 한다.
◆ 포인터를 쓰는 이유
● 동적 할당을 통해 힙 영역 메모리를 사용
● 변수의 범위 문제로 접근할 수 없는 곳의 데이터를 사용 ( 참조자와 유사한 목적 )
● 배열의 효율적인 사용
● 다형성은 포인터를 기반으로 구현됨
● 시스템 응용 프로그램 / 임베디드 프로그래밍에서는 메모리에 직접 접근이 필요하다.
포인터는 메모리 주소를 저장하는 변수이다.
포인터를 쓰는 이유는 힙 영역에 메모리를 사용하기 때문이다.
그렇기에 동적 할당을 사용한다.
Definiton of Pointers
◆ 포인터의 정의
● 기존 변수 타입 뒤에 "*"를 붙여 포인터 변수 정의
☞ "int*" 까지를 타입으로 생각
variable_type * pointer_name
◆ 포인터의 정의 및 초기화
variable_type * pointer_name = nullptr;
int* int_ptr;
double* double_ptr = nullptr;
● 초기화를 하지 않으면 쓰레기 값이 들어있는 상태이므로 방지가 필요하다.
● Nullptr 은 "nowhere" 개념
☞ 임의의 메모리 주소를 가리키고 있는 상태가 아니라, 아무것도 가리키지 않는 상태를 의미
변수에 int 를 선언해야 하는 이유는 네개의 칸에 걸쳐서 a 데이터를 저장하기 때문이다.
포인터는 기본 타입에 " * " 를 붙여서 선언한다.
포인터를 선언할때 쓰레기값을 넣지 않기 위해서
초기화를 할때 nullprt로 선언하는게 좋다.
Accessing Pointer Address
◆ 변수의 주소값 얻어오기
● 포인터 변수는 주소값을 저장하므로, 주소값을 얻어올 수 있어야 한다.
● 이를 위해 주소 연산자 ("&") 를 사용한다.
☞ 연산자가 적용되는 피 연산자의 주소값이 반환된다.
☞ 피연산자는 주소값을 얻을 수 있는 종류여야 한다. (l-value)
int num = 10;
cout << "Value : " << num << endl; // 10
cout << "Address : " << &num << endl; // num 의 주소값
cout << "Address : " << &10 << endl; // ERROR! 10은 주소가 없다.
이런 개념이 된다.
&10에서 10은 r-value 이기 때문에 선언조차 에러가 난다.
int* p;
cout << "Value : " << p << endl; // garbage - 컴파일러 에러가 나와 실행 되지 않음
cout << "Address : " << &p << endl;
cout << "Size : " << sizeof(p) << endl; // 4 for address
p = nullptr;
cout << "Value : " << p << endl; // 0
디버그 x86 에서는 Size 가 4가 나오며,
x64 에서는 Size가 8이 나온다.
포인터도 변수처럼 이런식으로 선언 된다고 생각하면 된다.
포인터도 메모리에 값만 다를뿐이지 할당은 똑같다고 보면 된다.
◆ 주소값의 이해
● " 포인터 변수의 크기" 와 " 포인터가 가리키고 있는 대상의 크기 " 는 별개이다.
● 포인터 변수들은 모두 같은 크기
☞ x86 에서는 4byte
☞ 포인터는 "주소값"을 저장하기 때문이다.
● 타입은 왜 필요할까?
☞ 해당 주소의 값에 접근할 때 몇 바이트 크기인지 알아야 한다.
포인터는 주소만 저장하기 때문에
x86 에서는 4바이트로 크기가 동일하다.
https://youtu.be/CcuhhF76Qmw?si=mzH1w_VCf3ZtxcR3