Post

C언어 포인터 & malloc 완벽 가이드 — 포인터, 주소, 스레드, 메모리 관리

C언어 포인터 & malloc 완벽 가이드 — 포인터, 주소, 스레드, 메모리 관리

요약: 포인터(*), 주소(&), 이중 포인터(**), 함수 포인터, malloc/free의 핵심과 시험장에서 자주 실수하는 포인트들을 한 장으로 정리한 치트시트입니다. ✅


🏠 비유로 이해하기 — 집(House)과 주소(Address) 🔑

  • 변수 (int a = 10;): ‘a’라는 이름의 이 있고, 그 안에 10이라는 사람이 살고 있습니다.
  • & (주소 연산자): 그 집의 도로명 주소입니다. (예: 0x1234abcd)
  • * (포인터): 그 주소가 적힌 쪽지입니다. 혹은 그 주소를 보고 찾아가는 행동(역참조)입니다.

1) & (주소 연산자) : “주소가 어디야?”

변수 앞에 &를 붙이면, 그 변수가 메모리 상에서 어디에 저장되어 있는지 알려줍니다.

1
2
int number = 50;
// &number -> number가 저장된 메모리 번지수 (예: 0x1234abcd)

실전 팁: pthread_create(&threads[i], ...) 같은 곳에서 주소를 넘기는 이유는 함수가 “여기에 값을 채워주세요”라고 하기 때문입니다.


2) * (아스테리스크) : 두 얼굴

*는 선언과 사용 위치에 따라 의미가 달라집니다.

선언 시: “나는 주소를 담는 그릇이야”

1
int *ptr; // ptr은 '정수의 주소'를 저장하는 포인터 변수

사용 시(역참조): “그 주소로 찾아가!”

1
*ptr = 100; // ptr이 가리키는 주소로 찾아가서 100을 넣음

구조체 포인터 접근은 ->로 간단히 쓸 수 있습니다. 예: order->plasma_swords ((*order).plasma_swords의 축약형)


3) 함수 포인터 해석법 — “안에서 밖으로” 읽기

예: void *(*start_routine)(void *)

  1. (*start_routine) : start_routine은 포인터다.
  2. (void *) : 이 포인터는 void * 하나를 인자로 받는 함수의 주소를 담고 있다.
  3. 왼쪽의 void * : 그 함수는 void * 타입을 반환한다.

한 문장 요약: “start_routinevoid *을 받아 void *을 반환하는 함수의 주소를 담는 변수다.” (pthread에서 흔히 사용)


4) 이중 포인터(**) — 지도 안의 지도

  • 비유: 보물 상자(a) ← 지도(p) ← 금고(pp)
  • 예:
    1
    2
    3
    4
    
    int a = 10;
    int *p = &a;
    int **pp = &p;
    printf("%d", **pp); // 10
    

pthread_join처럼 함수 내부에서 호출자 쪽의 포인터 값을 바꿔야 할 때 **를 사용합니다. 예를 들어 int pthread_join(pthread_t thread, void **retval)에서 retval은 스레드가 반환한 포인터를 호출자 쪽 변수에 저장하기 위해 “포인터의 주소”를 넘기는 용도로 사용됩니다.


5) 화살표 연산자(->)

p->member(*p).member의 축약형입니다. 포인터로 구조체 멤버에 접근할 때 편리합니다.


6) 포인터 덧셈과 타입

  • 포인터 연산은 “한 걸음”이 자료형 크기만큼 이동합니다.
  • 예: int *p; p = p + 1; → 주소가 sizeof(int)만큼 증가합니다 (보통 4바이트).

7) void * (보이드 포인터)와 캐스팅

  • void *은 내용물 타입이 알려져 있지 않은 만능 상자입니다. 역참조(*)나 포인터 산술(+1)은 불가능합니다.
  • 사용 전에 반드시 캐스팅하세요:
    1
    
    struct order *my_order = (struct order *)arg;
    

8) const의 위치 읽는 법

  • const int *p : *p(내용물)이 const — 내용물 변경 금지 (*p = 3 금지; p = &b 가능)
  • int * const p : p(포인터 자체)가 const — 주소 변경 금지 (p = &b 금지; *p = 3 가능)

읽을 때는 *를 기준으로 왼쪽/오른쪽 의미를 확인하세요.


9) volatile 간단 정리

하드웨어 레지스터나 외부에서 값이 바뀔 수 있는 변수에 사용합니다. 컴파일러 최적화를 막고 매번 메모리에서 읽도록 강제합니다.

1
volatile int *status_reg = (volatile int *)0xFFFF0000;

📘 malloc 완벽 가이드 (힙 메모리 관리)

malloc이란?

  • 힙에서 메모리를 동적으로 할당합니다.
  • 반환형: void * (성공 시 주소, 실패 시 NULL)
  • 헤더: <stdlib.h>

사용 패턴

1
2
3
4
5
int *arr = malloc(sizeof(int) * 10); // C에서는 (int *) 캐스팅 불필요
if (arr == NULL) { /* 오류 처리 */ }
// 사용
free(arr);
arr = NULL; // 댕글링 포인터 방지

스레드에서의 malloc / free (실전 팁)

스레드별로 독립적인 힙 메모리를 할당해야 데이터가 덮어써지지 않습니다. 각 스레드가 할당한 메모리는 그 스레드(또는 할당자를 명확히 정한 쪽)가 책임지고 해제해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 스레드 함수 예시
void *process_order(void *arg) {
    struct order *o = arg; // 적절히 캐스팅되어 넘어온다고 가정
    // 처리
    free(o); // 처리 직후 반납
    return NULL;
}

// 스레드 생성 예시
pthread_t tid;
struct order *o = malloc(sizeof *o);
// ... 초기화 ...
pthread_create(&tid, NULL, process_order, o); // &tid: tid의 주소, o: 인자

// 스레드 결과(옵션): pthread_join와 void **retval
void *retval;
pthread_join(tid, &retval);
// 만약 스레드가 malloc한 주소를 반환했다면
struct order *res = retval; // 캐스팅하여 사용

malloc 캐스팅 팁

  • C에서는 malloc의 반환을 명시적으로 캐스팅할 필요가 없습니다. (int *)malloc(...)는 생략해도 됩니다.
  • 주의: 헤더 <stdlib.h>를 포함하지 않으면 컴파일러가 경고 또는 오류를 낼 수 있으니 항상 포함하세요.
  • C++에서는 반드시 캐스팅이 필요합니다.

권장 스타일 (Best Practices)

  • malloc(sizeof *p) 형태로 쓰면 타입 수정 시 실수를 줄일 수 있습니다. 예: int *arr = malloc(sizeof *arr * n);
  • 항상 NULL 체크를 하고, 할당 실패 시 적절히 처리하세요.
  • 할당한 메모리는 더 이상 필요해지면 즉시 free()하세요. 필요시 ptr = NULL;로 댕글링 포인터를 방지합니다.
  • 가능한 한 초기화가 필요한 경우 calloc을 고려하세요.

핵심 주의사항

  • malloc 한 번에 free 한 번 — 메모리 누수 방지
  • 항상 sizeof를 사용하세요 (malloc(sizeof(int) * n))
  • NULL 체크는 필수

calloc / realloc

  • calloc(n, size) : 0으로 초기화된 메모리
  • realloc(ptr, new_size) : 기존 메모리 크기 조절

시험장에서 자주 하는 실수 체크리스트 ✅

  1. 포인터 초기화 없이 사용했다. (int *p; *p = 10;) → 초기화 또는 NULL 확인
  2. malloc 크기 실수 (malloc(10) 대신 malloc(sizeof(int) * 10))
  3. 문자열 복사는 strcpy 또는 적절한 버퍼 사용 필요
  4. pthread_create에 주소(&)를 넘겼는지 확인
  5. pthread_joinvoid **retval 사용 맥락 이해

시험용 요약 치트시트

기호의미한마디
&주소 연산자“너 어디 살아?”
int *p포인터 선언“주소 담을 그릇”
*p역참조“그 주소로 찾아가”
p->a구조체 포인터 멤버 접근“문 열어봐”
void *만능 포인터“내용물 모름(캐스팅 필요)”
This post is licensed under CC BY 4.0 by the author.