오늘의 TIL 목차 (22.08. 03)
- 포인터 ( feat. 포인터 연산자 ' * ', ADDRESS 연산자 ' & ' )
- 상수 포인터
포인터
포인터
주소값을 저장하는 용도의 변수
자료형* 변수명;
[포인터 선언 방식]
int* p; // 주로 사용
int *p;
int * p;
[포인터 0 초기화] // 포인터는 0 대신 nullptr로 선언해주기로 약속
int* p = nullptr; // 주로 사용
int* p = NULL;
int* p = 0xffffffff; // 32비트 기반 프로그래밍에서 주소 값 중 최소는 0번지 최대는 0xffffffff번지이다.

■ 포인터 사용 이유 / 주의
- 포인터는 주소를 저장하는 변수기 때문에 &를 이용하여 주소값을 받아야함
- 포인터 사용 이유는 주소값을 이용해 메모리 공간에 접근해서 직접 값을 갖고오거나 변경하기 위해서
☞ 이름을 통한 접근은 매개변수가 '복사'로 이뤄지기 때문에 속도가 느려짐
☞ 이름을 통한 접근은 접근 권한이 중괄호 안에서만 있음 (벗어나면 사라짐)
☞ 이름을 통한 접근 사용 시 전역변수를 사용하면 문제가 해결되지만 추후 코드가 복잡해짐
■ 포인터는 간접 참조 ( =주소를 통한 접근 )
: 포인터라는 영역을 통해서 주소에 접근하여 값을 가져오거나 변경하므로 간접 참조이다. (직접 참조 X)
- VMS 주소는 0xn 형태, 16진수 정수와 형태가 동일하여 컴파일러가 구분을 못함
☞ 포인터라는 문법을 둘테니까 포인터 문법 뒤에 16진수 정수가 오면 그건 메모리 주소로 인식해라고 정해둠
- 컴퓨터는 1byte당 하나의 주소를 가짐
- int 는 4byte로 주소값 4개, 첫번째 주소가 대표주소로 포인터로 대표 주소 부르면 해당 메모리 공간 전체 불러옴
- 프로그래머는 VMS의 주소만 사용 가능, 포인터를 이용해 RAM 주소에 접근해서 사용
- 포인터는 변수적 속성을 가짐으로 코드 흐름에 따라 값 / 주소 변경 가능 ( 심볼릭 상수 const 가능 )
■ 포인터에 사용되는 연산자
& (ADDRESS 연산자 / 주소 추출 연산자)
: 할당된 메모리 공간의 첫 번째 주소를 추출하여 반환하는 연산자
- &변수명 형태로 사용, 해당 변수명의 할당된 메모리 공간 중 시작 주소를 추출하여 반환
- 포인터는 주소를 저장하는 함수이기 때문에 &변수명 으로 변수의 주소를 보내야 한다.
- &iTemp 는 수정 불가능 ( ++(&iTemp); , &iTemp = ptr; , &iTemp += 3; 다 불가능 )
* (포인터 연산자)
: 주소를 통해서 메모리 공간의 접근하는 연산자
- *포인터변수명 형태로 사용, 해당 포인터가 가리키고 있는 (갖고있는) 시작 주소의 메모리 공간에 접근
- int* ptr = &iTemp; 일 때, ptr은 iTemp의 주소 - *ptr은 iTemp의 값 (*ptr 값 변경 시 iTemp 값도 변경됨)
int iTemp = 0;
int* p = &iTemp;
cout << (*p) << endl; // *p는 iTemp 주소의 메모리 공간 즉, iTemp
*p = 40; // 포인터 p가 가리키는 메모리 공간에 40을 대입해라 (*가 붙었으므로)
cout << iTemp << endl; // *p가 가리키는 주소가 iTemp의 주소이므로 *p 값 변경은 iTemp 값 변경임
출력 결과:
0
40
■ 포인터의 크기
- 포인터의 크기는 x비트 기반 프로그래밍에 따라서 크기가 달라짐
- "32비트 기반 프로그래밍" 기준 포인터의 크기는 4byte
// 32비트 기반 프로그래밍 기준 포인터 주소의 크기
bool bCheck = true;
bool* ptr = &bCheck;
cout << sizeof(bool*) << "\t";
cout << sizeof(short*) << "\t";
cout << sizeof(int*) << "\t";
cout << sizeof(long long*) << endl << endl;
cout << sizeof(ptr) << "\t"; // 포인터 ptr의 주소 크기는 4byte
cout << sizeof(*ptr) << "\t"; // 포인터 ptr의 메모리 공간(=bCheck)은 1byte(자료형 bool)
cout << sizeof(&bDest) << endl; // bDest의 주소 크기는 당연히 4byte
출력결과:
4 4 4 4
4 1 4
→ 포인터( bool*, int* ,,) 자료형 자체의 크기는 4byte (주소를 저장하는 공간)
→ sizeof(ptr)은 포인터 ptr이 가리키고 있는 주소의 자료형 크기(가리키는 주소가 int형이면 4byte, bool형이면 1byte)
■ 포인터 연산
int iTemp = 10;
int* ptr = &iTemp;
cout << ptr << endl; // 포인터 ptr(=&iTemp)은 주소값 출력
*ptr = 20; // *ptr(=iTemp)이므로 iTemp = 20과 같음
++(*ptr); // ptr의 메모리 공간 안에 있는 값 20 에 ++ 즉, 21
ptr++; // ptr의 주소를 이동(int형 타입 주소니까 4byte만큼 증가 이동)
ptr += 2; // ptr + 2 는 ptr 주소 2번 이동 (int형 타입 주소니까 4 * 2 = 8byte 이동)
// *ptr = 30; //이동된 메모리 공간은 변수명이 할당되지 않은 상태, 값을 넣긴하지만 논리적 오류
cout << ptr << endl; // iTemp 주소에서 총 3번 이동된 주소 출력

■ 포인터를 '언제' , '왜' 사용하는지에 대한 예시
- Swap 함수 ; 매개변수 포인터를 이용한 예시
void pointer_Swap(int* _pA, int* _pB);
void Swap(int _iA, int _iB);
void main(void)
{
int iA = 10, iB = 20, iC = 100, iD = 200;
pointer_Swap(&iA,&iB); //매개변수가 포인터이므로 주소값으로 넣어줘야함
Swap(iC,iD);
cout << iA << "\t" << iB << endl;
cout << iC << "\t" << iD << endl;
}
void pointer_Swap(int* _pA, int* _pB) //포인터를 사용한 경우
{
int iC = 0;
iC = *_pA;
*_pA = *_pB;
*_pB = iC;
}
void Swap(int _iC, int _iD) // 포인터를 사용하지 않은 경우
{
int iTemp = 0;
iTemp = _iC;
_iC = _iD;
_iD = iTemp;
}
출력 결과 :
20 10 // swap 됨
100 200 // swap 안됨
☞ 원리
Pointer_Swap() 함수
1. main()함수 iA와 iB의 주소값을 매개변수로 받아옴.
2. main()함수 포인터(*)를 통해 iA와 iB의 메모리 공간에 접근하여 해당 주소 안의 값을 직접 교체
Swap() 함수
1. main()함수의 iC와 iD의 값을 복사하여 매개변수로 받아옴
2. 매개변수(Swap의 지역변수) iC와 iD의 값을 바꿔줌 - iC, iD는 복사본
3. Swap 함수가 종료되면서 Swap()함수의 iC와 iD는 소멸됨
→ 복사해서 받아온 매개변수를 Swap 함수 안에서 복사본으로 변수들을 서로 교체하다가 함수 끝나면서 소멸 )
→ 만약 return을 한다면 임시 저장 장치에 복사해놓고 Swap() 함수의 변수들은 소멸, main()으로 돌아가 Swap(iC, iD); 라인 부분만 반환값을 돌려주고 해당 라인을 벗어나면 반환값도 소멸 즉, main() 자체의 iC와 iD 값(원본)은 그대로
4. main()으로 돌아오면 cout << iC << iD; 는 기존의 iC, iD 값 그대로 100 200 이 출력됨
상수 포인터
[ 상수 포인터 ]
: const의 위치에 따라 값 또는 주소를 상수화 시키는 포인터
int* const ptr = &iDest; // 상수 포인터 : 오로지 하나의 주소값만 가질 수 있는 상수화가 이뤄짐
const int* ptr = &iDest; // *ptr 가리키는 값이 오로지 30으로 고정 (읽기 전용만 사용 가능)
const int* const ptr = &iDest; // 포인터 ptr의 값과 주소 둘 다 고정
■ 예시
int iDest = 30;
// 상수 포인터는 주소 변경 불가능
int* const icPtr = &iDest; // 상수 포인터 : 오로지 하나의 주소값만 가질 수 있는 상수화가 이뤄짐
const int* ciPtr = &iDest; // *ptr 가리키는 값이 오로지 30으로 고정, 주소 변경은 가능하지만 변경된 주소의 값은 30이 아닌 쓰레기값 출력됨
const int* const ccPtr = &iDest; // 주소와 값 둘 다 %iDest, 30 으로 고정
cout << icPtr << endl; // 상수 포인터 주소 출력
cout << ++(*icPtr) << endl; // 상수 포인터 값 증가 -> 30 + 1 = 31 출력
// cout << ++icPtr << endl; 상수 포인터는 주소 변경 자체가 안됨
cout << ciPtr << endl; // 값 고정 포인터 주소 출력
cout << ++ciPtr << endl; // 값 고정 포인터 주소 증가 (4byte 이동)
cout << *ciPtr << endl; // 값 고정 포인터(주소 이동됨) 값 출력 -> 고정 값 30이 아닌 쓰레기값 출력
☞ 값이 고정된 상수 포인터 ( const int* ptr )에 대한 자세한 설명
int iA = 10;
const int* ptr = &iA; // 값 고정 상수 포인터는 포인터를 통한 읽기 모드만 가능
// *ptr = 30; // 값이 고정된 상수 포인터는 포인터를 통한 값 변경 불가능
iA = 30; // 포인터를 거치지 않은 직접 값 변경은 가능
cout << *ptr << endl; // 포인터를 통하지 않고 값 변경을 했으므로 값 변경된 30으로 출력
++ptr; // 값이 고정된 포인터의 주소를 이동할 경우
cout << *ptr << endl; // iA의 다음에 들어있는 쓰레기값 출력됨
// 포인터 ptr은 값 고정 상수 포인터이기에 이동된 변수에 있는 쓰레기값을 해당 주소에 고정시킴
→ 값을 고정한 상수 포인터는 포인터로 접근한 경우, 해당 주소는 해당 값만 읽을 수 있다는 것을 의미. 포인터를 통해서 값을 변경할 수 없고 ( *ptr = 30 ; ), 다른 접근 방식으로는 값 변경 가능 ( iA = 30; )
→ 값 고정 상수 포인터 주소가 이동한 경우, 이동한 주소의 값을 읽어들이고 그 값도 포인터를 통해서는 값 고정 시켜버림. ( ex. ++ptr; 은 쓰레기값이 들어있는데 그 쓰레기값도 고정시켜서 해당 주소에서는 포인터 접근으로는 읽기밖에 안됨, 변경 불가능 )
'C++' 카테고리의 다른 글
| [TIL 10장] 2차원 배열 (0) | 2022.08.05 |
|---|---|
| [TIL 9장] 이중 포인터 개념, 배열, 디버깅 (0) | 2022.08.04 |
| [TIL 7장] 함수(return, default 매개변수), 함수 오버로딩, 재귀함수, namespace, 정적변수 (0) | 2022.08.02 |
| [TIL 6장] RAM 이론 (feat. 전역변수/지역변수) (0) | 2022.08.01 |
| [TIL 5장] 3항 연산자, for문(이중 for문) (0) | 2022.08.01 |