본문 바로가기
경제

[임베디드기사 실기 20부작 17편] C Pointer, Struct, Union, malloc/free와 Double Free — 실기에서 자주 나오는 C 언어 핵심

by 프레스러쉬 2026. 9. 18.

[임베디드기사 실기 20부작 17편] C Pointer, Struct, Union, malloc/free와 Double Free — 실기에서 자주 나오는 C 언어 핵심

임베디드기사 실기에서 C 언어 문제는 단순 문법 암기보다 메모리가 실제로 어떻게 배치되고, 포인터가 무엇을 가리키며, 구조체와 공용체가 메모리를 어떻게 사용하는지를 이해하고 있는지를 확인하는 형태로 출제되는 경우가 많습니다.

특히 Pointer, Struct, Union, malloc/free, Memory Leak, Double Free는 서로 별개의 주제가 아니라 모두 “주소와 메모리의 생명주기”라는 하나의 축으로 연결됩니다. 이번 편에서는 시험에서 손으로 답을 쓰는 상황을 기준으로, 코드를 읽고 결과를 추적하는 방법까지 함께 정리합니다.

1. Pointer란 무엇인가 — 값을 저장하는 변수가 아니라 주소를 저장하는 변수

일반 변수는 값을 저장하지만 Pointer는 다른 객체의 Memory Address를 저장합니다.

int a = 10;
int *p = &a;

여기서 a는 10이라는 값을 가지고, &a는 a가 저장된 주소입니다. p는 그 주소를 저장하며, *p는 p가 가리키는 위치의 실제 값을 의미합니다.

시험 암기: &는 Address-of, *는 Dereference.

2. 포인터 문제는 종이에 '주소 → 값'을 그리면 풀린다

다음 코드를 보겠습니다.

int a = 10;
int *p = &a;
*p = 20;
printf("%d", a);

p는 a의 주소를 가리키므로 *p = 20은 결국 a에 20을 저장하는 것과 같습니다. 따라서 출력은 20입니다.

Pointer 문제를 풀 때는 변수 이름을 따라가기보다 “현재 어느 주소를 가리키는가?”를 먼저 표시하는 것이 좋습니다.

3. Pointer와 Array의 관계

C에서 배열 이름은 많은 식에서 배열 첫 원소를 가리키는 Pointer처럼 사용됩니다.

int a[3] = {10, 20, 30};
int *p = a;

printf("%d", *(p + 1));

p+1은 단순히 주소에 1바이트를 더하는 것이 아니라 가리키는 자료형 크기만큼 이동합니다. 따라서 int가 4바이트라면 다음 int 위치로 이동하고, *(p+1)은 20이 됩니다.

4. Pointer Arithmetic의 핵심

다음 관계를 기억하면 좋습니다.

a[i]*(a + i)

즉 배열 인덱싱과 Pointer 연산은 메모리 관점에서 밀접하게 연결되어 있습니다.

실기에서는 Pointer 증가, 배열 접근, 구조체 Pointer가 섞여 나올 수 있으므로 “주소 이동 단위는 자료형 크기”라는 원칙을 반드시 기억해야 합니다.

5. Struct — 서로 다른 자료형을 하나의 객체로 묶는다

Structure는 서로 다른 자료형의 Member를 하나의 논리적 단위로 묶는 기능입니다.

struct Sensor {
    int id;
    float value;
    char status;
};

각 Member는 독립된 저장공간을 가지며, 일반 변수에서는 . 연산자로 접근합니다.

struct Sensor s;
s.id = 1;
s.value = 12.5f;
6. Struct Pointer — -> 연산자를 기억하자

구조체의 주소를 Pointer가 가리킬 때는 ->를 사용합니다.

struct Sensor s;
struct Sensor *p = &s;

p->id = 3;
p->value = 25.0f;

p->id(*p).id와 같은 의미입니다.

시험 암기: 구조체 변수는 ., 구조체 Pointer는 ->.

7. Struct의 크기는 단순 합보다 커질 수 있다 — Padding과 Alignment

구조체 크기는 Member 크기의 단순 합과 같지 않을 수 있습니다. CPU가 데이터를 효율적으로 읽도록 Alignment를 맞추면서 Padding Byte가 삽입될 수 있기 때문입니다.

struct Example {
    char a;
    int  b;
};

char 1바이트 + int 4바이트라고 해서 반드시 5바이트가 되는 것은 아닙니다. 구현과 ABI에 따라 int 정렬을 맞추기 위한 Padding이 들어갈 수 있습니다.

따라서 시험에서 sizeof(struct)를 묻는다면 Member 크기와 Alignment 조건을 함께 확인해야 합니다.

8. Union — 모든 Member가 같은 메모리를 공유한다

Union은 Struct와 비슷하게 여러 Member를 선언하지만, 가장 큰 차이는 모든 Member가 동일한 메모리 영역을 공유한다는 점입니다.

union Data {
    int i;
    float f;
    char c[4];
};

Struct는 각 Member가 각각의 공간을 가지지만 Union은 같은 시작주소를 공유합니다.

9. Struct와 Union 비교
항목 Struct Union
Member 저장공간 각각 별도 동일 공간 공유
동시 유효 값 여러 Member 가능 일반적으로 마지막 저장 Member 중심
크기 Member 합 + Padding 영향 가장 큰 Member 크기와 Alignment 영향
용도 복합 데이터 표현 메모리 절약, 동일 비트패턴 해석 등
10. malloc() — Heap에서 동적으로 메모리를 할당한다

malloc()은 실행 중 필요한 크기만큼 Heap 영역의 메모리를 동적으로 할당합니다.

int *p = malloc(sizeof(int) * 100);

성공하면 할당된 메모리의 시작 주소를 반환하고, 실패하면 NULL을 반환합니다.

따라서 실무 코드에서는 반드시 NULL 확인을 고려해야 합니다.

if (p == NULL) {
    /* allocation failure */
}
11. malloc()은 초기값을 보장하지 않는다

malloc()으로 할당한 메모리의 내용은 자동으로 0으로 초기화된다고 가정하면 안 됩니다.

0으로 초기화된 배열이 필요하다면 calloc()을 사용할 수 있습니다. 시험에서는 malloc과 calloc의 차이를 묻는 변형문제가 나올 수 있습니다.

12. free() — Heap 메모리를 반환한다

free()는 malloc/calloc/realloc 등을 통해 할당했던 동적 메모리를 Heap Allocator에 반환합니다.

free(p);

중요한 점은 free()가 Pointer 변수 자체를 없애는 것이 아니라, Pointer가 가리키던 메모리 블록을 해제하는 것이라는 점입니다.

13. Dangling Pointer — free 후 Pointer에는 주소값이 남을 수 있다

다음 코드를 보겠습니다.

int *p = malloc(sizeof(int));
free(p);

free(p) 이후 p라는 변수 자체가 자동으로 NULL이 되는 것은 아닙니다. 기존 주소값이 남아 있을 수 있지만, 그 메모리는 더 이상 유효하게 사용할 수 없습니다. 이런 Pointer를 Dangling Pointer라고 부릅니다.

그래서 다음처럼 NULL을 대입하는 패턴을 자주 사용합니다.

free(p);
p = NULL;
14. Double Free — 이미 해제한 메모리를 다시 해제하는 오류

Drive의 실기 학습자료에는 다음과 같은 대표적인 코드가 정리되어 있습니다.

char *ptr1;
char *ptr2;

ptr1 = malloc(512);
ptr2 = malloc(512);

ptr2 = ptr1;

free(ptr1);
free(ptr2);

ptr2 = ptr1 이후 ptr1과 ptr2는 같은 첫 번째 Memory Block을 가리킵니다.

따라서 free(ptr1)으로 이미 해제한 Memory를 free(ptr2)에서 다시 해제하려고 하므로 Double Free가 발생합니다.

15. 같은 코드에서 Memory Leak도 동시에 발생한다

위 코드에는 Double Free만 있는 것이 아닙니다.

ptr2 = malloc(512)로 두 번째 512바이트 Block을 할당한 뒤, ptr2 = ptr1로 ptr2의 주소값을 덮어쓰면 두 번째 Memory Block의 주소를 잃게 됩니다.

이 Memory는 더 이상 free할 방법이 없으므로 Memory Leak가 발생합니다.

즉 이 짧은 코드에는 동시에 다음 두 문제가 있습니다.

Memory Leak — 두 번째 malloc Block의 주소 소실
Double Free — 첫 번째 Block을 두 번 해제

16. Alias Pointer — 여러 Pointer가 같은 주소를 가리킬 때

두 개 이상의 Pointer가 동일한 객체를 가리키는 것을 Alias 관계라고 볼 수 있습니다.

int *p1 = malloc(sizeof(int));
int *p2 = p1;

이 자체가 오류는 아닙니다. 문제는 누가 메모리의 소유권을 가지며 누가 free할 것인가가 명확하지 않을 때 생깁니다.

동적 메모리 코드에서 “한 Block을 누가 생성했고 누가 최종 해제하는가”를 명확히 하는 습관이 중요합니다.

17. Use-After-Free — 해제한 메모리를 다시 사용하는 오류
int *p = malloc(sizeof(int));
*p = 10;

free(p);

printf("%d", *p);

free 이후 *p를 읽는 행위는 유효하지 않습니다. 이처럼 해제한 Memory를 다시 사용하는 것을 Use-After-Free라고 합니다.

실기에서는 Double Free와 Use-After-Free를 구분해서 설명할 수 있어야 합니다.

18. NULL Pointer와 Dangling Pointer는 다르다

NULL Pointer는 유효한 객체를 가리키지 않는다고 명시적으로 표시한 Pointer입니다.

Dangling Pointer는 과거에는 유효한 객체를 가리켰지만, 객체가 해제된 뒤에도 예전 주소가 남아 있는 Pointer입니다.

둘은 모두 Dereference하면 문제가 되지만 발생 원인이 다릅니다.

19. Stack과 Heap을 구분하자

동적 메모리 문제를 풀 때 Stack과 Heap의 차이를 이해하면 훨씬 쉽습니다.

항목 Stack Heap
주 사용 지역변수, 함수 호출 정보 동적 할당
수명 Scope/함수 수명과 연관 free 전까지
관리 컴파일러/런타임 프로그램이 직접 관리
주요 오류 Stack Overflow Leak, Double Free, UAF
20. Embedded System에서 동적 할당을 신중히 쓰는 이유

일반 PC보다 Memory가 제한적이고 장시간 연속 동작하는 임베디드 장비에서는 반복적인 malloc/free로 인해 Fragmentation, Memory Leak, 예측하기 어려운 Allocation Failure가 문제가 될 수 있습니다.

특히 Hard Real-Time 환경에서는 동적 Allocation의 실행시간 변동과 실패 가능성까지 고려해야 하므로 고정 Buffer, Memory Pool 등의 방식을 선택하기도 합니다.

21. volatile Pointer와 MMIO — 임베디드에서 Pointer가 중요한 이유

임베디드에서는 Pointer가 단순한 메모리 주소를 넘어 Hardware Register 접근에도 사용됩니다.

volatile unsigned int *reg =
    (volatile unsigned int *)0x40000000;

*reg = 0x01;

이 코드는 특정 Memory-Mapped Register 주소에 값을 쓰는 형태입니다.

volatile은 해당 메모리 값이 프로그램 외부 요인, 예를 들어 Hardware에 의해 바뀔 수 있으므로 Compiler가 임의로 접근을 제거하거나 캐시하지 않도록 하는 데 사용됩니다.

22. Function Pointer — 함수의 주소도 저장할 수 있다

Pointer는 Data뿐 아니라 Function의 주소도 저장할 수 있습니다.

int add(int a, int b)
{
    return a + b;
}

int (*fp)(int, int) = add;

Function Pointer는 Callback, Driver Operation Table, State Machine 등에서 많이 사용됩니다. 실기에서는 선언식 해석이나 결과 추적 형태로 변형될 수 있습니다.

23. 실기 문제를 푸는 5단계 추적법

C Pointer 문제를 받으면 다음 순서로 추적하면 좋습니다.

① 변수와 Memory Block을 구분한다.
② 각 Pointer가 현재 어떤 주소를 가리키는지 표시한다.
③ 대입문이 나오면 “값 복사”인지 “주소 복사”인지 구분한다.
④ malloc/free가 나오면 Block의 생성·해제 상태를 표시한다.
⑤ 마지막에 Dangling, Leak, Double Free 여부를 확인한다.

24. 실기 예상문제와 모범답안

문제 1. Pointer에서 &와 *의 의미를 쓰시오.
답: &는 변수의 주소를 얻는 Address-of 연산자이고, *는 Pointer가 가리키는 주소의 값을 참조하는 Dereference 연산자이다.

문제 2. 구조체 변수와 구조체 Pointer의 Member 접근 연산자를 각각 쓰시오.
답: 구조체 변수는 ., 구조체 Pointer는 ->.

문제 3. Struct와 Union의 가장 큰 차이를 설명하시오.
모범답안: Struct는 각 Member가 독립된 저장공간을 가지지만 Union은 모든 Member가 동일한 Memory 공간을 공유한다. 따라서 Union의 크기는 일반적으로 가장 큰 Member의 크기와 Alignment의 영향을 받는다.

문제 4. Double Free란 무엇인가?
모범답안: Double Free는 이미 free()로 해제한 동일한 Heap Memory Block을 다시 free()하려는 오류이다. Heap 관리 정보 손상이나 비정상 종료의 원인이 될 수 있다.

문제 5. Memory Leak이 발생하는 대표적인 상황을 설명하시오.
모범답안: 동적으로 할당한 Memory를 free하지 않은 채 해당 Memory를 가리키는 마지막 Pointer 값을 잃어버리면 더 이상 접근하거나 해제할 수 없어 Memory Leak이 발생한다.

문제 6. free() 후 Pointer에 NULL을 대입하는 이유는?
모범답안: 해제된 주소를 계속 가리키는 Dangling Pointer 사용을 줄이고, 이후 Pointer가 유효하지 않음을 명시적으로 표현하기 위해 NULL을 대입한다.

25. 시험 직전 30초 암기

& = 주소 얻기
* = 가리키는 값 참조
. = Struct Variable Member
-> = Struct Pointer Member
Struct = Member별 독립 공간
Union = Member가 같은 공간 공유
malloc = Heap 동적 할당
free = Heap 반환
Leak = 할당한 주소를 잃어 해제 불가
Double Free = 같은 Block 두 번 해제
Dangling Pointer = 이미 해제된 주소를 계속 가리킴
Use-After-Free = 해제 후 다시 접근

26. 마무리 — Pointer 문제의 본질은 '주소와 소유권'이다

Pointer 문제를 어렵게 만드는 것은 별표(*)가 많아서가 아닙니다. 실제 핵심은 “누가 어떤 주소를 가리키고 있고, 그 Memory의 소유권과 수명이 어떻게 변하는가”입니다.

특히 동적 메모리 문제에서는 다음 질문을 계속 던지면 됩니다.

누가 malloc했는가?
지금 누가 그 주소를 가리키는가?
누가 free해야 하는가?
free 이후 그 주소를 다시 쓰고 있지는 않은가?

이 네 질문을 습관화하면 Pointer, malloc/free, Double Free 문제는 훨씬 안정적으로 풀 수 있습니다.

다음 편 예고

18편 — Stack, Queue, Tree, Infix/Prefix/Postfix: 실기 자료구조 문제를 푸는 법

태그
#임베디드기사 #임베디드기사실기 #C언어 #Pointer #포인터 #Struct #구조체 #Union #공용체 #malloc #free #DoubleFree #MemoryLeak #DanglingPointer #UseAfterFree #Heap #Stack #FunctionPointer #임베디드시스템 #자격증공부