8. 포인터
8.1 포인터
📌 포인터
변수는 할당된 메모리 공간에 붙여진 이름이다. 그리고 그 변수명을 통해 메모리 공간에 접근할 수 있다.
포인터
란 메모리의 주소를 갖고 있는 변수를 말한다.
→ 포인터 변수
라고도 부른다.
포인터를 통해 메모리의 내용에 직접적으로 접근할 수 있다.
→ 그 주소에 있는 값으로 가라!
포인터는 왜 필요한가?
포인터를 사용하면 프로그램의 속도 향상이 가능하다.
함수에도 적용 가능하다.
📌 포인터 연산자
포인터에서 사용되는 연산자는 다음과 같다.
&
&
연산자는 변수의 이름 앞에 사용하며, 해당 변수의 주소값을 가져온다.
*
*
연산자는 포인터의 이름이나 주소 앞에 사용하며, 포인터가 가리키는 값을 가져온다.
📌 선언 & 초기화
포인터의 선언은 데이터형
*
변수명
;
순으로 한다.
포인터가 선언된 후에 포인터에 접근하기 위해 변수가 반드시 있어야 한다.
포인터를 선언한 후 *
연산자를 사용하기 전에 포인터는 반드시 초기화되어야 한다.
→ 포인터의 선언과 동시에 초기화를 하는 것이 좋다.
데이터형을 반드시 일치시켜야 한다.
→ 포인터 변수는 항상 4byte이다.
포인터의 선언과 초기화를 동시에 하는 방법은 다음과 같다.
데이터형
*
포인터명
=
&
변수명
;
데이터형
*
포인터명
=
&
주소값
;
포인터를 선언할 때 *
연산자의 앞과 뒤에 존재하는 공백은 무시된다.
다음은 포인터 선언의 예시이다.
#include <iostream>
using namespace std;
int main() {
int num = 10; // 변수의 선언
int *ptr = # // 포인터의 선언, num의 메모리 주소를 포인터 변수 ptr에 저장
printf("num: %d\n", num); // num의 값
printf("&num: %#x\n", &num); // num의 주소값
printf("ptr: %#x\n", ptr); // ptr의 값
printf("&ptr: %#x\n", &ptr); // ptr의 주소값
printf("*ptr: %d\n", *ptr); // 역참조 연산자 * 사용
return 0;
}
&
연산자는 num 변수가 할당된 메모리의 시작 주소(0x6dc1b0a8)를 반환한다.
포인터 ptr
에 num
변수의 주소(0x6dc1b0a8)를 저장했기 때문에 포인터 ptr
을 출력하면 num
변수의 주소(0x6dc1b0a8)가 출력된다.
포인터 ptr
의 주소값을 출력하면 새로운 주소(0x6dc1b0a0)가 출력된다.
*
연산자를 사용하여 *ptr
을 출력하면 num
변수의 값(10)이 반환된다.
C++에서는 여러 개의 포인터를 동시에 선언할 수 있다.
int *ptr1, ptr2; // ptr1은 int형 포인터, ptr2는 그냥 int형 변수로 선언
int *ptr1, *ptr2; // ptr1, ptr2 모두 int형 포인터로 선언
📌 역참조
포인터와 역참조
포인터를 선언할 때와 역참조를 할 때 모두
*
연산자를 사용한다.
포인터를 선언할 때의*
연산자는 변수가 포인터라는 것을 알려주는 역할이다.
포인터를 역참조할 때의*
연산자는 포인터의 메모리 주소를 역참조하겠다는 것을 알려주는 역할이다.int *ptr; // 포인터 선언할때의 * 연산자 printf("%d\n", *ptr); // 포인터를 역참조할 때의 * 연산자
역참조로 주소에 접근하여 값을 저장하는 것이 가능하다.
#include <iostream>
using namespace std;
int main() {
int num = 10;
int *ptr = #
printf("num: %d\n", num);
printf("&num: %#x\n", &num);
printf("ptr: %#x\n", ptr);
printf("&ptr: %#x\n", &ptr);
printf("*ptr: %d\n", *ptr);
*ptr = 20; // 역참조로 주소에 접근하여 값을 변경
printf("*ptr: %d\n", *ptr);
return 0;
}
포인터 변수의 주소값이 가리키는 값을 변경하면 일반 변수 num의 값이 변경된다.
8.2 이중 포인터
📌 이중 포인터
#include <iostream>
using namespace std;
int main() {
int num = 10; // 변수의 선언
int *ptr = # // 포인터의 선언, num의 메모리 주소를 포인터 변수 ptr에 저장
printf("num: %d\n", num); // num의 값
printf("&num: %#x\n", &num); // num의 주소값
printf("ptr: %#x\n", ptr); // ptr의 값
printf("&ptr: %#x\n", &ptr); // ptr의 주소값
printf("*ptr: %d\n", *ptr); // 역참조 연산자 * 사용
int **ptr2 = &ptr; // 이중 포인터
printf("ptr2: %#x\n", ptr2); // 이중 포인터 ptr2의 값
printf("&ptr2: %#x\n", &ptr2); // 이중 포인터 ptr2의 주소값
printf("*ptr2: %#x\n", *ptr2); // 이중 포인터 ptr2의 실제값
printf("**ptr2: %d\n", **ptr2); // 이중 포인터 ** 사용
return 0;
}
이중 포인터 **ptr2
는 2번 참조하여 num
의 값(10)을 반환한다.
&
연산자는 포인터 ptr
이 할당된 메모리의 시작 주소(0x6dc1b0a0)를 반환한다.
포인터 ptr2
에 포인터 ptr
의 주소(0x6dc1b0a0)를 저장하였기 때문에 포인터ptr2
를 출력하면 ptr
의 주소(0x6dc1b0a0)가 출력된다.
포인터 ptr2
의 주소값을 출력하면 새로운 주소(0x6dc1b098)가 출력된다.
*
연산자를 사용하여 *ptr2
을 출력하면 포인터 ptr
의 값(0x6dc1b0a8)이 출력된다.
포인터 ptr
에는 num
변수의 주소(0x6dc1b0a8)를 저장했기 때문에 포인터 ptr
을 출력하면 num
변수의 주소(0x6dc1b0a8)가 출력된다.
*
연산자를 사용하여 출력하면 num
의 값(10)을 반환된다.
흐름: **ptr2
→ *(*ptr2)
→ *(ptr)
→ *(&num)
→ num
→ 10
8.3 포인터 연산
📌 포인터 연산
포인터는 값을 증가시키거나 감소시키는 *
, /
, +
, -
연산이 가능하다.
+
연산의 경우 증가되는 값은 포인터가 가리키는 객체의 크기이다.
하지만 포인터끼리의 *
, /
, +
은 아무런 의미가 없다.
포인터끼리의 -
연산은 두 포인터 사이의 상대적인 거리를 나타낸다.
포인터에 정수를 더하거나 뺄 수는 있지만, 실수와의 연산은 할 수 없다.
포인터끼리 대입하거나 비교할 수 있다.
포인터 연산 후 포인터가 가리키고 있는 주소는 포인터의 타입에 따라 달라진다.
다음은 포인터 증가의 예시이다.
#include <iostream>
using namespace std;
int main() {
int num = 10;
int *ptr = #
double num2 = 0.1;
double *ptr2 = &num2;
printf("num: %d\n", num);
printf("ptr: %#x\n", ptr); // 16진수
printf("ptr + 1: %#x\n", ptr + 1); // 16진수
printf("ptr: %d\n", ptr); // 10진수
printf("ptr + 1: %d\n", ptr + 1); // 10진수
printf("num2: %f\n", num2);
printf("ptr2: %#x\n", ptr2); // 16진수
printf("ptr2 + 1: %#x\n", ptr2 + 1); // 16진수
printf("ptr2: %d\n", ptr2); // 10진수
printf("ptr2 + 1: %d\n", ptr2 + 1); // 10진수
return 0;
}
int의 경우 +1을 하면 4 byte 만큼 증가하고, double의 경우 +1을 하면 8 byte만큼 증가한다.
8.4 배열과 포인터
📌 배열과 포인터