C프로그래밍 9강 - 배열과 포인터(2)
메모리 주소를 저장하는 포인터의 개념을 이해하고 포인터 변수를 선언·초기화하는 방법을 익힌다. 주소 연산자와 참조 연산자, void형 포인터, 배열과 포인터의 관계 및 포인터 산술 연산을 중심으로 안전한 메모리 참조 원리를 정리한다.
1. 포인터의 개념
포인터(pointer)는 처리할 자료가 저장된 메모리의 주소를 값으로 저장하는 변수이다. 일반 변수는 실제 자료 값을 저장하지만 포인터 변수는 자료가 있는 위치를 저장하므로, 그 주소를 거쳐 메모리의 자료에 간접적으로 접근할 수 있다.
| 구분 | 저장하는 값 | 자료 접근 방식 |
|---|---|---|
| 일반 변수 | 실제 자료의 값 | 변수명으로 저장하거나 읽음 |
| 포인터 변수 | 자료가 저장된 메모리 주소 | 저장된 주소를 통해 간접 접근 |
int n;
int *p = &n;
*p = 200;
printf("n = %d\n", n); /* n = 200 */
p에는 n의 주소가 들어 있고 *p는 그 주소에 저장된 n을 뜻한다. 따라서 *p = 200;은 n에 200을 직접 대입한 것과 같은 결과를 만든다.
포인터도 하나의 변수이지만 실제 자료가 아니라 자료의 메모리 주소를 저장한다는 점이 일반 변수와 다르다.
2. 포인터 변수의 선언과 연산자
포인터 변수는 자료형 *포인터변수명; 형식으로 선언한다. int *p는 int형 자료가 저장된 곳의 주소를 담는 포인터이고, float *fp는 float형 자료의 주소를 담는 포인터이다.
| 연산자 | 명칭 | 기능 | 예 |
|---|---|---|---|
& | 주소 연산자 | 변수의 메모리 주소를 구함 | p = &a; |
* | 참조 연산자 | 포인터가 가리키는 메모리에 접근함 | *p = 20; |
int a = 10;
int *p;
p = &a;
*p = 20;
printf("%d\n", *p); /* 20 */
&a는 a의 주소를 구하고, *p는 p에 저장된 주소를 따라가 그곳의 값을 참조한다. 같은 기호 *가 선언문에서는 포인터 변수임을 표시하고 실행문에서는 간접 참조를 뜻한다는 점을 구분해야 한다.
3. 포인터를 이용한 메모리 참조
포인터에 다른 변수의 주소를 차례로 대입하면 같은 포인터로 서로 다른 변수를 참조할 수 있다.
int a, b, c;
int *p;
a = 5000;
p = &a;
b = *p;
p = &c;
*p = 100;
p가 a를 가리킬 때 b = *p;를 실행하면 a의 값 5000이 b에 복사된다. 그 뒤 p에 c의 주소를 넣고 *p = 100;을 실행하면 c가 100이 된다. p의 값은 a의 주소에서 c의 주소로 바뀌지만 a와 b의 값은 유지된다.
int *p, i = 3, j;
p = &i;
j = *p;
j++;
j는 i의 값 3을 복사한 뒤 증가하여 4가 된다. j++는 j만 바꾸므로 *p와 i는 계속 3이다. 포인터를 통한 참조와 단순 값 복사를 구분해야 한다.
4. 안전한 포인터 사용과 자료형
정확한 대상 참조
초기화하지 않은 포인터에는 어떤 주소가 들어 있는지 알 수 없다. 이를 곧바로 참조하면 임의의 메모리를 수정하여 다른 자료를 손상시킬 수 있다.
int *p, i = 4;
/* *p = 10; 위험: 대상이 정해지지 않음 */
p = &i;
*p = 10; /* 안전: p가 i를 가리킴 */
포인터와 자료형의 일치
포인터가 가리키는 메모리의 자료형은 포인터형과 일치시키는 것이 원칙이다. float 변수의 주소는 float *에, int 변수의 주소는 int *에 저장해야 올바르게 해석할 수 있다.
int a = 10;
float b = 20.0f, c;
float *p = &c;
*p = (float)a * b; /* c = 200.0 */
int *p = &c;나 double *p = &c;처럼 형이 다른 포인터에 float 변수 c의 주소를 대입하면 경고가 발생하며, 결과가 잘못되거나 다른 메모리 영역을 침범할 수 있다.
포인터 사용 전에는 유효한 주소로 초기화했는지, 포인터형이 대상 자료형과 일치하는지 확인한다.
5. void형 포인터
void형 포인터는 가리키는 자료형이 정해지지 않은 범용 포인터로, 자료형에 관계없이 어떤 변수의 주소든 저장할 수 있다.
int x;
float y;
void *p = &x;
p = &y;
void형 포인터에는 대상의 자료형 정보가 없으므로 *p만으로 직접 참조할 수 없다. 실제 대상의 자료형에 맞게 포인터 형변환을 명시한 후 참조해야 한다.
int a = 100;
char b = 'b';
void *p = NULL;
p = &a;
printf("%d\n", *(int *)p);
p = &b;
printf("%c\n", *(char *)p);
(int *)p는 p를 int형 포인터로, (char *)p는 char형 포인터로 해석한다. 그 결과 100과 문자 b를 올바르게 읽을 수 있다.
void *는 어떤 주소든 저장할 수 있지만 대상을 참조할 때는 실제 자료형에 맞는 포인터형으로 변환해야 한다.
6. 포인터 연산의 원리
포인터의 값은 주소이므로 +, -, ++, --로 참조 위치를 바꿀 수 있다. 포인터에 1을 더하거나 빼면 주소는 1바이트가 아니라 가리키는 자료형의 크기만큼 이동한다.
| 포인터형 | p | p+1 | p+2 |
|---|---|---|---|
char * | 0x100 | 0x101 | 0x102 |
int *의 int가 4바이트인 예 | 0x100 | 0x104 | 0x108 |
p+n은 현재 위치에서 같은 자료형의 요소 n개만큼 뒤의 위치를 뜻한다. 이 규칙은 동일한 자료형의 요소가 연속된 배열을 순회할 때 활용된다.
7. 1차원 배열과 포인터
1차원 배열의 이름은 배열의 시작 주소를 나타낸다. 따라서 배열명이나 특정 요소의 주소를 같은 자료형의 포인터에 저장할 수 있다.
int a[10];
int *p = a;
int *q = &a[3];
*(p + 2) = 10;
p[2] = 10;
p는 a[0], q는 a[3]을 가리킨다. p+2는 a[2], q-2는 a[1], q+3은 a[6]의 위치이다. 또한 p[i]와 *(p+i)는 같은 요소를 참조한다.
두 포인터 사이의 감산
p = &a[6];
q = &a[2];
/* p - q는 4, q - p는 -4 */
두 포인터를 빼면 바이트 차이가 아니라 두 위치 사이의 배열 요소 간격을 구한다. a[6]과 a[2]의 첨자 차이는 4이다. 반면 두 포인터의 합산 연산은 사용할 수 없다.
8. 포인터의 증가·감소 연산
++와 --를 사용하면 포인터를 앞이나 뒤의 요소로 한 칸 이동할 수 있다. 전위와 후위 연산자의 적용 순서에 따라 참조하는 요소가 달라진다.
int a[] = {10, 20, 30, 40, 50};
int *p = &a[0];
printf("%d\n", *p); /* 10 */
printf("%d\n", *p++); /* 10, 그 뒤 p가 이동 */
printf("%d\n", *++p); /* p가 먼저 이동, 30 */
*p++는 *(p++)로 해석되어 현재 값을 먼저 참조한 뒤 p가 증가한다. 이어지는 *++p는 p를 먼저 증가시킨 뒤 그 위치의 값 30을 참조한다.
*p++는 포인터 p를 이동시키고, (*p)++는 p가 가리키는 자료 값을 증가시킨다.
핵심 개념 정리
- 포인터는 자료가 저장된 메모리 주소를 값으로 저장하는 변수이다.
&는 변수의 주소를 구하고*는 포인터가 가리키는 메모리를 참조한다.- 포인터는 유효한 주소로 먼저 초기화하고 대상 자료형과 포인터형을 일치시킨다.
- void형 포인터는 어떤 주소든 저장하지만 참조할 때는 실제 포인터형으로 변환한다.
- 포인터에 1을 더하거나 빼면 주소는 대상 자료형의 크기만큼 이동한다.
- 배열명은 시작 주소이며
p[i]와*(p+i)는 같은 요소를 가리킨다. - 두 포인터의 감산은 요소 단위 간격을 구하지만 두 포인터를 더할 수는 없다.
최종 정리: 포인터 문제는 포인터에 저장된 주소, 그 주소를 해석할 자료형, 연산 후 이동할 요소를 차례로 추적하면 해결할 수 있다. 특히 참조 전에 올바른 주소와 자료형을 설정하는 것이 안전한 포인터 사용의 출발점이다.
예상문제 20선
1. 포인터 변수에 저장되는 값은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
포인터는 실제 자료가 아니라 그 자료가 저장된 메모리 주소를 저장한다.
2. int형 포인터 p의 올바른 선언은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
포인터는 자료형 *포인터변수명; 형식으로 선언한다.
3. 변수 a의 주소를 구하는 식은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
주소 연산자 &는 변수의 메모리 주소를 구한다.
4. 포인터 p가 가리키는 값을 참조하는 식은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
참조 연산자 *는 포인터에 저장된 주소의 자료에 접근한다.
5. int a=10; int *p=&a; *p=20; 실행 후 a의 값은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
p가 a를 가리키므로 *p=20은 a에 20을 저장한다.
6. 초기화하지 않은 포인터가 위험한 이유는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
대상이 정해지지 않은 포인터는 알 수 없는 주소를 참조해 다른 메모리를 침범할 수 있다.
7. p=&c; *p=100;에서 100이 저장되는 변수는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
p가 c의 주소를 저장했으므로 *p는 c를 참조한다.
8. 포인터형과 대상 자료형의 원칙은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
자료의 크기와 표현을 올바르게 해석하려면 대상과 포인터의 자료형을 일치시킨다.
9. void형 포인터의 특징은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
void형 포인터는 가리키는 자료형이 정해지지 않은 범용 포인터이다.
10. void *p=&x;이고 x가 int형일 때 올바른 참조식은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
int형 포인터로 명시적 형변환한 뒤 참조해야 한다.
11. int가 4바이트이고 p가 0x100일 때 int형 포인터 p+2의 주소는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
int형 두 요소는 8바이트이므로 0x108로 이동한다.
12. 포인터에 1을 더할 때 주소 변화량을 결정하는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
포인터 산술은 대상 자료형의 요소 단위로 이동한다.
13. 1차원 배열 이름이 나타내는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
배열명은 첫 요소가 위치한 배열의 시작 주소를 나타낸다.
14. *(p+2)와 같은 표현은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②p[i]는 *(p+i)와 같은 요소를 참조한다.
15. p=&a[6]; q=&a[2];일 때 p-q는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
가리키는 배열 요소의 첨자 차이는 6-2=4이다.
16. 사용할 수 없는 포인터 연산은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
두 주소의 간격은 뺄 수 있지만 두 포인터를 서로 더할 수는 없다.
17. p가 값 10을 가리킬 때 *p++가 반환하는 값은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
현재 값 10을 먼저 참조한 뒤 p가 다음 요소로 이동한다.
18. p가 a[1]을 가리킬 때 *++p의 처리 순서는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
전위 증가가 먼저 적용되어 한 요소 이동한 후 그 위치를 참조한다.
19. *p++와 (*p)++의 차이는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
괄호에 따라 p 자체와 *p 중 증가 대상이 달라진다.
20. 안전한 포인터 사용 순서로 알맞은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
올바른 주소와 자료형을 설정한 뒤 참조해야 메모리 손상을 피할 수 있다.
댓글
댓글 쓰기