int addTen(int a) {
a = 7;
return a;
}
int main() {
int a = 5;
addTen(a);
printf("함수 밖: a = %d\n", a);
return 0;
}
이러면 출력값 5
main()에서 a는 5로 선언됨.
addTen(a)를 호출할 때,
→ a의 값 5가 복사되어 함수 매개변수 a(다른 변수) 에 전달됨.
main의 a = 5
addTen의 a = 5 (복사본)
함수 안에서 a = 7;을 해도, 복사본(addTen의 a) 을 바꾸는 것.
→ main의 a는 그대로 5인 상태로 남음
addTen 함수가 return a;로 7을 반환하지만, main()에서 그 반환값을 변수에 저장하지 않음
addTen(a); -> 이렇게만 써놨기 때문에 리턴값이 버려짐
만약 7로 바꾸고 싶다면?
✅: 리턴값을 받아서 저장
int main() {
int a = 5;
a = addTen(a); // 리턴값(7)을 저장
printf("함수 밖: a = %d\n", a);
return 0;
}
➡️ 출력: 함수 밖: a = 7
#include <stdio.h>
int addTen(int a) {
a = 7;
printf("%d ", a); // 따옴표("") 안에 %d 넣기, 뒤에 공백 하나
return a;
}
int main() {
int a = 5;
addTen(a);
printf("%d\n", a); // "함수 밖: " 대신 숫자만 출력하도록
return 0;
}
하면 출력값 7 5
#include <stdio.h>
int change(int x) {
x = x + 5;
printf("함수 안: x = %d\n", x);
return x;
}
int main() {
int a = 10;
change(a);
printf("함수 밖: a = %d\n", a);
return 0;
}
함수 안 15, 함수 밖 10
call by value vs call by reference
void add(int *x) {
*x = *x + 1; // x가 가리키는 "원본 int"를 1 증가
}
int calc(int x, int *y) {
x = x + 2; // x는 값 복사본
add(y); // y는 이미 "주소(int*)"다. 그대로 전달
printf("calc 안: x = %d, *y = %d\n", x, *y);
return x + *y;
}
int main() {
int a = 5, b = 10;
int result = calc(a, &b); // &b: b의 주소를 넘김
printf("main: a = %d, b = %d, result = %d\n", a, b, result);
}
흐름 추적
main: a=5, b=10.
calc(a, &b) 호출
x는 값 복사 → x = 5y는 포인터(주소) → y = &bx = x + 2; → x = 7 (원본 a는 그대로 5)
add(y) 호출
add의 매개변수 타입은 int *xy는 이미 int*이므로 그대로 넘기는 게 맞음 (add(y))add 내부에서 *x = *x + 1; → *(&b) = b = 10 + 1 = 11그래서 calc 안에서 *y는 *(&b) = b = 11
출력: calc 안: x = 7, *y = 11
return x + *y → 7 + 11 = 18
main 출력: a = 5(안 바뀜), b = 11(주소로 바뀜), result = 18
헷갈린 포인트
“& 없으면 주소로 안 들어가는 거 아냐?”
int의 주소가 필요할 때)는 &가 필요해. 그래서 calc(a, &b)에서 &b를 쓴 거야.그 다음 단계에서 y는 이미 주소(int*) 를 담고 있어.
add도 int*를 받으니까 그냥 add(y)로 호출하는 게 정답.add(&y)라고 했다면 `int(이중 포인터)를 넘기는 꼴**이 되고, 함수 시그니처도 void add(int **x)` 여야 해. 전혀 다른 이야기.한 줄 요약
&원본변수가 필요.add(y))#include <stdio.h>
void addOne(int *x) {
*x = *x + 1;
}
void changePtr(int **p) {
**p = **p + 2;
printf("changePtr 안: **p = %d\n", **p);
}
int main() {
int a = 5;
int *ptr = &a;
addOne(ptr);
changePtr(&ptr);
printf("main: a = %d\n", a);
return 0;
}
전체 흐름
int a = 5;
int *ptr = &a;
addOne(ptr); // (*ptr) = (*ptr) + 1 → a = 6
changePtr(&ptr); // (**p) = (**p) + 2 → a = 8
printf("main: a = %d\n", a); // ??? 왜 8이지?
addOne() 함수 내부
void addOne(int *x) {
*x = *x + 1;
}
x는 ptr의 복사본(주소를 복사)x가 가리키는 곳은 여전히 a의 메모리 주소*x = *x + 1; → a 값이 직접 바뀜changePtr() 함수 내부
void changePtr(int **p) {
**p = **p + 2;
printf("changePtr 안: **p = %d\n", **p);
}
&ptr을 넘겼으니p는 “ptr의 주소”를 받음.ptr 자체가 가리키는 건 여전히 a.그림으로 보면
main 영역
┌───────────────┐
│ a = 6 │
│ ptr ───────┐ │
└────────────┼──┘
│
▼
(주소) → [ a의 메모리 6 ]
이때 &ptr이 넘어가면
p는 ptr을 가리키는 포인터의 주소를 가짐.
그래서 **p는 결국 p → ptr → a → 값(6)
**p = **p + 2;는 a = a + 2;,
💡 a = 8
changePtr() 함수 종료 후
함수가 끝나도, a는 여전히 메모리에 8로 저장되어 있음.
왜냐면 **p로 a의 실제 메모리 공간을 바꾼 거니까.
a: 8
ptr: 여전히 &a
main()의 printf 실행 시점
이제 printf("main: a = %d\n", a); 하면 바뀐 a(8)를 그대로 출력
최종 출력은
changePtr 안: **p = 8
main: a = 8
🧠 요약 포인트
| 함수 | 매개변수 | 실제로 바뀌는 대상 | 결과 |
|---|---|---|---|
addOne(int *x) | x → a의 주소 복사 | a 값 직접 바뀜 | a = 6 |
changePtr(int **p) | p → ptr의 주소 복사, **p → a | a 값 직접 바뀜 | a = 8 |
int arr[3] = {10, 20, 30};
int *p;
arr(배열이름, 상수) = p; ❌ 오류
p(포인터변수) = arr; ✅ 가능
p = arr; → ✅ 가능
이건 배열의 시작 주소(1000) 를 포인터 변수 p에 저장하는 것
arr = p; → ❌ 오류
포인터의 주소값을 배열 이름에 넣으려는 시도, 배열 이름은 상수이기 때문에 값을 바꿀 수 없음
컴파일러 입장:
“상수(주소)를 다른 값으로 대입하려 하네? 말이 안 돼!”
배열 이름 arr은 변수(variable) 이 아니라 “고정된 메모리 이름(label)”
= 주소값을 바꾸는 대입 연산(=)은 불가능
| 예시 | 실제 의미 |
|---|---|
p = arr; | “변수 p에 5를 넣어라.” — 가능 |
arr = p; | “5 = 7;” — 말이 안 됨 ❌ |
arr = p; 이건 “5 = 7;” 같은 말도 안 되는 문장
왼쪽이 ‘값을 담는 변수’가 아니라 ‘이미 정해진 상수’이기 때문
| 상황 | 설명 |
|---|---|
p는 “택배 기사 이름표” | 기사 이름표(p)는 다른 집 주소로 옮길 수 있음 |
arr은 “집 주소 자체” | 집 주소(arr)는 영구적으로 고정돼 있음 |
풀이하면
| 표현 | 실제 의미 |
|---|---|
p는 “택배 기사 이름표” | 포인터 변수는 주소를 저장할 수 있는 변수다. 즉, “지금 어느 집에 가야 하는지 적어둘 수 있다.” |
p는 다른 집으로 옮길 수 있다 | p = 다른주소; 가능. 즉, “기사 이름표에 새 주소를 적을 수 있다.” |
arr은 “집 주소 자체” | 배열 이름은 이미 그 집(메모리)의 고정된 주소다. |
arr은 영구적으로 고정돼 있다 | arr = ...; 불가능. 집 주소는 바뀌지 않는다. |