학습 목표
포인터 변수의 주소, 저장한 주소, 역참조한 값을 나누어 추적한다.
본문
핵심 이론
int *p=&x에서 p는 x의 주소, *p는 x의 값이다. *p를 바꾸면 x가 바뀐다. int **pp=&p에서 *pp는 p이고 **pp는 x의 값이다.
같은 배열 안에서 p+1은 다음 원소를 가리킨다. 이동 바이트 수는 대상 자료형 크기에 비례한다. a[i]는 *(a+i)와 같은 원소 접근이다.
*p++는 *(p++)로 해석되어 포인터가 이동한다. (*p)++는 가리키는 값을 증가시킨다. ++*p는 가리키는 값을 먼저 증가시킨다.
이차원 배열에서 행 포인터의 이동 단위는 한 행이다. 배열 끝 다음 주소는 계산할 수 있지만 역참조할 수 없다. 초기화하지 않은 포인터·해제된 메모리·범위 밖 접근의 출력을 정상 숫자로 가정하지 않는다.
개념 도식
주소를 담은 변수와 그 주소의 값을 분리한다
int a[]={10,20,30}; int *p=a; int **pp=&p;에서는 a의 세 칸, p라는 주소 저장 칸, pp라는 주소 저장 칸을 각각 그린다. p는 a[0]을 가리키고 pp는 p를 가리킨다. *pp는 p에 저장된 주소이며 **pp는 그 주소에서 읽은 정수 10이다.
| 실행문 | 주소의 변화 | 배열의 변화 |
|---|---|---|
p++ | p가 a[1]을 가리킴 | 없음 |
**pp=25 | 없음 | a[1]이 25 |
(*p)++ | 없음 | a[1]이 26 |
포인터에 1을 더하면 대상 자료형 한 개만큼 이동한다. 정수형 포인터의 p+1을 무조건 1바이트 이동으로 계산하지 않는다. *p++는 *(p++)이므로 기존 대상의 값을 사용한 뒤 p가 이동하고, (*p)++는 p를 고정한 채 대상 값을 증가시킨다.
배열 포인터와 포인터 배열도 다르다. int (*r)[3]의 r+1은 정수 3개인 한 행을 건너뛰고, int *r[3]은 정수 포인터 세 개를 담는 배열이다. 선언 전체를 외우기보다 괄호와 대괄호가 무엇을 묶는지 확인한다. 실기에서는 실제 객체 범위 안의 이동과 유효한 주소만 추적한다. 해제된 주소나 지역변수 수명이 끝난 주소의 값을 임의로 정답 숫자로 추측하지 않는다.
풀이 예시
다음 C17 프로그램이 표준 출력에 기록하는 내용을 쓰시오. 마지막 줄바꿈의 유무는 답안에서 구별하지 않는다.
#include <stdio.h>
void advance(int **cursor) {
*cursor += 2;
**cursor += 4;
}
int main(void) {
int a[] = {5, 10, 15, 20};
int *p = a;
advance(&p);
printf("%d %d %d\n", (int)(p - a), *p, a[0]);
return 0;
}
정답과 풀이 보기
예시 정답
2 19 5
풀이 과정
cursor는 p의 주소이다. *cursor를 2 증가시키면 p 자체가 a[2]로 옮겨진다. 그 원소에 4를 더해 19가 되며 a[0]은 5로 유지된다.
복습 문제
답을 생각한 뒤 정답과 근거를 펼쳐 보세요.
함수 매개변수 int a[] 안에서 sizeof(a)는 전체 배열 크기인가?
정답과 근거
아니다. 매개변수에서는 포인터로 조정되므로 길이를 별도로 전달한다.
p + 1이 이동하는 바이트 수는 무엇으로 결정되는가?
정답과 근거
p가 가리키는 자료형의 크기만큼 이동한다.