Pointer
포인터
- 포인터는 값 자체가 아닌, 값이 저장된 메모리 공간의 주소를 저장하는 변수이다.
Address Operator "&" (주소연산자 "&")
- 변수가 저장된 메모리 공간의 주솟값을 반환하는 연산자이다.
Ex. 주소 연산자 사용 예시
int donuts = 7;
cout << "변수 donuts의 값 : " << donuts << endl;
cout << "변수 donuts의 주솟값 : " << &donuts << endl;
// 실행 예시
변수 donuts의 값 : 7
변수 donuts의 주솟값 : 0012FF7C
- 보편적으로, 메모리 주솟값을 표현하는 기본 세팅값은 16진수이다.
Indirect Value Operator "*" (간접값 연산자 "*")
- Dereferencing Oprator (간접 참조 연산자)라고도 부른다.
- 포인터 변수 앞에 * 연산자를 붙이면 해당 주소에 저장되어 있는 값을 반환한다.
- 포인터 변수 선언 시에도, * 연산자를 변수이름 앞에 붙인다.
- * 연산자를 통해 해당 포인터가 가리키는 값 자체를 변경하는 것 또한 가능하다.
- 또한, * 연산자는 곱셈을 의미하기도 하는데, 컴파일러는 문맥을 파악하여 이 연산자를 통해 곱셈을 수행할 지, 간접 참조를 수행할 지를 정한다.
Ex. 간접값 연산자 사용 예시
int val = 135;
int* ptr = &val;
cout << "val의 주솟값 : " << &val << endl;
cout << "ptr의 값 : " << ptr; << endl;
cout << "ptr이 가리키고 있는 값 : " << *ptr << endl;
// 실행 예시
val의 주솟값 : 0015FF4A
ptr의 값 : 0015FF4A
ptr이 가리키고 있는 값 : 135
메모리내에서 연속적으로 저장된 데이터 간의 주솟값 차이
변수 A와 B가 선언되었다고 가장하자.
변수 A는 double형 변수로 8byte의 메모리 공간을 차지하고,
변수 B는 int형 변수로 4byte의 메모리 공간을 차지하고 있으며
두 변수가 메모리내에서 연속적으로 저장되었을 경우, 두 변수 간 주소값 차이와 그 이유는 아래와 같다.
※ 두 변수가 연속적으로 선언되었다고 해서 컴파일러가 두 변수를 꼭 연속적으로 저장하는 것은 아니다!
포인터 선언 방식
- 데이터형에따라 차지하는 메모리 크기도 다르고, 저장 형식도 상이하므로 포인터를 선언할 때에는 그 포인터가 지시하는 데이터형이 무엇인지 까지 서술해야 한다.
Ex. 포인터 변수 선언 예시 (C vs C++)
int *ptr;
// C Style 포인터 변수 "ptr"선언
int* ptr;
// C++ Style 포인터 변수 "ptr"선언
- 두 코드의 차이는 간접값 연산자(Indirect Value Operator, *)가 데이터형에 붙었느냐 변수명에 붙었느냐 인데, 실행하는 데 차이는 없다.
- 또한, * 연산자 앞 뒤로는 빈칸이 있어도 되고 없어도 된다.
- 위 코드에서 처럼 "int*" 이 자체를 데이터형으로 보는것이 바람직하다.
- 포인터 변수 선언 시에 특정 주솟값 혹은 NULL값으로 초기화 하지 않으면 해당 포인터 변수는 알 수 없는 곳을 가리키게 될 확률이 있으며, 이 때문에 시스템에 에러를 야기할 수 있다.
그러므로, 포인터 변수 선언 후에는 NULL값으로라도 꼭 초기화 해야한다.
포인터 변수의 메모리 크기
- 일반적으로, 포인터 변수 자체의 메모리 크기는 2 Byte 혹은 4 Byte이며, 지시하는 데이터형과는 크게 관련이 없다.
ex) 일반적인 컴퓨터 시스템에서, double형 포인터 변수와 int형 포인터 변수의 크기는 같다.
포인터와 수(Numbers)
- 포인터가 특정 주솟값 (예를 들어, 0x00013CA9와 같은 16진수 수치)을 저장한다고 해서 포인터 변수가 정수 데이터를 저장하고 있는 것은 아니다.
- 따라서, 포인터는 정수형 변수와는 다른 개념이며 포인터에 정수를 직접 대입할 수 없으며, 주솟값을 직접적으로 입력해서 대입시키고 싶은 경우 적절한 데이터형 변환자를 이용해야 한다.
Ex. 포인터 변수 사용의 잘못된 예시와 해결 방안
int* pt;
pt = 0xB8000000;
// 컴파일러는 0xB8000000 를 주소라 인식하지 않고, 정수 데이터로 인식한다.
pt = (int *) 0xB8000000;
// 적절한 데이터형 변환 (정수형 -> 정수형 데이터의 포인터(주솟값))
포인터와 문자열
- std내에 기술된 cout객체는 문자열이 저장된 배열을 출력시키는 경우, 그 객체의 주소가 문자열의 주소임을 자체적으로 판단하여 NULL을 만날 때 까지 계속해서 문자들을 출력해서 프로그래머에게 편의를 제공한다.
- 즉, cout에 문자의 주소를 넘겨 주면, 그 문자부터 시작해서 NULL을 만날 때까지 계속 출력한다.
char str[10] = "abcd";
cout << str << endl;
cout << (int*)str << endl; // 주소를 출력하고 싶은 경우
- 문자열 "abcd"가 출력된다.
- 위 예시와 우리가 기존에 알고 있는 것들을 통해 알 수 있는 점은 cout의 매개변수로 포인터(배열) 또한 사용가능하다는 점이며, 문자열 상수(double quote로 둘러싸인 문자열)또한 해당 문자열의 첫 번쨰 문자의 주소가 매개변수로 사용된다.
- 정리하면, cout을 비롯한 대부분의 C++ 표현에서, char형의 배열 이름, char형을 지시하는 포인터, 문자열 상수는 모두 문자열의 첫 번째 문자의 주소로 해석되어 매개변수로써 옮겨진다.
- C++은 문자열 상수를 상수로 취급할것을 권고했고, 특정 컴파일러는 문자열 상수를 읽기 전용 상수로 취급해서 새로운 데이터를 기록하려고 하면 실행 시간 에러를 발생시키기도 하고, 그렇게 하지 않는 컴파일러도 있는 상태이다.
Pointer (포인터) vs Reference (참조)
Pointer | Reference |
NULL값을 할당할 수 있다. | NULL값을 할당할 수 없다. |
참조대상의 주소를 할당받는 방식이다. | 참조하는 대상을 직접 할당받는 방식이다. |
포인터를 위한 연산자를 이용해야 한다. (-> 연산자) | 지역변수와 같은 연산자를 이용한다. (. 연산자) |
참조대상을 변경할 수 있다. | 참조대상을 변경하지 못한다. |
- 참조형이 포인터형보다 보수적인 것을 볼 수 있는데, 본래 참조형은 포인터를 잘못 사용해서 생기는 에러를 예방하기 위해 고안된 개념이기 때문이다.