본문으로 건너뛰기

포인터와 주소 제어

포인터(Pointer)

포인터는 특정 타입의 메모리 주소를 저장한다.

int a = 10;
int* p = &a; // p는 int형 변수를 가리키는 포인터
  • p 자체는 메모리 주소를 저장하고,
  • *p를 통해 p가 가리키는 주소에 저장된 값을 가져올 수 있다. (역참조)
printf("%d", *p); // 10

*은 선언할 때는 "포인터 타입 선언"을 의미
사용할 때는 "역참조 연산자"로 가리키는 값을 읽는다는 의미

포인터 타입

포인터도 타입이 명확히 존재하며, 어떤 타입의 데이터를 가리키는지 지정해야 함.

포인터의 타입은 역참조 시 메모리를 해석하는 방법을 결정한다.

char c = 'A';
char* pc = &c; // char형을 가리키는 포인터

float f = 3.14f;
float* pf = &f; // float형을 가리키는 포인터

포인터 연산자

  • 주소 연산자 &

    • 변수의 메모리 주소에 접근
    int a = 5;
    int* p = &a; // a의 주소를 p에 저장
  • 역참조 연산자 *

    • 포인터 변수가 가리키는 메모리의 값에 접근
    *p = 20; // a의 값을 20으로 변경
    printf("%d", a); // 20
  • 포인터 산술 연산

    • 포인터끼리 +, - 연산 가능
    • 연산 시, 포인터 타입 크기만큼 이동
    int arr[3] = {1, 2, 3};
    int *p = arr;
    printf("%d", *(p + 1)); // 2

    p + 1int 크기(4바이트)만큼 다음 메모리 주소로 이동

    p++, p--도 가능(배열 순회 시 유용함)

동적 할당 및 메모리 해제

포인터를 활용하여 프로그램 런타임 중 메모리를 동적으로 할당하고 해제할 수 있다.

  • malloc(Memory Allocation)

    • 고정 크기의 메모리 할당
    • 할당 후 초기화 필요, 쓰래기 값이 포함됨
    int* p = (int*)malloc(sizeof(int) * 4); // int 4개 크기만큼 메모리 할당
    *p = 10; // 초기화 필요
  • calloc(Contiguous Allocation)

    • 연속된 메모리 할당
    • 모든 블록을 0으로 초기화

    동적 배열을 생성하는 경우 calloc을 사용하여 모든 요소를 0으로 초기화

    int* arr = (int*)calloc(4, sizeof(int)); // int 4개 크기만큼 메모리 할당 및 초기화
  • realloc(Reallocation)

    • 기존 할당된 메모리 크기 변경
    • 기존 데이터를 유지(크기가 줄어들어 잘린 경우는 X)
    int* p = (int*)malloc(sizeof(int) * 4); // int 4개 크기만큼 메모리 할당
    p = (int*)realloc(p, sizeof(int) * 8); // 크기 확장
  • free

    • 동적으로 할당된 메모리를 해제하여 운영체제에 반환
    • 더이상 사용하지 않는 메모리를 **해제하지 않으면 메모리 누수(Memory Leak)**가 발생
    int* p = (int*)malloc(sizeof(int) * 4); // int 4개 크기만큼 메모리 할당
    free(p); // 해제

메모리 직접 제어 시 주의사항

잘못된 타입 해석

  • 메모리 주소는 할당 받은 메모리 공간의 시작 주소이다.
  • 이 숫자를 어떻게 해석할지는 타입에 달려 있음.
int a = 65;
char* p = (char*)&a;
printf("%c\n", *p); // 'A' (ASCII 65)
  • &a는 변수 a의 시작 주소이다.
  • char로 읽으면 1바이트, int로 읽으면 4바이트로 해석

포인터 초기화

  • 포인터를 초기화하지 않고 사용하면, 쓰래기 값(garbage value) 을 가져올 수 있다.
int* p; // 초기화 안됨
*p = 10; // 어느 주소를 가리키는지 모른다.(정의되지 않은 동작, Undefined Behavior)

NULL 포인터 확인

  • 포인터가 NULL이면 아무것도 가리키지 않는 상태
  • NULL 포인터를 역참조하면 프로그램이 즉시 크래시(튕김)된다.
int* p = NULL;
*p = 10; // 잘못된 메모리 접근(Segmentation Fault)

역참조 전에 반드시 NULL 여부 체크

if (p != NULL) {
*p = 10;
}

해제된 메모리 접근(Dangling Pointer)

  • 해제된 포인터를 사용하면 정의되지 않은 동작(Undefined Behavior) 발생
int *p = (int*)malloc(sizeof(int)); // 동적 할당
*p = 5;
free(p); // 해제

*p = 10; // Undefined Behavior

메모리를 해제한 후에는 반드시 NULL로 설정

free(p);
p = NULL;

중복 해제(double free)

  • 포인터를 중복해서 해제하면 정의되지 않은 동작(Undefined Behavior) 발생
int *p = (int*)malloc(sizeof(int)); // 동적 할당
free(p); // 해제
free(p); // 중복 해제, double free detected in tcache 2

메모리 해제 후 NULL로 설정하여 이중 해제를 막는다.