C언어 포인터 개념과 메모리 구조 이해
포인터(Pointer)란?
포인터는 메모리 주소(Address)를 저장하는 변수이다. 일반 변수는 값을 저장하지만, 포인터 변수는 다른 변수의 위치(주소)를 가리킨다.
예를 들어 다음 코드에서
int num = 10;
int* ptr = #
- num → 정수 값 저장
- &num → num의 메모리 주소
- ptr → num의 주소를 저장하는 포인터 변수
즉 포인터를 이용하면 특정 메모리 위치에 직접 접근할 수 있다.
포인터 변수의 메모리 크기
포인터 변수의 크기는 운영체제 아키텍처에 따라 결정된다.

포인터 연산 (Pointer Arithmetic)
포인터는 자료형 크기만큼 이동하는 특징이 있다.
int a[2] = { 0x12345678, 0x87654321 };
int* ptr_a = a;
printf("pointer a : %p\n", ptr_a);
printf("pointer a + 1 : %p\n", ptr_a + 1);
printf("*(ptr_a + 1) : %x\n", *(ptr_a + 1));
출력
pointer a : 0x1000
pointer a + 1 : 0x1004
*(ptr_a + 1) : 0x87654321
지역 변수와 메모리 배치
int a1 = 0x1234;
int a2 = 0x5678;
위 코드에서 지역 변수는 메모리에 연속적으로 배치된다는 보장이 없다.
따라서
*(ptr_a1 + 1)
이 다음 변수를 가리키는 것은 우연일 수 있으며 실행 환경에 따라 달라질 수 있다.
문자열과 포인터
char str[] = "123456789";
char* ptr = str;
printf("%c\n", *ptr);
printf("%c\n", *(ptr + 1));
printf("%c\n", *(ptr + 2));
메모리 구조
주소 값
1000 '1'
1001 '2'
1002 '3'
포인터는 한 문자씩 이동하며 접근한다.
2차원 배열과 메모리
2차원 배열도 실제 메모리에서 연속된 메모리 공간으로 저장된다.
int array[][3] = {
{3,2,1},
{6,5,4},
{9,8,7}
};

이중 포인터 (Double Pointer)
이중 포인터는 포인터의 주소를 저장하는 포인터이다.
int val = 0x12345678;
int* ptr = &val;
int** pptr = &ptr;
구조
- val → 값 저장
- ptr → val 주소 저장
- pptr → ptr 주소 저장
접근 방식
- *ptr → val 값
- **pptr → val 값
동적 메모리 할당 (Dynamic Memory Allocation)
malloc() 함수를 이용하여 정수형 배열 공간을 동적으로 할당하고, 포인터 연산을 통해 값을 저장하고 출력한 뒤 free()로 메모리를 해제하는 과정을 확인하였다.
1. 포인터 변수 선언
int* nums;
먼저 int형 포인터 변수 nums를 선언한다. 여기서 nums는 정수형 데이터를 저장할 메모리 공간의 시작 주소를 저장하는 변수이다.
아직은 메모리를 할당하지 않았기 때문에, 이 시점의 nums는 유효한 배열 공간을 가리키고 있지 않다.
즉, 현재 단계에서는 단순히 정수형 주소를 저장할 준비만 한 상태이다.
2. 동적 메모리 할당
nums = (int*)malloc(sizeof(int) * 20);
이 코드는 힙(Heap) 영역에 정수 20개를 저장할 수 있는 메모리 공간을 동적으로 할당한다.
코드 해석
- malloc() : 필요한 크기만큼 메모리를 할당하는 함수
- sizeof(int) : int형 하나의 크기
- sizeof(int) * 20 : int 20개에 필요한 전체 바이트 수
- (int) : 반환된 주소를 int 타입으로 형변환
예를 들어 int가 4바이트라면 - 4 byte × 20 = 80 byte
즉, 총 80바이트의 연속된 메모리 공간이 힙 영역에 생성되고, 그 시작 주소가 nums에 저장된다.
3. 메모리에 값 저장
for (int i = 0; i < 20; i++) {
*(nums + i) = i + 1;
}
이 반복문은 동적으로 할당된 메모리 공간에 값을 저장하는 부분이다.
동작 과정
- i = 0일 때 → *(nums + 0) = 1
- i = 1일 때 → *(nums + 1) = 2
- i = 2일 때 → *(nums + 2) = 3
- …
- i = 19일 때 → *(nums + 19) = 20
즉, 힙에 할당된 20칸의 정수 공간에 1부터 20까지 순서대로 저장된다.
4. 저장된 값 출력
for(int i = 0; i < 20; i++) {
printf("nums[%d] : %d\n", i, *(nums + i));
}
이 반복문은 힙 메모리에 저장된 값을 순서대로 출력하는 부분이다.
5. 동적 메모리 해제
free((int*)nums);
malloc()으로 할당한 메모리는 사용이 끝난 뒤 반드시 free()를 통해 해제해야 한다.
이 코드의 의미는 다음과 같다.
- 힙 영역에 동적으로 확보했던 메모리 공간을 반환
- 더 이상 사용하지 않는 메모리를 운영체제에 돌려줌
- 메모리 누수(Memory Leak) 방지
전체코드
int* nums;
nums = (int*)malloc(sizeof(int) * 20);
for (int i = 0; i < 20; i++) {
*(nums + i) = i + 1;
}
for(int i = 0; i < 20; i++) {
printf("nums[%d] : %d\n", i, *(nums + i));
}
free((int*)nums);
return 0;
출력결과

정리
이번 실습을 통해 malloc()을 사용한 동적 메모리 할당, 포인터 연산을 이용한 값 저장과 출력, 그리고 free()를 통한 메모리 해제 과정을 확인할 수 있었다.
동적 메모리 할당은 프로그램 실행 중 필요한 크기만큼 메모리를 사용할 수 있다는 장점이 있으며, 배열의 크기를 유연하게 다룰 수 있게 해준다.
다만 사용이 끝난 메모리를 반드시 해제해야 하므로 free()를 통한 관리가 매우 중요하다.
댓글남기기