포인터란?
C언어에서 포인터란 메모리의 주소값을 저장하는 변수이며, 포인터 변수라고도 부른다.
int n = 100; // 변수의 선언
int *ptr = &n; // 포인터의 선언

연산자
주소 연산자(&)
주소 연산자는 변수의 이름 앞에 사용하며 해당 변수의 주소값을 반환한다.
& 기호는 앰퍼샌드(ampersand)라고 읽으며 번지 연산자라고도 부른다.
#include <stdio.h>
int main() {
int num1 = 3;
char num2 = 'A';
double num3 = 3.1415
int *ptr1 = &num1; // 선언과 동시에 초기화
char *ptt2 = &num2; // 선언과 동시에 초기화
double *ptr3 = &num3; // 선언과 동시에 초기화
printf("num1의 저장위치 : %p\\n", ptr1); // num1의 저장위치 : 001AF97C
printf("num2의 저장위치 : %p\\n", ptr2); // num1의 저장위치 : 001AF973
printf("num3의 저장위치 : %p\\n", ptr3); // num1의 저장위치 : 001AF960
printf("포인터 변수 ptr1의 크기 : %d\\n", sizeof(ptr1)); // 포인터 변수 ptr1의 크기 : 4
printf("포인터 변수 ptr2의 크기 : %d\\n", sizeof(ptr2)); // 포인터 변수 ptr1의 크기 : 4
printf("포인터 변수 ptr3의 크기 : %d\\n", sizeof(ptr3)); // 포인터 변수 ptr1의 크기 : 4
return 0;
}
int형, char형, double형은 각각 자료형이 다르나 주소값의 크기는 무조건 4Byte인 것을 확인할 수 있다.
메모리에서의 포인터와 일반변수

포인터가 일반변수의 주소값을 저장할 수 있는 이유는 첫번째 바이트의 주소 값만을 저장하기 때문이다.
예를 들어 double형 변수인 num3의 주소값을 포인터가 저장할 때에는 8Byte의 모든 주소 값을 저장하는 것이 아닌 첫번째 Byte의 주소 값만 저장하므로 아무 문제 없이 일반변수의 주소 값을 저장할 수 있다.
참조 연산자(*)
참조 연산자는 포인터의 이름이나 주소 앞에 사용하며 포인터에 가리키는 주소에 저장된 값을 반환한다.
#include <stdio.h>
int main() {
int num;
int *ptr; // * 연산자를 통해 메모리에 접근 가능한 변수가 됨
ptr = # // 포인터가 변수 num의 주소값을 저장함
*ptr = 777; // 변수 num에다가 777을 대입
printf("포인터 ptr에 저장된 값 : %d\\n", *ptr); // 포인터 ptr에 저장된 값 : 777
// 위의 printf의 *이 참조 연산자이다.
return 0;
}
간접 참조 연산자(*)
int ptr 의 가 간접 참조 연산자이다.
다른 말로는 역 참조 연산자 또는 *연산자라고 한다.
연산자의 기능은 바로 메모리에 접근해서 주소 값을 저장 해가는 변수가 된다.
#include <stdio.h>
int main() {
int num;
int *ptr; // * 연산자를 통해 메모리에 접근 가능한 변수가 됨
ptr = # // 포인터가 변수 num의 주소값을 저장함
*ptr = 777; // 변수 num에다가 777을 대입
printf("포인터 ptr이 저장한 주소 값 : %p\\n", *ptr); // 포인터 ptr이 저장한 주소 값 : 00000309
printf("포인터 ptr에 저장된 값 : %d\\n", *ptr); // 포인터 ptr에 저장된 값 : 777
printf("변수 num에 저장된 값 : %d\\n", num); // 변수 num에 저장된 값 : 777
return 0;
}
포인터 타입
포인터로 값을 대입해줄 경우 그 때의 타입을 포인터 타입으로 결정해준다.
예를들어 아래와 같은 코드가 있다.
int val;
double *ptr = &val;
*ptr = 57
위의 코드에서 *ptr의 타입은 double이 된다.
따라서 %d 와 같은 정수형 서식문자에는 반응하지 않고 %f 같은 실수형 서식문자에만 출력된다.
단순히 주소 값만 저장하는 변수가 아닌 그 주ㅜ소 값의 타입의 정보도 같이 담고있는 변수이다.
포인터 상수 연산
포인터를 사용해서 연산도 할 수 있다.
#include <stdio.h>
int main() {
int int1 = 100;
double double1 = 100.0001;
*(&int1) += 500;
*(&double1) += .0005;
printf("%d\\n", int1); // 600
printf("%f\\n", double1); // 100.0006
return 0;
}
void형 포인터
void형 포인터를 선언하면 어떠한 타입의 대상체라도 모두 가리킬 수 있다.
int num = 40;
float num2 = 3.14;
double num3 = 999.99;
int num4;
double num5;
int *pi = &num4;
double *pd = &num5;
void *ptr;
ptr = &num1;
ptr = &num2;
ptr = &num3;
ptr = pi;
ptr = pd;
하지만 간접 참조 연산자를 쓸 수 없으나 아래와 같은 방법으로 사용할 수 있다.
int num1 = 777;
void *ptr = &num1;
printf("변수 num1의 값 : %d\\n", *(int *)ptr); // 변수 num1의 값 : 777
먼저 형 변환 연산자 (int *) 로 void형 포인터 ptr의 타입을 int로 변환시켜주어 간접 참조 연산 할 수 있게 해준다.
void형 포인터에서는 증가나 감소같은 연산이 불가능하다.
포인터 연산
포인터끼리 더할 수 없다.
#include <stdio.h>
int main() {
int a = 300;
int b = 700;
int *ptr1 = &a;
int *ptr2 = &b;
ptr1 + ptr2; // Error
return 0;
}
포인터끼리 뺄 수 있다.
#include <stdio.h>
int main() {
char ar[] = "Pointer";
char *p1, *p2;
p1 = &ar[0];
p2 = &ar[5];
printf("%c와 %c사이의 거리는 %d", *p1, *p2, p1-p2);
return 0;
}
포인터끼리 빼주어 배열의 6번째 요소의 주소 값과 배열의 1번째 요소의 주소 값의 거리를 구해 메모리상에서 얼마나 떨어져 있는지 구해낼 수 있다.
포인터에 정수를 더하거나 뺄 수 있다.
++나 —같은 연산도 가능하다.
자료형의 크기만큼 증가하고 감소한다.
포인터끼리 대입은 가능하다.
대입받을 포인터와 대입해줄 포인터의 타입이 일치해야 한다.
일치하지 않는다면 형 변환 연산자로 강제 형 변환을 해주어야 한다.
다가 정수는 대입할 수 없다.
int num1 = 50, num2 =70;
int *ptr1 = &num1, *ptr2 = &num2;
int *ptr3;
ptr3 = ptr2 - ptr1; // Error
위의 예제에서 주소 값 - 주소 값을 해주어는데 결국 남는 값은 주소 값이 아닌 정수이다.
따라서 대입할 수 없다.
포인터와 실수는 연산 할 수 없다.
포인터끼리 비교는 가능하다.
관계연산자(==, ≥, ≤, ≠, <, >)를 쓸 수 있다.
이외 연산은 할 수 없다.
곱셈, 나눗셈, 나머지, 비트연산자는 쓸 수 없다.
이중포인터
int ** ptr;로 사용한다.
#include <stdio.h>
int main() {
int a = 3;
int *ptr1 = &a;
int *ptr2 = &ptr1;
int **dptr1 = &ptr1;
printf("ptr2의 주소 : %p\\n", ptr2); // ptr2의 주소 : 0022FF40
printf("dptr1의 주소 : %p\\n", dptr1); // dptr1의 주소 : 0022FF40
printf("dptr1이 가리키는 값 : %d\\n", **dptr1); // dptr1이 가리키는 값 : 3
return 0;
}