포인터(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}
};

2차원배열포인터메모리구조

이중 포인터 (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()를 통한 관리가 매우 중요하다.

태그:

카테고리:

업데이트:

댓글남기기