본문으로 건너뛰기

함수 호출과 값·참조 전달

정보처리기사 목차프로그래밍 기초레슨 132 / 233

목차

함수(function)는 이름을 붙여 둔 코드 묶음이고, 부르면 받은 값으로 일한 뒤 return으로 값 하나를 부른 자리에 돌려줍니다. 인자를 넘기는 방법은 값을 복사해 넘기는 값 전달(call by value)과, 변수의 자리를 넘겨 함수가 그 변수를 바꾸게 하는 참조 전달(call by reference)로 나뉩니다. 실행이 함수로 갔다가 부른 줄로 돌아오는 차례와 무엇이 복사되는지를 놓치면 출력을 틀립니다. 아래 그림은 함수를 부르면 칸 묶음이 쌓이고, 주소를 넘긴 칸만 바뀌며, 돌려준 값이 부른 자리로 가는 모습입니다.

10단계 중 1단계. 칸 아래 작은 글자는 주소입니다(그림 주소 — 실제 값은 실행마다 다릅니다). 9번 줄이 a에 1을 넣고, b와 c는 아직 값을 넣기 전입니다.

1함수를 만들고 부르기

그림의 코드에 결과를 찍는 12번 줄을 더했습니다.

#include <stdio.h>

int up(int n, int *p) {
  n = n + 1;
  *p = *p + 1;
  return n * 10;
}
int main(void) {
  int a = 1;
  int b = 5;
  int c = up(a, &b);
  printf("%d %d %d\n", a, b, c);
  return 0;
}
실행 결과
1 6 20
  • 3번 줄 맨 앞 int는 돌려줄 값의 자료형입니다. 돌려줄 값이 없으면 void라 적습니다.
  • n과 p는 매개변수(parameter)로, 부를 때 넘긴 인자(argument)를 받는 칸입니다.
  • 실행은 main의 첫 줄부터이고, up은 11번 줄에서 부를 때만 실행됩니다.
  • 부르면 그 함수의 칸 묶음인 프레임(frame)이 생기고, 인자 값이 매개변수 칸에 복사됩니다.

2값 전달과 참조 전달

값 전달은 인자의 사본을 넘겨, 함수가 매개변수를 바꿔도 부른 쪽 변수는 그대로입니다. 참조 전달은 변수의 자리를 넘겨, 함수가 부른 쪽 변수를 바꿉니다. 자료에 따라 값에 의한 호출·참조에 의한 호출이라고도 부릅니다. C의 인자는 늘 복사되어 넘어갑니다. C에서 참조 전달은 변수의 주소를 값으로 넘기는 것이고, C 과목은 이것을 주소 전달이라 부릅니다.

  • 4번 줄의 n은 a의 사본이라, 2가 되어도 a는 1 그대로입니다(값 전달).
  • &b는 b 칸의 주소이고, 주소를 받는 매개변수는 int *p처럼 별을 붙입니다.
  • 5번 줄의 *p는 p가 가리키는 칸, 곧 main의 b 칸 자체라 b가 6이 됩니다(참조 전달).
  • 배열을 넘기면 첫 칸의 주소가 넘어가, 함수가 바꾼 칸이 부른 쪽 배열에 남습니다. 포인터는 32단원에서 배웁니다.

3돌려준 값은 부른 자리로

return이 값을 돌려주면 프레임이 걷히고, 실행은 부른 줄로 돌아옵니다. 6번 줄이 돌려준 20이 11번 줄의 up(a, &b) 자리에 들어가 int c = 20;이 됩니다. printf("%d\n", up(a, &b));⁠처럼 호출을 printf 안에 쓰면, printf가 돌려받은 20을 찍습니다. 함수 이름이 무엇이든 그 자리에 들어가는 것은 돌려준 값입니다.

4지역 변수와 전역 변수

함수 안에서 선언한 변수와 매개변수는 그 함수의 프레임에 든 지역 변수(local variable)입니다. 부를 때 생기고 함수가 끝나면 수명이 끝나며, 이름이 같아도 다른 함수의 칸과 섞이지 않습니다. 함수 밖에 선언한 전역 변수(global variable)는 모든 함수가 함께 쓰는 칸 하나이고, 값을 주지 않으면 0에서 시작합니다. 범위와 수명, static은 32단원에서 더 배웁니다.

예제

두 값을 맞바꾸는 함수 둘

한 파일을 두 덩이로 나눠 보입니다.

#include <stdio.h>

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 main(void) {
  int x = 1;
  int y = 2;
  swap1(x, y);
  printf("%d %d\n", x, y);
  swap2(&x, &y);
  printf("%d %d\n", x, y);
  return 0;
}

무엇이 찍힐까요? 답을 정한 뒤 펼쳐 보세요.

실행 결과 보기
실행 결과
1 2
2 1

swap1은 x·y의 사본인 a·b만 맞바꾸고 끝나, 돌아온 뒤 x·y는 그대로입니다(값 전달). swap2는 x·y의 주소를 받아 *a·*b로 main의 칸을 맞바꿉니다(참조 전달). 맞바꾸기 세 줄은 배열에서 본 꼴입니다.

같은 코드를 Java·Python으로

CJavaPython
함수 머리int up(int n)static int up(int n)def up(n):
돌려줄 값이 없으면voidvoidNone을 돌려줌 — print(f(x))가 None을 찍음
부른 쪽 변수 바꾸기주소를 넘김못 함못 함
배열·리스트를 넘기면바꾼 칸이 부른 쪽에 남음같음같음

시험 포인트

  • 필기: 함수가 든 코드의 결과를 고릅니다. return 값은 부른 식의 자리에 들어가고 실행은 부른 줄로 돌아옵니다. 값 전달은 사본이라 부른 쪽 변수가 그대로이고, C에서 주소(&x)나 배열을 넘기면 함수가 부른 쪽 칸을 바꿀 수 있습니다. 지역 변수는 그 함수의 칸, 전역 변수는 모든 함수가 함께 쓰는 칸입니다.
  • 실기: 출력을 그대로 씁니다(값 전달과 참조 전달 출제 주제). a = f(x)·printf("%d", f(x))·Python의 print(f(x))에 들어가는 것은 함수 이름이 아니라 f가 돌려준 값입니다. 이름은 값 전달(Call by Value)·참조 전달(Call by Reference)로 씁니다.

확인 문제

1. 아래 함수를 x가 1일 때 부릅니다. 부른 뒤 main의 x가 바뀌는 호출을 고르세요.

void f1(int n) {
  n = 5;
}
void f2(int *p) {
  *p = 5;
}
int f3(int n) {
  return n + 5;
}
  • ① f1(x);
  • ② f2(&x);
  • ③ f3(x);
  • ④ f1(x + 4);
정답

②입니다. f2는 x의 주소를 받아 *p로 main의 x 칸에 5를 넣습니다(참조 전달).

  • ① n은 x의 사본이라 n만 5가 되고 x는 1 그대로입니다.
  • ③ f3은 6을 돌려주지만 그 값을 x에 담지 않았습니다. x = f3(x);라야 x가 바뀝니다.
  • ④ x + 4를 계산한 값 5의 사본을 넘길 뿐입니다.

2. 1절 코드의 11번 줄을 int c = up(b, &a);로 바꿨습니다. 출력을 그대로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

정답

받는 답:

2 5 60

이번에는 b의 값 5가 n에 복사되고, a의 주소가 p에 넘어갑니다. *p로 바뀌는 칸은 a라 a가 2, n은 6이 되어 60을 돌려줍니다. b는 5 그대로입니다.

  • 틀리는 꼴 2 6 60: n을 바꾼 4번 줄도 부른 쪽 b를 바꾼다고 봤습니다. n은 b의 사본입니다.
  • 틀리는 꼴 1 6 20: 바꾸기 전의 결과입니다. 주소가 넘어간 쪽이 바뀝니다.

3. 다음 코드의 호출을 표로 따라갔습니다. (가)~(다)를 채운 뒤 출력을 그대로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

부른 곳부른 뒤 total돌려준 값그 뒤 r
9번 줄 add(3)366
10번 줄 add(4)(가)(나)(다)
#include <stdio.h>

int total;
int add(int x) {
  total = total + x;
  return total * 2;
}
int main(void) {
  int r = add(3);
  r = r + add(4);
  printf("%d %d\n", r, total);
  return 0;
}
정답

받는 답: (가) 7 · (나) 14 · (다) 20, 출력은 아래와 같습니다.

실행 결과
20 7

total은 전역 변수라 0에서 시작하고, 두 호출이 같은 칸에 더합니다. 둘째 호출은 3에 4를 더한 7의 두 배 14를 돌려주고, r은 6 + 14 = 20입니다.

  • 틀리는 꼴 14 7: 10번 줄에서 r에 돌려준 값만 담는다고 봤습니다. 앞의 r 6에 더합니다.
  • 틀리는 꼴 14 4: 부를 때마다 total이 0에서 다시 시작한다고 봤습니다. 전역 칸은 하나뿐이라 값이 남습니다.

4. 다음 Python 코드의 출력을 그대로 쓰세요. [1, 2]는 C의 배열처럼 0번부터 번호로 칸을 꺼내 쓰는 리스트입니다. 답을 끝까지 쓴 뒤 정답을 펼치세요.

def put(a, x):
    a[0] = x
    return x * 2


def show(n):
    print(n)


b = [1, 2]
print(put(b, 5))
print(show(b[0]))
print(b)
정답

받는 답:

실행 결과
10
5
None
[5, 2]

print(put(b, 5))는 put이 돌려준 10을 찍습니다. put은 넘겨받은 리스트의 0번 칸을 5로 바꿔, b도 [5, 2]가 됩니다. show는 5를 찍기만 하고 돌려주는 값이 없어 None을 돌려주고, 바깥 print가 그 None을 찍습니다.

  • 틀리는 꼴 [5, 2]로 시작: 함수 이름이 put이라 바뀐 리스트가 찍힌다고 봤습니다. 찍히는 것은 돌려준 값 10입니다.
  • 틀리는 꼴 None 줄이 없음: show 안의 print가 찍은 5를 돌려준 값으로 봤습니다. 찍는 것과 돌려주는 것은 다릅니다.
  • 틀리는 꼴 마지막 줄 [1, 2]: 리스트도 사본이 넘어간다고 봤습니다. 함수가 바꾼 칸은 b에 남습니다.

5. 함수에 인자를 넘기는 두 방법 가운데 (가)·(나)에 맞는 이름을 한글 이름과 영문으로 함께 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

  • (가) 함수가 매개변수를 고쳐도 부른 쪽 변수는 그대로 남습니다. 사본을 받기 때문입니다.
  • (나) 함수가 부른 쪽 변수의 자리(C에서는 주소)를 받아 그 변수를 직접 고칩니다.
정답

받는 답: (가) 값 전달(Call by Value) · (나) 참조 전달(Call by Reference)

(가)의 실마리는 '사본을 받아 부른 쪽이 그대로', (나)는 '자리를 받아 부른 쪽을 고친다'입니다. 값에 의한 호출·참조에 의한 호출이라는 이름도 받습니다.

  • 같은 이름의 다른 꼴: (나) 주소 전달 — C 과목이 C의 꼴을 부르는 이름입니다. 시험 답은 참조 전달(Call by Reference)로 씁니다.
  • 틀리는 꼴 값 전달 — (나)에: C가 주소도 복사해 넘기는 것은 맞지만, 자리(주소)를 넘겨 부른 쪽 변수를 고치는 방식은 참조 전달이라 부릅니다.

처음이라면: C 언어 과목(준비 중)의 '함수 만들고 부르기' · '매개변수는 복사본이다' · '주소를 넘겨 바깥 칸 바꾸기' 레슨에서 같은 규칙을 처음부터 천천히 봅니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

정보처리기사 목차로