C언어 포인터 연산은 단순히 주소값을 계산하는 산술 연산이 아니다.
메모리 공간을 가리키는 포인터가 자료형의 크기 단위로 이동하는 논리적인 동작이다.
포인터 연산의 원리를 코드로 확인하면 이 개념을 명확히 이해할 수 있다.
예를 들어 int *p;가 있을 때 p + 1은 주소값에 1을 더하는 것이 아니라, 가리키는 대상의 크기인 sizeof(int)만큼 주소값을 이동시킨다.
시스템에서 int가 4바이트라면 주소값은 실제 4만큼 증가한다.
#include <stdio.h>
int main() {
int arr[3] = {10, 20, 30};
int *p = arr;
printf("p의 주소: %p, 값: %d\n", (void*)p, *p);
printf("p + 1의 주소: %p, 값: %d\n", (void*)(p + 1), *(p + 1));
// 주소값 차이 확인 (바이트 단위)
printf("주소값 차이: %ld bytes\n", (char*)(p + 1) - (char*)p);
return 0;
}
위 코드를 실행하면 p + 1이 가리키는 곳이 arr[1]의 주소와 일치함을 확인할 수 있다.
*(p + 1)을 통해 다음 요소인 20을 가져오는 과정이 자연스럽게 연결된다.
배열 인덱스 접근 방식인 arr[i]는 내부적으로 *(arr + i)라는 포인터 연산으로 처리된다.
즉, 배열과 포인터는 메모리 접근 방식에서 밀접하게 연관되어 있다.
포인터 연산에는 몇 가지 규칙이 있다.
덧셈과 뺄셈은 가능하지만, 포인터끼리의 곱셈이나 나눗셈은 허용되지 않는다.
주소값끼리 곱하는 것은 논리적으로 성립하지 않기 때문이다.
반면 포인터끼리의 뺄셈은 두 지점 사이에 데이터가 몇 개 포함되어 있는지 계산할 때 유용하다.
또한 p++와 같은 증감 연산은 배열을 순회할 때 코드를 간결하게 만든다.
메모리 주소를 직접 계산하고 시각화해 보면 C언어가 메모리를 다루는 방식을 이해할 수 있다.
주소값이 바이트 단위가 아니라 자료형의 크기를 기준으로 움직인다는 원칙을 기억하면 메모리를 정교하게 제어할 수 있는 것 같다.