문서 읽는 데 46분 · 40강 · 4과목 · 프로그래밍 언어 활용

C언어 심화: 포인터와 함수

목차 24
전체 59강 중 40강 · 4과목 · 프로그래밍 언어 활용

C언어 심화는 메모리를 직접 다루는 영역이다. 포인터(& 주소·* 역참조)·배열-포인터 동치·함수(Call by Value vs Reference)·변수 범위·동적 메모리를 코드로 따라가며 출력값을 도출한다. 매 회차 1~2문항 이상 나오는 4과목 최빈출 영역으로, 역참조 vs 곱셈 · sizeof(arr) vs sizeof(ptr) · Value vs Reference · free 누락 · NULL 미체크가 단골 함정이다. 39강의 arr ≡ &arr[0](배열 이름 = 첫 원소 주소)와 static 저장 영역(Data)이 여기서 포인터로 본격적으로 다뤄진다.

핵심 암기: 포인터 2대 연산자 ★&주*역★(&=주소 · *=역참조) · 배열-포인터 동치 arr ≡ &arr[0] ≡ ptr · 변수 범위 ★지전스★(지=Stack · 전=Data · 스=Data) · 동적 메모리 4단계 ★할쓰해놀★(malloc → 사용 → free → NULL) · 함정(역참조 vs 곱셈 · sizeof 8B · Value/Reference swap · 누수/dangling/이중 해제)


PART A — 포인터 기초

포인터와 ★&주*역★ ·포인터·시험 1순위·

[정의] 포인터(Pointer) = 변수의 주소를 담는 변수. 다른 변수가 사는 메모리 번지수를 적어둔 한 칸이다. 함수에서 원본을 수정하거나 동적 메모리를 다루려면 주소가 필요하다.

[표] ★&주*역★ — 2대 연산자

두음 연산자 의미 시험 함정
& &x 소(address) 연산자 — 변수의 메모리 번지 scanf("%d", &age)
* *p 참조(dereference) 연산자 — 주소가 가리키는 값 *p++ vs (*p)++

[코드]

c
int x = 10;
int *p = &x;          // p는 x의 주소를 담은 포인터
printf("%d\n", *p);   // 10 — *p = p가 가리키는 값 = x
*p = 20;              // p가 가리키는 곳의 값을 20으로
printf("%d\n", x);    // 20 — 역참조로 원본 수정

💡 포인터 = 변수의 우편함 주소를 적어둔 메모지. &는 주소 가져오기, *는 그 주소의 값 꺼내기.

🔑 암기 "★&주*역★ — &=주소 / *=역참조. 사용 전 NULL 체크 필수"

⚠️ 함정 선언만 한 포인터(int *p;)는 쓰레기 주소(wild pointer) — 그대로 *p = 10 하면 세그먼테이션 폴트(segfault). 사용 전 if (p != NULL). / 선언의 *(포인터 표시)와 사용의 *(역참조), 변수 둘 사이 *(곱셈)는 위치로 구분.

🎯 빈출 & vs * 분별이 매 회차 1문제 이상.

포인터 크기와 연산 ·포인터·

[정의] 포인터 크기는 자료형과 무관하게 64비트 = 8바이트 고정. 포인터에 1을 더하면 자료형 크기만큼 이동한다(int *는 +4B).

[표] 자료형 크기 vs 포인터 크기

자료형 자료형 크기 포인터 크기
char 1B char * = 8B
int 4B int * = 8B
double 8B double * = 8B
구조체 100B 100B struct * = 8B

[코드]

c
int arr[3] = {10, 20, 30};
int *p = arr;
int a = *p++;     // *(p++) — 10 가져오고 p는 arr[1]로 이동
p = arr;          // 다시 초기화
int b = (*p)++;   // *p의 값 10을 b에, 그 값을 11로 증가. p는 이동 X

💡 64비트 CPU는 주소를 8바이트로 표현 — 포인터는 주소를 담는 그릇이라 무조건 8B. 자료형은 '역참조 때 몇 바이트 읽을지'만 결정.

🔑 암기 "포인터 크기 8B 고정 · +1 = 자료형 크기만큼 이동 · *p++(값 쓰고 이동) vs (*p)++(값 자체 증가)"

⚠️ 함정 *p++(*p)++는 괄호 위치가 우선순위를 뒤집는다.

🎯 빈출 포인터 크기 8B · int * +1 = 4B 이동이 단골.


PART B — 배열과 포인터

배열-포인터 동치 — arr ≡ &arr[0] ≡ ptr ·배열-포인터·시험 1순위·

[정의] 배열 이름은 곧 첫 원소의 주소다. arr ≡ &arr[0] ≡ ptr, 그리고 arr[i] ≡ *(arr+i).

[흐름] 동치 등식

텍스트
arr  ≡  &arr[0]  ≡  ptr            (배열 이름 = 첫 원소 주소)
arr[i]  ≡  *(arr + i)  ≡  ptr[i]  ≡  *(ptr + i)

[코드]

c
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;                       // ptr = &arr[0]
printf("%d %d\n", arr[2], *(arr+2));  // 30 30 — 동치
printf("%d %d\n", ptr[2], *(ptr+2));  // 30 30 — ptr도 인덱스 접근 가능

💡 함수에 배열을 넘기면 자동으로 포인터가 전달되고, 포인터로 배열을 순회할 수 있다. 39강의 arr ≡ &arr[0] 성질이 여기서 본격화된다.

🔑 암기 *"arr ≡ &arr[0] ≡ ptr · arr[i] ≡ (arr+i) — 두 줄이면 배열-포인터 80%"

🎯 빈출 동치 셋 비교가 매 회차. 단 sizeof만은 예외(다음 카드).

sizeof(arr) vs sizeof(ptr) ·배열-포인터·매년 단골·

[정의] arr ≡ ptr 동치지만 sizeof만은 다르다. 배열은 전체 크기, 포인터는 8B.

[코드]

c
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;
printf("%zu %zu\n", sizeof(arr), sizeof(ptr));  // 20  8

[표] sizeof 3케이스

케이스 sizeof 이유
sizeof(arr) 선언 함수 내 20 (4B × 5) 배열 전체 크기
sizeof(ptr) 8 포인터 크기(64비트)
sizeof(arr) 함수 매개변수 8 배열이 포인터로 자동 변환

💡 배열은 함수 매개변수로 들어가는 순간 포인터로 변신 — 함수 안에선 sizeof로 길이를 못 구해 길이 매개변수를 따로 전달하는 게 관행.

🔑 암기 "sizeof(arr)=전체 / sizeof(ptr)=8B · 함수 매개변수로 가면 배열도 8B"

⚠️ 함정 "arr ≡ ptr이니 sizeof도 같다" ❌(→ 컴파일러가 배열인지 포인터인지 선언 형태로 판단해 다름).

🎯 빈출 sizeof(arr) vs sizeof(ptr)가 시험 1순위 함정.


PART C — 함수

함수 — 선언·정의·호출 ·함수·

[정의] 함수 = 입력을 받아 처리하고 결과를 돌려주는 추상화 단위. 선언(프로토타입)·정의(본문)·호출 3단계. 매개변수(Parameter) = 정의 쪽 '받는 변수', 인자(Argument) = 호출 쪽 '보내는 값'.

[코드]

c
int add(int a, int b);     // ① 선언 — 프로토타입
int add(int a, int b) {    // ② 정의
    return a + b;
}
int result = add(3, 5);    // ③ 호출 — result = 8

[표] 입출력 4패턴

패턴 예시 의미
입력 O · 출력 O int add(int a, int b) 받아서 반환
입력 O · 출력 X void print(int x) 출력만
입력 X · 출력 O int getTime() 인자 없이 반환
입력 X · 출력 X void hello() 둘 다 없음

💡 void=반환 없음, return=즉시 종료 + 값 전달. void가 아닌데 return이 빠지면 쓰레기 값 반환 위험.

🔑 암기 "선언·정의·호출 3단계 · 매개변수=받는 쪽(정의) / 인자=보내는 쪽(호출)"

🎯 빈출 매개변수 vs 인자 용어 분별.

Call by Value vs Reference ·함수·시험 1순위·

[정의] Call by Value = 값을 복사해 전달(원본 안 바뀜). Call by Reference = 주소(포인터)를 전달해 함수가 원본을 직접 수정. C는 기본이 Call by Value.

[코드]

c
void swap1(int a, int b)   { int t = a; a = b; b = t; }     // 값 복사 — swap 실패
void swap2(int *a, int *b) { int t = *a; *a = *b; *b = t; } // 주소 — swap 성공

int x = 10, y = 20;
swap1(x, y);     // x y 그대로 10 20
swap2(&x, &y);   // x y 교환 20 10

[표] Value vs Reference

항목 Call by Value Call by Reference
전달 값 복사 주소 전달(포인터)
원본 변경 불가 가능
비유 피자 사진 피자 가게 주소
swap 안 됨

💡 값 = 사진(찢어도 원본 안전) / 주소 = 가게 주소(가서 진짜 먹어버림). 배열을 함수에 넘기면 첫 원소 주소만 가서 자동으로 Call by Reference& 없이도 원본 수정(void f(int *arr)).

🔑 암기 "Value=사진(원본 안전) / Reference=가게 주소(원본 변경) · swap은 Reference(*,&)만 됨 · 배열은 자동 Reference"

⚠️ 함정 swap을 Call by Value(*,& 없이)로 쓰면 절대 안 바뀜.

🎯 빈출 swap 결과 · Value/Reference 분별이 매 회차 1문제 이상.

재귀 함수 ·함수·

[정의] 재귀(Recursion) = 함수가 자기 자신을 호출. 2대 필수 요소 = 종료 조건 + 자기 호출. 종료 조건 없으면 무한 재귀 → 스택 오버플로우 → 크래시.

[코드]

c
int factorial(int n) {
    if (n <= 1) return 1;        // 종료 조건
    return n * factorial(n - 1); // 자기 호출
}
// factorial(5) = 5 × 4 × 3 × 2 × 1 = 120

[표] 재귀 vs 반복문

항목 재귀 반복문
가독성 수학 정의에 가까움 단계별 명시
메모리 Stack에 호출 정보 쌓임 변수 몇 개
어울림 분할정복·트리 순회 횟수 정해진 반복

💡 마트료시카 — 큰 인형 안에 작은 인형, 가장 작은 인형(종료 조건)에 닿을 때까지.

🔑 암기 "재귀 2요소 = 종료 조건 + 자기 호출 · 종료 조건 없으면 스택 오버플로우"

⚠️ 함정 종료 조건 누락 = 무한 재귀.

🎯 빈출 factorial · 피보나치 추적.


PART D — 변수 범위와 메모리

메모리 4영역 — Stack · Heap · Data · Text ·메모리·

[정의] 프로그램 메모리는 4영역으로 나뉜다. Stack(지역 변수·함수 호출 정보) · Heap(동적 메모리) · Data(전역·static) · Text(실행 코드).

[흐름] 메모리 구조

텍스트
높은 주소 
┌─────────────┐
│   Stack     │  지역 변수 · 함수 호출 정보 ( 자동 반납)
├─────────────┤
│   Heap      │  동적 메모리 malloc ( 직접 임차/반납 free)
├─────────────┤
│   Data      │  전역 변수 · static 변수
├─────────────┤
│   Text      │  실행 코드 (읽기 전용)
└─────────────┘
낮은 주소 

[표] Stack vs Heap

항목 Stack Heap
할당 컴파일 시(자동) 런타임(malloc)
반납 자동(함수 종료 시) 수동(free)
크기 작음(~MB) 큼(~GB 가능)
비유 도서관 사물함 자가 창고

💡 Stack은 들어갈 때 자동으로 받고 나올 때 자동 반납, Heap은 직접 임차(malloc)·직접 반납(free). 안 정리하면 임차료 누수.

🔑 암기 "Stack(지역·자동) / Heap(동적·수동) / Data(전역·static) / Text(코드)"

🎯 빈출 영역별 저장 대상 매칭.

★지전스★ — 변수 범위 3유형 ·범위·시험 1순위·

[정의] 변수가 어디 살고 언제 사라지는지 3유형. 역(Stack) · 역(Data) · static(Data). 두음 ★지전스★.

[표] 변수 범위 3유형

두음 종류 영역 수명 초기화 비유
지역 변수 Stack 함수 끝나면 소멸 자동 X(쓰레기) 내 방
전역 변수 Data 프로그램 끝까지 0 자동 거실
static 변수 Data 프로그램 끝까지 첫 호출만 금고

[코드]

c
int global = 100;            // 전 — Data·항상
void counter() {
    static int s = 0;        // 스 — Data·첫 호출만 초기화
    int local = 0;           // 지 — Stack·매번 새로
    s++; local++;
    printf("s=%d local=%d\n", s, local);
}
// counter() 3번 호출: s=1,2,3 / local=항상 1

💡 39강의 거실(전역)·내 방(지역)·금고(static) 비유 그대로. static의 저장 영역이 Data다.

🔑 암기 "★지전스★ — 지=Stack / 전=Data / 스=Data(첫 호출만 초기화)"

⚠️ 함정 static은 '첫 호출만 초기화'(둘째 호출부터 누적). / 지역 변수의 주소를 반환하면 안 됨 — 함수가 끝나면 Stack에서 소멸한 주소(dangling pointer). int *f() { int x = 10; return &x; } ❌.

🎯 빈출 static 누적 추적 · 지역 주소 반환(dangling)이 매 회차.


PART E — 동적 메모리

★할쓰해놀★ — 동적 메모리 4단계 ·동적 메모리·시험 1순위·

[정의] 런타임에 크기가 정해지거나, 함수 종료 후에도 살아남아야 하거나, Stack보다 큰 데이터는 Heap에서 동적 할당한다. 4단계 = (malloc) → (사용) → (free) → (NULL). 두음 ★할쓰해놀★.

[표] 4단계

두음 단계 동작 비유
할당 malloc/calloc/realloc 창고 임차
사용 p[i] = ..., *p = ... 물건 넣고 빼기
해제 free(p) 임차 종료
NULL 대입 p = NULL 열쇠 버리기

[코드]

c
int *p = (int*)malloc(5 * sizeof(int)); // 할 — 임차
if (p == NULL) return 1;                 // 할당 실패 체크(관행)
for (int i = 0; i < 5; i++)
    p[i] = i * 10;                       // 쓰 — 사용
free(p);                                 // 해 — 반납
p = NULL;                                // 놀 — dangling 방지

💡 free한 뒤에도 p에는 옛 주소가 남아 — NULL을 넣어야 혹시 모를 재접근을 차단한다.

🔑 암기 "★할쓰해놀★ — 할(malloc) → 쓰(사용) → 해(free) → 놀(NULL)"

⚠️ 함정 malloc 직후 NULL 체크 관행 / free 뒤 p = NULL 누락.

🎯 빈출 동적 메모리 4단계가 매 회차 1~2문제.

동적 메모리 4총사 ·동적 메모리·

[정의] 동적 메모리 함수 4종. 매개변수 개수 + 초기화 여부가 분별 키.

[표] 4총사

함수 시그니처 인자 초기화 용도
malloc malloc(size) 1개 X(쓰레기) 단순 임차
calloc calloc(n, size) 2개 O(모두 0) 0 초기화 임차
realloc realloc(p, size) 2개 기존 보존 크기 변경
free free(p) 1개 반납

[코드]

c
int *p = (int*)malloc(5 * sizeof(int));  // p[0..4] = 쓰레기
p = (int*)calloc(5, sizeof(int));        // p[0..4] = 0
p = (int*)realloc(p, 10 * sizeof(int));  // 기존 5칸 보존 + 5칸 확장
free(p); p = NULL;

💡 malloc=전 사용자 물건이 남은 창고(쓰레기) / calloc=청소 끝난 창고(0) / realloc=물건 그대로 창고 확장·축소.

🔑 암기 "malloc 1인자·쓰레기 / calloc 2인자·0 / realloc 2인자·보존 / free 1인자"

⚠️ 함정 malloc은 0 초기화를 하지 않음(calloc과 혼동).

🎯 빈출 malloc vs calloc 인자 수·초기화 분별.

동적 메모리 3대 사고 ·동적 메모리·

[정의] ★할쓰해놀★ 4단계를 어기면 나는 3대 사고.

[표] 3대 사고

사고 위반 비유
메모리 누수 free 누락 창고 열쇠 안 반납
dangling pointer NULL 대입 누락 빈집 주소로 친구 보냄
이중 해제 free 두 번 반납한 열쇠 또 반납

[코드]

c
int *p = (int*)malloc(sizeof(int)); // 할
if (p == NULL) return -1;            // NULL 체크
*p = 10;                             // 쓰
free(p);                             // 해
p = NULL;                            // 놀 — dangling + 이중 해제 동시 방지

💡 free(p) 다음 줄 p = NULL 한 줄이면 dangling + 이중 해제를 동시에 차단. free(NULL)은 표준상 안전하다.

🔑 암기 "누수=해 누락 / dangling=놀 누락 / 이중 해제=해 두 번"

⚠️ 함정 free 후 NULL을 안 넣으면 dangling + 이중 해제 위험.

🎯 빈출 3대 사고 원인 매칭.


PART F — 포인터 심화

함수 포인터와 콜백 ·심화·

[정의] 함수 포인터 = 함수의 시작 주소를 담는 포인터. 시그니처 int (*op)(int, int) — 별 + 변수명을 괄호로 묶는다. 런타임에 호출할 함수를 변수로 바꿀 수 있다.

[코드]

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

int (*op)(int, int);                 // 함수 포인터 선언
op = add; printf("%d\n", op(3, 5));  // 8
op = sub; printf("%d\n", op(3, 5));  // -2

💡 콜백 = 내가 만든 함수를 다른 함수에 호출 권한과 함께 넘기는 것(qsort의 비교 함수). 시험 직접 출제는 드물어도 코드 분석에 자주 등장.

🔑 암기 "함수 포인터 = 함수 주소를 담는 변수 · int (*op)(int,int) · 가리키는 함수가 곧 호출되는 함수"

🎯 빈출 op가 가리키는 함수가 바뀔 때 결과.

구조체 포인터 — p->x ·심화·

[정의] 구조체 포인터로 멤버에 접근할 땐 화살표 ->. p->x ≡ (*p).x 동치. 자기참조 구조체로 연결리스트를 만든다.

[코드]

c
struct Point { int x; int y; };
struct Point p = {3, 4};
struct Point *pp = &p;
printf("%d %d\n", pp->x, (*pp).y);  // 3 4 — 둘 다 같은 결과

struct Node { int data; struct Node *next; };  // 자기참조
struct Node n1 = {10, NULL}, n2 = {20, NULL};
n1.next = &n2;                                  // n1 → n2 → NULL

💡 기차 칸 — 각 칸이 데이터 + 다음 칸 주소를 들고 있고, 마지막 칸의 next = NULL.

🔑 암기 "구조체 포인터 멤버 = 화살표 -> · p->x ≡ (*p).x"

⚠️ 함정 *p.x*(p.x)로 해석돼 오류 — p->x 또는 (*p).x를 쓴다.

🎯 빈출 p->멤버로 원본 수정 · 동치 분별.

const 포인터 ·심화·

[정의] const 위치로 보호 대상이 갈린다. * 기준 왼쪽=값 보호, 오른쪽=주소 보호.

[코드]

c
const int *p1 = &x;   // 값 보호:  *p1 = ... 금지 / p1 = ... 허용
int * const p2 = &x;  // 주소 보호: *p2 = ... 허용 / p2 = ... 금지
const int * const p3 = &x;  // 둘 다 보호

[표] const 위치별 보호

선언 *p 변경 p 변경
const int *p
int * const p
const int * const p

💡 함수 매개변수 void f(const int *arr, int n) — 함수 안에서 arr[i] = 0은 컴파일 오류(원본 보호 보증).

🔑 암기 "const가 * 왼쪽 = 값 보호 / * 오른쪽 = 주소 보호 (변수명에서 시계 방향으로 읽기)"

⚠️ 함정 값 보호인데 주소 변경을 막는 걸로(또는 그 반대로) 착각.

🎯 빈출 const 위치별 컴파일 오류 줄 고르기.

C 문자열 — char[] vs char* ·심화·

[정의] C 문자열 = char 배열 + 끝에 \0(null 종결). char str[]는 Stack 복사본(수정 가능), char *p는 Text 영역 리터럴(읽기 전용).

[코드]

c
char str1[] = "Hello";  // char 배열 — Stack에 복사. 수정 가능
str1[0] = 'h';           // OK

char *str2 = "Hello";   // char 포인터 — Text 영역(읽기 전용)
// str2[0] = 'h';       // 런타임 크래시 — 매년 단골
str2 = "World";          // 다른 리터럴 가리키기는 OK

[표] char[] vs char*

형태 메모리 수정
char str[] = "..." Stack(복사본) 가능
char *p = "..." Text(읽기 전용) 불가

💡 strlen\0 제외, sizeof\0 포함 — "Hello"는 strlen=5, sizeof=6.

🔑 암기 "char[]=Stack 수정 가능 / char=Text 읽기 전용 · strlen은 \0 제외 / sizeof는 \0 포함"*

⚠️ 함정 char *p = "..."p[0] = 'x' = 런타임 크래시.

🎯 빈출 char[] vs char* 수정 가능 여부.


기출 다지기

[기출 1 출제] 다음 C 코드의 실행 결과는? (코드 실행 결과 예측)

c
int x = 10;
int *p = &x;
*p = 20;
printf("%d %d", x, *p);
  • ① 10 10
  • ② 10 20
  • ③ 20 20
  • ④ 20 10
정답 및 해설 보기

정답: ③ 20 20

*p = 20은 'p가 가리키는 주소에 20 저장'이다. p가 x를 가리키므로 x도 20이 된다. 따라서 x와 *p 모두 20.

*p를 'p라는 변수 자체에 20 대입'으로 잘못 읽는 함정을 조심한다. *가 붙으면 'p가 가리키는 곳'이지 p 자신이 아니다.

🔑 암기 *p = 값 → p가 가리키는 원본이 바뀐다.

[기출 2 출제] 다음 C 코드의 실행 결과는? (코드 실행 결과 예측)

c
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d %d %d", arr[2], *(arr+2), p[2]);
  • ① 30 30 30
  • ② 30 40 30
  • ③ 20 30 40
  • ④ 컴파일 에러
정답 및 해설 보기

정답: ① 30 30 30

arr ≡ &arr[0] ≡ p 동치 + arr[i] ≡ *(arr+i) 동치다. 셋 다 인덱스 2의 값 30을 가리킨다. p도 arr와 동치이므로 p[i] 인덱스 접근이 그대로 되며 별도 변환이 필요 없다.

🔑 암기 *arr ≡ &arr[0] ≡ ptr · arr[i] ≡ (arr+i) · 배열 이름 = 첫 원소 주소.

[기출 3 출제] 다음 C 코드의 실행 결과는? (코드 실행 결과 예측)

c
void swap1(int a, int b)   { int t = a; a = b; b = t; }
void swap2(int *a, int *b) { int t = *a; *a = *b; *b = t; }

int x = 10, y = 20;
swap1(x, y);     printf("%d %d / ", x, y);
swap2(&x, &y);   printf("%d %d", x, y);
  • ① 10 20 / 10 20
  • ② 10 20 / 20 10
  • ③ 20 10 / 20 10
  • ④ 20 10 / 10 20
정답 및 해설 보기

정답: ② 10 20 / 20 10

swap1은 Call by Value라 값 복사본만 교환 → 원본 x, y는 그대로 10 20(피자 사진). swap2는 Call by Reference라 주소로 원본을 직접 교환 → 20 10(피자 가게 주소).

swap1 결과를 '20 10'으로 잘못 답하는 함정이 매년 나온다. 포인터(*, &)가 없으면 swap은 절대 안 된다.

🔑 암기 swap은 Call by Reference(*, &)일 때만 원본이 바뀐다.

[기출 4 출제] 다음 C 코드의 실행 결과는? (64비트 환경 · 코드 실행 결과 예측)

c
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
printf("%zu %zu", sizeof(arr), sizeof(p));
  • ① 20 20
  • ② 20 8
  • ③ 8 8
  • ④ 40 8
정답 및 해설 보기

정답: ② 20 8

arr는 배열이므로 전체 크기 = 4B × 5 = 20. p는 포인터이므로 8B 고정(64비트). arr ≡ p 동치이니 sizeof도 같을 거라 잘못 답하는 함정을 조심한다 — sizeof만은 컴파일러가 '배열이냐 포인터냐'를 선언 형태로 판단해 다른 결과를 낸다.

🔑 암기 sizeof(arr) = 전체 크기 / sizeof(ptr) = 8B · 동치라도 sizeof는 예외.

[기출 5 출제] 다음 C 코드의 실행 결과는? (코드 실행 결과 예측)

c
int *p = (int*)calloc(3, sizeof(int));
p[0] = 10;
p[1] = 20;
printf("%d %d %d / ", p[0], p[1], p[2]);
free(p);
p = NULL;
if (p == NULL) printf("safe");
  • ① 10 20 0 / safe
  • ② 10 20 쓰레기 / safe
  • ③ 10 20 0 (출력 없음)
  • ④ 컴파일 에러
정답 및 해설 보기

정답: ① 10 20 0 / safe

calloc은 0으로 초기화하므로 건드리지 않은 p[2] = 0이다. malloc이었다면 p[2]는 쓰레기 값 — 여기가 4총사 분별 함정. 그 뒤 free → NULL 대입으로 p == NULL이 참이 되어 safe가 출력된다. ★할쓰해놀★ 4단계를 완전 준수한 코드다.

🔑 암기 calloc=0 초기화 / malloc=쓰레기 · free 뒤 p = NULL이 안전 패턴.

[기출 6 출제] 다음 C 코드의 실행 결과는? (코드 실행 결과 예측)

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

int (*op)(int, int);
op = add;   printf("%d / ", op(2, 3));
op = mul;   printf("%d", op(2, 3));
  • ① 5 / 6
  • ② 6 / 5
  • ③ 5 / 5
  • ④ 6 / 6
정답 및 해설 보기

정답: ① 5 / 6

op가 add를 가리킬 땐 add(2,3) = 2+3 = 5, mul을 가리킬 땐 mul(2,3) = 2×3 = 6. 같은 op(2,3) 호출이지만 op가 무엇을 가리키느냐에 따라 결과가 달라진다 — 런타임에 호출할 함수를 변수로 바꾸는 게 함수 포인터의 본질.

🔑 암기 함수 포인터 = 함수 주소를 담는 변수 · 가리키는 함수가 곧 호출되는 함수.

[기출 7 출제] 다음 C 코드의 실행 결과는? (코드 실행 결과 예측)

c
struct Student { int id; int score; };
struct Student s = {101, 85};
struct Student *p = &s;
p->score = 95;
printf("%d %d", p->id, (*p).score);
  • ① 101 85
  • ② 101 95
  • ③ 95 101
  • ④ 컴파일 에러
정답 및 해설 보기

정답: ② 101 95

ps의 주소를 담은 구조체 포인터다. p->score = 95로 원본 s.score가 95로 바뀐다. id는 그대로 101. p->id ≡ (*p).id, (*p).score도 95.

*p.x*(p.x)로 해석돼 오류 — 구조체 포인터 멤버 접근은 p->x 또는 괄호를 쓴 (*p).x.

🔑 암기 구조체 포인터 멤버 접근 → p->x ≡ (*p).x · 괄호 없는 *p.x는 함정.

[기출 8 출제] 다음 중 컴파일 오류가 나는 줄을 모두 고르면? (옳지 않은 것 고르기)

c
int x = 10, y = 20;
const int *p1 = &x;
int * const p2 = &x;

// A: *p1 = 100;    B: p1 = &y;
// C: *p2 = 100;    D: p2 = &y;
  • ① A, B
  • ② A, D
  • ③ B, C
  • ④ C, D
정답 및 해설 보기

정답: ② A, D

const int *p1값 보호 → A(*p1 = 100, 값 변경) 오류, B(p1 = &y, 주소 변경) OK. int * const p2주소 보호 → C(*p2 = 100, 값 변경) OK, D(p2 = &y, 주소 변경) 오류.

시계 방향 규칙으로 읽으면 분별 끝 — const* 왼쪽이면 값 못 바꿈, 오른쪽이면 주소 못 바꿈.

🔑 암기 const * 왼쪽 = 값 보호 / * 오른쪽 = 주소 보호.


한 장 요약

주제 암기·핵심 결정 카드
포인터 ★&주*역★ &=주소 / *=역참조 · 사용 전 NULL 체크
포인터 크기 8B 고정 +1 = 자료형 크기만큼 이동
배열-포인터 arr ≡ &arr[0] ≡ ptr arr[i] ≡ *(arr+i) · sizeof만 예외(전체 / 8B)
함수 선언·정의·호출 매개변수=받는 쪽 / 인자=보내는 쪽
Value vs Reference 사진 vs 가게 주소 swap은 Reference(*,&)만 · 배열은 자동 Reference
재귀 종료 조건 + 자기 호출 종료 조건 없으면 스택 오버플로우
메모리 4영역 Stack/Heap/Data/Text 지역=Stack · 동적=Heap · 전역·static=Data
변수 범위 ★지전스★ 지=Stack / 전=Data / 스=Data(첫 호출만)
동적 메모리 ★할쓰해놀★ 할(malloc)→쓰→해(free)→놀(NULL)
4총사 인자·초기화 malloc 1·쓰레기 / calloc 2·0 / realloc 2·보존
3대 사고 누수·dangling·이중 해제 free 뒤 p=NULL 한 줄로 둘 동시 방지
심화 함수/구조체 포인터·const·문자열 p->x · const 왼쪽=값/오른쪽=주소 · char* 읽기 전용
함정 (매년 2~3쌍 출제) 정답
역참조 *p vs 곱셈 위치로 구분
sizeof(arr) vs sizeof(ptr) 전체 / 8B
Value vs Reference swap Reference에서만 바뀜
free 누락 메모리 누수
NULL 미체크 segfault
*p++ vs (*p)++ 괄호가 우선순위 뒤집음
지역 변수 주소 반환 dangling
char *p 리터럴 수정 읽기 전용 크래시

🎯 합격 한 끗: 40강은 ★&주*역★(주소·역참조) · arr ≡ &arr[0] ≡ ptr(배열-포인터 동치) · ★할쓰해놀★(동적 메모리 4단계) 세 공식에 ★지전스★(변수 범위)까지 더하면 4과목 최고 킬러 영역의 80%를 가져온다. 깊이 파기보다 추적 한 글자가 정답을 가르는 영역 — *p=20이 원본을 바꾸고, sizeof만은 동치의 예외(20/8)이며, swap은 Reference에서만 되고, free 뒤 p=NULL 한 줄이 누수·dangling·이중 해제를 막는다. 39강의 arr ≡ &arr[0]와 static 저장 영역(Data)이 여기서 포인터·★지전스★로 회수됐다. 이로써 C언어 2강(39·40강)이 마무리되고, 41강부터 Java로 넘어간다.

전체 목록 필기 이론

합격까지

정처기, 혼자 막막하다면

초개인화 학습앱 Klue와 에듀윌 온라인강의로 합격까지 이어가세요.