C언어 포인터와 배열, 아직도 어렵게 느껴지시나요? 많은 초보 개발자들이 이 개념 앞에서 좌절감을 맛보곤 합니다. 특히 메모리 주소와 값의 관계를 명확히 이해하지 못해 예상치 못한 버그에 시달리기도 해요.
하지만 걱정하지 마세요! 이 글을 통해 C언어 포인터와 배열의 핵심 원리를 깊이 있게 파고들고, 실제 코딩 시 맞닥뜨릴 수 있는 까다로운 문제들을 함께 해결하며 여러분의 C언어 실력을 한 단계 업그레이드할 수 있을 것입니다.

C언어에서 포인터(Pointer)와 배열(Array)은 떼려야 뗄 수 없는 관계를 가집니다. 둘 다 메모리 주소와 깊이 연관되어 있어, 이들을 제대로 이해하는 것은 C언어 마스터의 필수 관문이라고 할 수 있죠.
● **포인터: 메모리 주소를 가리키는 변수**
포인터는 말 그대로 다른 변수의 메모리 주소를 저장하는 변수입니다. `*` 연산자를 사용하여 선언하고, `&` 연산자를 사용하여 변수의 주소를 얻습니다. 포인터를 사용하면 메모리에 직접 접근하여 데이터를 조작하거나, 함수 호출 시 대량의 데이터를 효율적으로 전달할 수 있습니다.
예시:
```c
int num = 10; // 정수형 변수 선언
int *ptr = # // num의 주소를 저장하는 포인터 선언
printf("num의 값: %d\n", num); // 10
printf("ptr이 가리키는 값: %d\n", *ptr); // 10 (역참조)
printf("num의 주소: %p\n", &num);
printf("ptr의 값 (주소): %p\n", ptr);
```
● **배열: 연속된 메모리 공간에 저장된 동종 데이터 집합**
배열은 동일한 타입의 여러 데이터를 연속된 메모리 공간에 저장하는 자료 구조입니다. 배열의 이름 자체는 배열의 첫 번째 요소의 주소를 나타내는 포인터 상수처럼 동작합니다. 이 점 때문에 포인터와 배열이 자주 혼동되곤 하죠.
예시:
```c
int arr[5] = {10, 20, 30, 40, 50}; // 5개의 정수를 저장하는 배열
printf("arr[0]의 값: %d\n", arr[0]); // 10
printf("arr의 첫 번째 요소 주소: %p\n", arr);
printf("arr[0]의 주소: %p\n", &arr[0]); // arr과 동일
```
---
### 까다로운 문제 풀이: 포인터 & 배열의 심화 관계
이제 많은 분들이 헷갈려 하는 포인터와 배열의 심화 개념들을 예시와 함께 살펴보겠습니다.
1. **배열 이름은 포인터인가? (YES but NO!)**
배열 이름은 대부분의 상황에서 배열의 첫 번째 요소 주소를 가리키는 포인터처럼 작동합니다. `arr`와 `&arr[0]`이 같은 주소를 나타내는 것이 그 예시죠. 하지만 `arr`는 `arr = another_array;`와 같이 다른 주소를 할당받을 수 없는 **포인터 상수**입니다. 반면 `int *ptr;`로 선언된 포인터 변수는 언제든지 다른 주소를 가리킬 수 있습니다.
`sizeof` 연산자를 사용할 때 그 차이가 명확하게 드러납니다.
```c
int arr[5];
int *ptr = arr;
printf("sizeof(arr): %lu\n", sizeof(arr)); // 배열 전체의 크기 (ex: 20바이트, int가 4바이트일 경우)
printf("sizeof(ptr): %lu\n", sizeof(ptr)); // 포인터 변수의 크기 (ex: 8바이트, 64비트 시스템)
```
2. **2차원 배열과 포인터: 계단식 배열 vs 포인터 배열**
2차원 배열은 '배열의 배열'로 이해하는 것이 좋습니다. `int arr[2][3];`는 `int`형 3개짜리 배열 2개가 연속적으로 놓인 형태입니다.
| 개념 | 설명 및 예시 |
|---|---|
| 2차원 배열 | 연속된 메모리 공간에 모든 요소가 저장됩니다. 컴파일 시 크기가 고정됩니다. int arr[2][3] = {{1,2,3}, {4,5,6}}; int (*p)[3] = arr; // p는 'int형 3개짜리 배열을 가리키는 포인터' printf("%d\n", p[0][1]); // 2 printf("%d\n", *(*(arr + 1) + 2)); // 6 |
| 포인터 배열 | 포인터들을 저장하는 배열입니다. 각 포인터가 서로 다른 메모리 블록을 가리킬 수 있어 유연성이 높습니다. (보통 문자열 처리 시 유용) char *names[3] = {"Alice", "Bob", "Charlie"}; // char* 포인터 3개 배열 printf("%s\n", names[0]); // "Alice" printf("%c\n", *names[1]); // 'B' |
이중 포인터 (int**) |
포인터를 가리키는 포인터입니다. 동적으로 2차원 배열을 할당할 때 자주 사용됩니다. 이때는 메모리 할당을 수동으로 해주어야 합니다. int **dp = (int**)malloc(sizeof(int*) * 2); for (int i=0; i<2; i++) { dp[i] = (int*)malloc(sizeof(int) * 3); } dp[0][1] = 7; printf("%d\n", dp[0][1]); // 7 // 사용 후 반드시 free() 해줘야 합니다. |
3. **포인터 연산과 자료형 크기**
포인터에 정수를 더하거나 빼는 연산은 단순히 메모리 주소 값을 바꾸는 것이 아니라, 해당 포인터가 가리키는 **자료형의 크기만큼** 주소를 이동시킵니다.
```c
int arr[3] = {10, 20, 30};
int *ptr = arr; // arr의 첫 번째 요소 주소
printf("ptr의 주소: %p\n", ptr); // ex: 0x7ffee000
printf("ptr+1의 주소: %p\n", ptr + 1); // ex: 0x7ffee004 (int 크기 4바이트 이동)
printf("*(ptr+1)의 값: %d\n", *(ptr + 1)); // 20
```
이 원리를 이해하면 `arr[i]`가 `*(arr + i)`와 동일하게 작동하는 이유를 알 수 있습니다.

C언어의 포인터와 배열은 단순히 데이터를 저장하고 접근하는 것을 넘어, 메모리를 직접 제어하고 효율적인 데이터 구조를 설계하는 데 필수적인 강력한 도구입니다. 이 글에서 다룬 핵심 개념들을 통해 포인터와 배열이 어떻게 작동하며, 어떤 함정들이 있는지 이해하셨기를 바랍니다.
**추가 팁:** C언어는 '메모리와의 싸움'이라고 할 정도로 메모리 관리가 중요합니다. 동적 할당된 메모리는 반드시 `free()` 함수를 이용해 해제해 주어 메모리 누수를 방지하는 습관을 들이세요. 직접 다양한 예제를 코딩하고 디버깅해보면서 실력을 탄탄히 다져나가시길 응원합니다!

'정보처리기사' 카테고리의 다른 글
| 정보처리기사 SQL 조인 & 서브쿼리: 실기 합격 마스터! (0) | 2026.01.29 |
|---|---|
| 정보처리기사 합격 비법: 데이터 정규화 1~3단계 완전 정복! (0) | 2026.01.28 |
| 정보처리기사 합격률 UP! 디자인 패턴 5분 암기법 (0) | 2026.01.27 |
| 2026 정보처리기사 필기 합격, 초단기 공략법! (0) | 2026.01.15 |
| 2024년 정보처리기사 실기 3회 12번 문제 공부 및 풀이 (0) | 2025.10.21 |