포인터가 무엇인가를 검색하면 어떤 특정한 값을 가리키는 사람들이 나온다.
포인터는 무엇인지 정의부터 알아보자.
그렇다. 포인터는 특정한 변수, 메모리 공간주소를 가리키는 변수를 말하는 것이다.
그래서 포인터는 다른 변수의 포인터를 가리킬 수 있는 것이다.
다른 짤로는 이런게 있다.
이 짤을 해석하자면
int를 가리키는 int*
int* 를 가리키는 int**
int**를 가리키는 int***
이렇게 쭉 가는 것이다.
즉 주소값의 포인터도 있는 것이다.
포인터는 C 언어에서 정말 중요한 개념 중 하나이다.
왜냐하면 포인터를 사용해서 특정 데이터를 직접 조작하거나 다른 데이터로 전환할 수 있기 때문이다.
C언어는 다른 언어와는 달리 저수준의 언어이기 때문에 메모리에 직접 접근하고 제어할 수 잇다.
다른말로 다시 설명하게 되면
파이썬을 비롯한 고급언어들은 포인터의 개념을 전혀 알지 못해도 언어에서 자동으로 해 주지만
포인터를 재대로 사용한다는 뜻은
C언어를 사용할때는 내 코드와 컴퓨터의 메모리가 의 상호작용을 이해한다는 것이기 때문이다.
그래서 포인터를 잘못 사용하게 되면 메모리의 누수도 심각하고, 문법도 어렵고 디버깅도 힘들고 메모리에 직접 접근하는 것이라 위험성도 커지게 된다.
포인터의 연산을 찾아보기 전에 2개를 확실히 알고 넘어가야 한다.
바로 *참조연산자, &주소연산자 이다.
정의를 살펴보자
둘이서 쌍으로 날 괴롭히고 있다.
그래도 뭔 소리인지는 알아봐야되니까 집중해서 글을 보자.
주소연산자는 해당 변수의 주소값을 반환한다고 한다.
그러니까 &a 라고 쓰면 a의 주소값을 반환한다는 것이다.
참조연산자는 주소에 저장된 값을 반환한다고 한다.
그러니까 *ptr = &a 라고 하면 a의 주소에 저장된 값을 반환한다는 소리다.
#include<stdio.h>
int main() {
int a = 123; //값을 저장할 변수
int *ap; //a의 값을 저장할 포인터변수
ap = &a; //ap에 a의 포인터를 저장
printf("a의 값 : %d\n", a);
printf("a의 주소값 : %u\n", &a);
printf("ap의 값 : %u\n", ap);
printf("ap를 통해 구한 a의 값 : %d\n", *ap);
printf("ap의 포인터 : %u\n", &ap);
}
출처: https://angangmoddi.tistory.com/14 [Language Note:티스토리]
다음은 내가 본 정말 좋은 설명이다.
과정을 살펴보자.
즉 다시 말해서
이걸로 포인터 변수와 주소연산자, 참조연산자에 대한 기본적인 이해가 되었다.
#include <stdio.h>
int main()
{
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = numArr; // 배열 첫 번째 요소의 메모리 주소를 포인터에 저장
numPtrB = numPtrA + 1; // 포인터 연산
numPtrC = numPtrA + 2; // 포인터 연산
printf("%p\n", numPtrA); // 00A3FC00: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB); // 00A3FC04: sizeof(int) * 1이므로 numPtrA에서 4가 증가함
printf("%p\n", numPtrC); // 00A3FC08: sizeof(int) * 2이므로 numPtrB에서 8이 증가함
return 0;
}
코딩도장의 예시이다.
이렇게 했을때 출력값도 주석으로 적혀있는데 살펴보게 되면
포인터의 값에 상수를 더하게 되면 int의 배열 크기가 4bite이기 때문에
포인터가 가르키는 주소는 4씩 증가하는걸 볼 수 있다.
반대로 빼기에도 똑같다.
#include <stdio.h>
int main()
{
int numArr[5] = { 11, 22, 33, 44, 55 };
int *numPtrA;
int *numPtrB;
int *numPtrC;
numPtrA = &numArr[2]; // 배열 세 번째 요소의 메모리 주소를 포인터에 저장
numPtrB = numPtrA - 1; // 포인터 연산
numPtrC = numPtrA - 2; // 포인터 연산
printf("%p\n", numPtrA); // 00A3FC08: 메모리 주소. 컴퓨터마다, 실행할 때마다 달라짐
printf("%p\n", numPtrB); // 00A3FC04: sizeof(int) * -1이므로 numPtrA에서 4가 감소함
printf("%p\n", numPtrC); // 00A3FC00: sizeof(int) * -2이므로 numPtrB에서 8이 감소함
return 0;
}
이번에는 빼기의 결과이다.
마찬가지로 포인터의 주소가 int자료형의 크기의 배수만큼 줄어드는 것을 볼 수 있다.
#include <stdio.h>
int main()
{
char *cPtr1 = NULL;
short *numPtr1 = NULL;
long long *numPtr2 = NULL;
printf("%p\n", cPtr1 + 1); // 00000001: 0x000000에서 1바이트만큼 순방향으로 이동
printf("%p\n", numPtr1 + 1); // 00000002: 0x000000에서 2바이트만큼 순방향으로 이동
printf("%p\n", numPtr2 + 1); // 00000008: 0x000000에서 8바이트만큼 순방향으로 이동
return 0;
}
다른 예시를 보자.
처음 cPtr1, numPtr1, numPtr2는 모두 같은 주소인 0x000000을 가르키고 있었을 것이다.
하지만 각각의 자료형은 char, short, longlong로 다르기 때문에
상수를 더해준다면 각각의 자료형 크기만큼 더해질 것이다.
이때, c의 증감연산자 ++와 --에도 같은 방식으로 적용이 된다.
너무나 간단한 예시이기 때문에 생략을 하도록 하자.
추가로 하나 더 적으면
arr[i] = *(arr + i) 가 성립된다는 소리다!