모두의 코드
씹어먹는 C 언어 - <13 - 1. 마술 상자 함수(function)>

작성일 : 2009-12-14 이 글은 49612 번 읽혔습니다.

이번 강좌에서는

  • 함수의 필요성

  • 함수의 의미, 함수의 인자, 함수의 리턴값, 함수 내부에서 선언된 변수

  • main 함수

씹어먹는 C 언어

안녕하세요 여러분. 이 강좌를 읽고 있을 여러분은 포인터의 고지를 정복하고 온 위대한 전사(?) 들 입니다. 이제, 앞으로 다룰 내용은 C 언어에서 중요하면서도 쉬운 부분 이니 큰 부담 없이 편히 읽으시면 합니다. 또한, 앞에서 배운 포인터를 이제 본격적으로 활용하는 단계에 접어들기 때문에 혹여라도 잊은 것이 있는지 없는지 매일 한 번씩 다시 정독하시면 좋습니다. 또는, 다른 C 언어 강좌로 한 번 더 공부해 보세요. 다른 방식으로 공부하다 보면 이해가 더 잘될 수 도 있습니다.

저는 제 강좌에서 여러분이 최소한 초등학교 4 학년 정도의 수학을 이수하셨다면 아래와 같은 문제를 본 기억이 어렴풋이나마 있을 것 입니다.

이 상자는 들어간 값에 4 를 더한 게 튀어나온다. 36 을 집어넣으면 뭐가 튀어나올까요!!

음.. 아래 물음표에서 어떤 값이 출력될까요? 아마, 여러분 대부분은 '40' 이라고 짐작하실 것입니다. 맞습니다. 40 입니다. 위 마술 상자는 입력 받은 값에 4 를 더해서 출력하는 상자 입니다. 만일 우리가 36 이 아니라 10 을 집어넣었다면 14 가 나왔을 것이지요.

수학에서 함수는 마술 상자와 비슷합니다. 특정한 값을 입력 받아, 이 값을 가지고 상자 내부에서 지지고 볶고 해서 결과를 내보낸 마술 상자 처럼, 수학에서는 특별한 값 x 를 입력 받아 지지고 볶은 뒤(유식한 말로 연산을 취하여)에 결과를 출력하는 것을 함수 라고 합니다. (참고적으로, 수학에서 보통 입력값은 x, 출력값은 y 라고 하니, 아래에선 아무런 이야기 없이 사용하도록 하겠습니다.)

수학에서 마술 상자를 글로 표현하기 조금 껄끄러우니 보통 다음의 표현을 사용합니다.

$y = f(x)$

이는, 'x 라는 값을 f 라는 마술 상자 (함수) 를 통과시켰더니 y 라는 값이 되었다' 라는 의미와 일맥 상통합니다. 위의 마술 상자의 경우 입력값에 4 를 더한 값을 반환하였습니다. 그렇다면, 위의 마술 상자는 아래와 같은 식으로 나타낼 수 있습니다.

$f(x) = x + 4 $

예를 들어 x 에 36 이 들어간다면 f(x) 의 값, 즉 y 의 값은 36 + 4 인 40 이 됩니다. 따라서, 40 이 출력된다는 사실을 볼 수 있습니다. 그렇다면, 아래의 예를 보고 어떠한 값이 출력되는지 맞추어 보세요.

$f(x) = x^3 + 2x, f(x) = ? $

$g(x) = x^2 - 3x + 4, g(5) = ?$

(이례적으로 답을 올리자면 f(3) = 33, g(5) = 14)

간혹 제 블로그를 방문하는 분들 중에는 초등학생인 분들이 있기에, 함수를 전혀 들어보지 못한 분들이 있을 까봐 짤막하게 함수에 대해 설명하였습니다. C 언어의 함수도 비슷한 개념으로 사용됩니다.

함수의 시작

우리가 프로그래밍을 하면서 여러가지 작업들을 반복적으로 해야되는 경우가 종종 있습니다. 예를 들어서 변수 ab 중 최대값을 구하는 것을 생각해봅시다. 우리가 이를 프로그래밍 시에 필요로 하게 된다면 다음과 같이 해야 될 것입니다.

int max;
if (a >= b) {
  max = a;
} else {
  max = b;
}

뭐, 위 코드는 아주 아주 쉬운 코드 이니 설명은 하지 않겠습니다. 그런데, 실제로 프로그래밍을 하다 보면 어떠한 두 변수의 최대값을 구하는 경우가 자주 생긴다는 것입니다. 현재 까지 배운 바로는 이러한 상황에서는 코드 복사 붙여넣기를 통해 소스를 채워나가면 된다고 생각했습니다. 자, 그렇다면 이러한 방법이 합리적인 것일까요?

만일 최대값을 구하는 것이 프로그램에서 100 번 정도 필요하다면 그 때 마다 위 코드를 복사해서 변수 이름만 살짝 바꿔주면 됩니다. 하지만, 소스가 얼마나 지저분해질까요? 소스가 수천줄이 넘어가면 위 코드가 무슨 작업을 하는지 눈에 팍 들어오기 힘듧니다.

그렇다면 여러분은 이렇게 생각해 볼 수 있습니다.

"최대값을 출력하는 함수를 만들어버리자!!"

응? 도대체 위 말이 무슨뜻인감.. 아마도 여러분은 갈피를 잡기 힘들 것입니다. 하지만 이렇게 생각하면 편합니다. 아까 위에서 설명한 마술 상자 처럼 우리가 만들게 될 마술상자는 두 개의 값이 입력된다면 큰 놈을 출력하는 것이야!

오오. 괜찮은 아이디어 아닌가요. 우리는 그 긴 코드(사실 그렇게 긴 것은 아니지만;;) 를 매번 쓰는 대신에 두 값을 입력받아서 큰 것을 출력하는 마술 상자 (함수) 를 제작하여, 최대값을 구하는 것이 필요할 때 마다 그 마술 상자에 두 변수를 넣어 버리면 되지 않습니까? 그러면 우리는 그 마술 상자가 뱉어내는 값을 받아 먹기만 하면 되는 것이니까요.

자, 그럼 마술 상자를 만들어봅시다~

일단, 최대값을 구하는 함수를 만들어 보기 전에 아주 아주 간단한 함수를 먼저 만들어보겠습니다.

#include <stdio.h>
/* 보통 C 언어에서, 좋은 함수의 이름은 그 함수가
무슨 작업을 하는지 명확히 하는 것이다. 수학에서는
f(x), g(x) 로 막 정하지만, C 언어에서는 그 함수가 하는
작업을 설명해주는 이름을 정하는 것이 좋다. */
int print_hello() {
  printf("Hello!! \n");
  return 0;
}
int main() {
  printf("함수를 불러보자 : ");
  print_hello();

  printf("또 부를까? ");
  print_hello();
  return 0;
}

성공적으로 컴파일 하였다면

실행 결과

함수를 불러보자 : Hello!! 
또 부를까? Hello!! 

음.. 일단 우리가 여태까지 보아왔던 것과 매우 다른 모습입니다. 하지만 걱정하지 마세요. 금세 이해하게 될 것이니까요.

int print_hello() {
  // 잠시 생략
}

일단, 함수의 정의(definition) 부분을 살펴 봅시다. 위와 같이 int print_hello() 라고 써 있는 부분을 함수의 '정의' 부분이라 부릅니다. 우리는 함수의 정의 부분에서 3 가지 사실을 알 수 있는데, 일단은 2 가지만 먼저 설명하고 나머지 하나는 아래에서 설명하겠습니다.

먼저, 우리에게 친근한 키워드가 하나 있습니다. 바로 int ! 우리는 여태까지 int 를 변수나 배열을 정의하는데만 사용하였습니다. 그런데, int 가 놀랍게도 함수를 정의하는데도 사용되고 있습니다. 여기서의 int 는 다음과 같은 사실을 알려줍니다. '이 함수는 int 형의 정보를 반환한단다~'. 반환? 그렇다면 반환은 또 뭐야.

우리가 앞서, 마술 상자를 이야기 하였을 때, 우리가 36 을 마술 상자에 넣는다면, 4 를 더해서 40 을 출력한다고 하였습니다. 이 때, 우리는 '출력된다' 라는 사실을 함수에서는 '반환한다' 라고 이야기 합니다. 영어로는 return 이라고 하지요.

int print_hello() {
  printf("Hello!! \n");
  return 0;
}

위 함수 정의 부분에서, 아래에서 두번째 줄에 return 0; 라고 써있는 부분을 볼 수 있습니다. 이 함수는 0 을 반환한다는 뜻이군요. 즉, 우리가 위와 같은 마술 상자를 이용한다면 언제나 0 이 출력됩니다. 이 때, 함수의 반환형이 int 이므로, 0 은 int 의 형태로 저장되어 나갑니다.

여기서 int 의 형태로 저장된다는 말의 의미는 0 이라는 데이터가 메모리 상의 4 바이트를 차지하여 반환된다는 뜻이지요. (통상적으로 정수를 반환하는 함수들은 모두 int 를 사용합니다. )

함수의 정의 부분에서 알 수 있는 두 번째 사실은 바로 함수의 이름 입니다. 대충 짐작이 가듯이, 위 함수의 이름은 print_hello 입니다. 끝에 붙는 () 는 함수의 이름에 포함되는 것이 아닙니다. 끝에 붙는 괄호 두 개는 이것이 함수라는 사실을 의미합니다. 만일 우리가 끝에 () 를 붙이지 않는다면 int print_hello 라는 문장은 단순히 끝에 ; 를 제대로 붙이지 않았구나 라고 해석되어 오류를 출력하게 됩니다. 꼭 () 를 붙여주세요~

주석에서도 잘 설명 하였듯이 좋은 함수 이름의 조건은 함수가 무슨 일을 하는지에 대해서 잘 설명하는 것 입니다. 만일 우리가 함수를 int asdfasd() 라고 만들었다면 우리가 asdfasd 라는 함수를 보고 무슨 일을 하는지 잘 알 수 없습니다.

하지만 우리의 예제 처럼 print_hello 라고 하게 된다면 이 함수가 대략 'hello 를 출력하는구나' 라는 사실을 알 수 있겠지요. 다만, 함수의 이름이 너무 길어지면 함수를 사용시 너무 불편하므로 20 자가 넘어가게 하지는 맙시다. 또한, 함수의 이름 역시 변수의 이름 조건과 동일하므로 기억나지 않는 분들은 3강 변수가 뭐지? 의 맨 마지막 부분을 보세요.

int print_hello() {
  printf("Hello!! \n");
  return 0;
}

함수의 정의부분은 그만 살펴보고, 이제 함수가 무슨 일을 하는지 알 수 있는 부분을 살펴 봅시다. 이 부분은 보통 함수의 몸체(body) 라고 부릅니다. 이번 예제 함수의 몸체는 설명을 안해도 잘 알 수 있습니다. 이 함수는 printf("Hello!! \n"); 을 실행한 후, 0 을 반환한다 이지요?

printf("함수를 불러보자 : ");
print_hello();

printf("또 부를까? ");
print_hello();

마지막으로 실제로 함수를 호출하는 부분을 살펴 봅시다. 함수를 불러내는 방법(보통 호출한다(call) 라는 표현을 사용하므로 앞으로 호출한다고 표현하겠습니다) 은 단순히 함수의 이름을 써주시기만 하면 됩니다. 물론 그 뒤에 () 도 붙여주어야 겠지요.

다시 말하지만 () 는 함수의 이름에 포함되는 것이 아닙니다. 하지만, () 를 써줌으로써 컴파일러에게 '내가 지금 쓴 것이 함수 이니라~' 라는 사실을 말해주게 되는 것이지요. 만일 함수를 호출한답시고 print_hello; 라고 쓴다면 컴파일러는 '어딘가에 print_hello 라는 변수에 접근하였네' 라고 생각하는데, print_hello 라는 변수가 없으므로 오류를 출력하게 됩니다.

함수를 호출하면 프로그램은 함수의 내용을 실행하게 됩니다. 그리고 다시, 원래 실행되려는 부분으로 돌아오게 되죠. 위의 경우, "함수를 불러보자" 가 출력된 후 print_hello() 를 통해 함수를 호출하였습니다. 그러면 프로그램은 print_hello() 라는 함수로 넘어가서, 이 함수의 내용을 다 실행한 뒤에 다시 원래 있던 곳으로 돌아와 넘어가게 됩니다.

이와 같은 현상은 실생활에서도 볼 수 있습니다. 밥을 먹고 있다가 '엄마가 부르신다' 라는 함수가 호출되면 엄마한테로 달려갑니다. 그리고 '엄마가 부르신다' 라는 함수가 종료되면 다시 밥 먹던 식탁으로 와서 밥을 먹게 되지요. 이 때, 함수의 종료는 두 가지 형태로 있을 수 있습니다. 하나는 반환이 되어 종료를 하게 되는 것이고 다른 하나는 함수의 끝 부분 까지 실행하여 종료되는 것입니다. 함수는 반환을 하여 종료되는 것이 안전합니다. 한 가지 중요한 사실은 return 을 실행하면 함수는 무조건 종료되어 함수를 호출하였던 부분을 돌아간다는 점입니다.

/* 함수의 리턴 */
#include <stdio.h>
int return_func() {
  printf("난 실행된다 \n");
  return 0;
  printf("난 안돼 ㅠㅠ \n");
}
int main() {
  return_func();
  return 0;
}

성공적으로 컴파일 한다면

실행 결과

난 실행된다 

물론 앞에서 이야기 하였듯이 짐작은 하고 있으셨겠지만 확실히 보여드리기 위해 예제를 작성하였습니다.

int return_func()

연습 삼아 위 부분이 무슨 의미인지 다시 한 번 살펴봅시다. 일단, int 를 보아 이 함수는 int 형을 리턴한다는 의미이고, return_func 을 보아서 이 함수의 이름이 return_func 라는 사실을 알 수 있습니다.

{
  printf("난 실행된다 \n");
  return 0;
  printf("난 안돼 ㅠㅠ \n");
}

다음은 함수의 몸체 입니다. 앞에서 이야기 하였듯이 return 이 실행되면 프로그램은 바로 함수를 호출하였던 부분으로 넘어가 버려 그 다음에 오는 모든 것들(위 예제에선 printf("난 안돼 ㅠㅠ \n");) 이 실행되지 않게 됩니다.

/* 반환값 */
#include <stdio.h>
int ret() { return 1000; }
int main() {
  int a = ret();
  printf("ret() 함수의 반환값 : %d \n", a);

  return 0;
}

성공적으로 컴파일 한다면

실행 결과

ret() 함수의 반환값 : 1000 

마지막으로 한 번더, 함수의 정의 부분을 분석해봅시다.

int ret()

아마 이쯤 되면 여러분은 위 것만 보고도 이 함수는 이름이 ret 이고, int 형을 반환한다 라는 사실을 알 수 있을 것 입니다.

그리고 ret 함수의 몸체를 살펴 보자면 상당히 간단하다라는 것을 알 수 있습니다.

{ return 1000; }

그리고 위 코드는 "이 함수를 호출하면 1000 을 리턴한다" 정도 되겠지요.

int main() {
  int a = ret();
  printf("ret() 함수의 반환값 : %d \n", a);

  return 0;
}

위는 ret() 함수를 호출하여 그 값을 a 에 대입하는 문장 입니다. 그런데 ret() 가 가지는 값이 있나요? 물론, ret() 는 함수이기 때문에 위와 같이 이용하면 안될것 같습니다만, ret() 를 코드에 쓰게 된다면 이 말은 "ret() 함수의 반환값" 라는 의미를 가집게 됩니다. 즉, 컴퓨터가 위 코드를 실행한다면 a 에는 ret 함수의 반환값인 1000 이라는 값이 들어가게 됩니다.

아무튼 아래의 유명한 격언을 기억하시기 바랍니다.

호랑이는 죽어서 가죽을 남기고, 함수는 죽어서 리턴값을 남긴다!

메인(main) 함수

아마 꼼꼼하신 여러분들은 이미 int main() 이란 부분도 main 이라는 함수를 정의하고 있다는 사실을 눈치 채고 있을 것입니다. 맞습니다. 여러분은 main 이라는 이름의 함수를 정의하고 있는 것이였습니다. 그런데 왜 하필이면 main 일까요?

왜냐하면 프로그램을 실행할 때 컴퓨터가 main 함수 부터 찾기 때문입니다 (물론 모든 경우가 그런 것은 아니고 적어도 우리가 앞으로 만들게 될 C 프로그램들의 경우). 즉, 컴퓨터는 프로그램을 실행할 때 프로그램의 main 함수를 호출함으로써 시작합니다. 만일 main 함수가 없다면 컴퓨터는 프로그램의 어디서 부터 실행할 지 모르게 되어 오류가 나게 되죠.

보통 메인 함수를 아래와 같은 형태로 정의합니다.

int main()

위에서 배운 내용을 살짝 활용하면 "이 함수는 리턴형이 int 이고 이름은 main 이네!" 정도 알 수 있겠지요. 그런데, 메인 함수가 리턴을 하면 누가 받을까요? 메인 함수가 프로그램 맨 처음에 실행되는 함수라면, 맨 마지막으로 종료되는 함수도 메인 함수 이기 때문에 리턴값을 받을 수 있는 함수가 없을 듯 합니다.

사실, 그렇지 않습니다. 메인 함수가 리턴하는 데이터는 바로 운영체제가 받아들입니다. 운영체제. 즉 여러분이 아마도 쓰고 계실 Windows XP 나 Linux 에서 받는 다는 이야기 이지요.

보통 메인 함수가 정상적으로 종료되면 0 을 리턴하고, 비정상적으로 종료되면 1 을 리턴한다고 규정되어 있습니다. 우리가 여태까지 만들어왔던 모든 메인 함수들은 정상적으로 종료되므로 마지막에 0 을 리턴하였죠. 사실, 1 을 리턴한다고 해서 큰 문제는 없습니다. 이 정보를 활용하는 경우는 매우 드물기 때문이죠.

아무튼, 여기서 알아야 할 사실은 "main 도 함수다!" 정도만 알아 두셨으면 합니다.

이번에는 맨 위에서 구상하였던 마술 상자 ( 4 를 더한값을 출력하는..) 를 제작해보기로 하였습니다. 일단 여러분은 아래와 같이 구현할 수 있지 않을까 라는 것을 머리속에 떠올릴 것입니다.

/* 마술 상자 */
#include <stdio.h>
int magicbox() {
  i += 4;
  return 0;
}
int main() {
  int i;
  printf("마술 상자에 집어넣을 값 : ");
  scanf("%d", &i);

  magicbox();
  printf("마술 상자를 지나면 : %d \n", i);
  return 0;
}

컴파일 하면 아래와 같이 달콤한 오류를 볼 수 있습니다.

컴파일 오류

error C2065: 'i' : 선언되지 않은 식별자입니다.

아니, 왜? 이런 오류가 뜨는 것이지.. 분명히 우리는 main 함수 내에서 i 라는 이름의 int 형 변수를 선언하였고 다른 함수(여기선 magicbox) 에서 사용할 수 있어야 되는 것 아닌가요? 하지만 안타깝게도 아닙니다. 사실, 이 마술상자는 우리가 생각했던 것 보다도 훨씬 멋진 개념입니다.

어떠한 함수를 호출할 때, 호출된 함수는 함수를 호출한 놈에 대해서 어떠한 것도 알고 있지 않습니다. 즉, 내가 magicbox 라는 함수를 호출하였을 때, 이 magicbox 는 내가 얘를 호출하였는지, 다른 애가 (즉, 다른 코드를 말하는 것이겠죠;;) 얘를 호출하였는지 '전혀 알 수 없다' 라는 것입니다.

int magicbox() {
  i += 4;
  return 0;
}

따라서 이 함수는 i 라는 변수에 대해서 아무런 정보도 가지지 않고 있습니다. 왜냐하면 이 함수를 호출한 것이 무엇인지에 대한 정보가 하나도 없기 때문이죠. 결과적으로 main 함수에서 정의된 i 라는 변수는 magicbox 의 입장에서 본다면 듣도 보도 못한 것이 되는 것입니다. 결과적으로 위에서 보았던 오류와 같이 i 라는 변수가 선언되어있지 않다는 오류를 내게 됩니다.

아직도 위 코드가 왜 작동이 되지 않는지 이해가 되지 않으신 분들은 아래의 옛날 이야기(?) 를 보시면 됩니다.

옛날 옛날 이집트 시대에 어떤 부유한 귀족이 있었습니다. 이 귀족은 하루에 10000 달러씩 장사를 해서 벌었습니다. 그런데 공교롭게도 수학을 매우매우 못했죠. 따라서, 이 귀족은 노예를 한 명 사서, 이 노예에게 자신의 현재 재산에 10000 을 더해서 알려 달라고 하였습니다. 그리고 시간이 흘러 10 시간 뒤, 귀족의 일과가 끝났습니다. 이제, 그는 오늘 자신의 재산 현황을 파악하기 위해서 노예를 호출했습니다.

야 말해

그런데 노예는 아무 말도 하지 못했습니다.

야 말하라고, 내 재산에 10000 을 더해서 말하라니까

역시 아무말도 없었습니다. 왜일까요? 그야, 당연히 노예는 귀족의 재산에 대한 정보가 없었기 때문입니다. 귀족이 방금 노예를 호출함으로써 한 일은, "자신의 재산 += 10000" 이였습니다. 그런데, '자신의 재산' 이란 변수는 노예의 머리에서 정의된 것이 아니므로 알 노릇이 없습니다.

그렇다면, 이제 아무 쓸모 없게된 불쌍한 노예를 악랄한 귀족이 죽이게 내버려 두어야 하나요? 물론, 그리하면 안되겠죠. 일단, 여기서 문제점을 해결하기 위해선 노예가 "현재 귀족의 재산" 이라는 데이터만 머리에 넣고 있으면 됩니다. (노예가 계산을 충분히 잘한다는 가정 하에..) 이 말을, C 언어 적으로 이야기 하면 노예라는 함수에 "주인의 현재 재산" 이라는 변수를 정의하고 이 변수에 "자신(주인)의 재산" 의 값을 넣은 뒤에, "주인의 현재 재산+=10000" 을 계산한 후, "주인의 현재 재산" 을 반환(입으로 말함) 하면 되는 것입니다.

이제, 문제는 노예 머리속에 "주인의 현재 재산" 이라는 변수에 "자신(주인) 의 재산" 값을 어떻게 넣느냐가 문제 입니다. 바로 아래에서 보도록 하죠.

함수의 인자

#include <stdio.h>
int slave(int master_money) {
  master_money += 10000;
  return master_money;
}
int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));

  return 0;
}

성공적으로 컴파일 하면

실행 결과

2009.12.12 재산 : $110000 

일단, 함수의 정의 부분이 바뀐 것을 볼 수 있습니다.

int slave(int master_money)

slave 가 함수 임을 알려주는 소괄호 안에 int master_money 가 써 있군요. 이는 다음과 같은 의미를 가집니다.

"나를 호출하는 코드로 부터 어떤 값을 mater_money 라는 int 형 변수에 인자(혹은 매개변수라고도 부름)로 받아들이겠다!"

허걱.. 정말 뭔소린지 알 수 없군요. 먼저 '인자' 가 무엇인지 살펴 보도록 합시다. 아까 전에 우리는 노예의 머리속에 '현재 주인이 가지고 있는 재산' 이라는 값을 어떻게 입력해야 할지가 문제라고 하였습니다. 그런데, slave 함수와 main 함수는 전혀 별개의 함수 이기 때문에 slave 함수는 main 함수 안의 변수를 사용할 수 없을 뿐더러 main 함수에서도 slave 함수의 변수들이 무엇인지 전혀 알 길이 없습니다.

하지만, 인자(argument, 혹은 매개변수(parameter) 라고 부른다) 를 이용하면 이러한 일을 가능하게 합니다. 일단, 인자는 직관적으로 봐도 알 수 있듯이 slave 함수 내에 선언이 되어 있는 변수 입니다. 이 때, 인자는 함수 정의할 때의 소괄호 안에 나타나게 되죠. 위의 경우 slave 함수는 int 형의 master_money 라는 변수를 인자로 가지고 있습니다. 이제, 이 함수를 어떠한 함수에서 호출을 한다고 합시다. 그렇다면, 이 함수를 호출 할 때, 인자에 적당한 값을 넣어 주어야 합니다. 마치 아래와 같이요.

slave(500);

이 말은 slave 함수를 호출할 때, slave 함수 안에서 정의된 master_money 라는 변수에 500 이라는값을 전달하겠다! 라는 의미입니다. 따라서, slave 함수 내부에 정의된 master_money 라는 변수에는 500 이라는 값이 들어가게 됩니다. 그렇다면 아래는 어떨까요?

slave(my_money);

이 것도 마찬가지 입니다. 이렇게 이용한다면 "slave 함수를 호출할 때, slave 함수 안에서 정의된 master_money 라는 변수에 my_money 의값을 전달하겠다!" 가 되겠지요. 만일 my_money 에 10000 이 있었더라면 slave 함수를 호출 시에 master_money 에는 10000 이 들어가게 됩니다. 결론적으로 말하자면 함수의 인자는 '함수를 호출한 것과, 함수를 서로 연결해 주는 통신 수단' 이라고 말할 수 있습니다. 이러한 연유에서 수학적인 용어로 틀린 표현 이지만 C 에선 '매개 변수' 라고 부릅니다.

그렇다면, 위의 예제를 한 번 살펴볼까요?

int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));

  return 0;
}

일단, slave 함수를 호출하는 호출자(caller) 의 코드를 살펴봅시다. printf 에서, 맨 뒤에 %d 에 들어갈 값으로 slave(my_money) 가 반환 하는 값을 넣었습니다. slave(my_money) 가 반환하는 값을 먼저 넣기 위해선 slave 함수를 호출해야 하는데 이 때 my_money 의 값이 slave 함수의 인자로 전달이 됩니다. 그러면 slave 함수는 아래의 코드를 실행하겠지요.

{
  master_money += 10000;
  return master_money;
}

즉, master_money 에 10000 을 더한 후, 그 값을 반환하게 됩니다. 따라서, 100000 에 10000 이 더해진 110000 이 출력되겠지요.

이번에는 과연 성공적으로 컴파일 될지 의문이 드는 예제를 한 번 만들어 보았습니다.

/* 될까용 */
#include <stdio.h>
int slave(int my_money) {
  my_money += 10000;
  return my_money;
}
int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));
  printf("my_money : %d", my_money);

  return 0;
}

성공적으로 컴파일 하면

실행 결과

2009.12.12 재산 : $110000 
my_money : 100000

아마도, 앞의 내용을 열심히 배우신 분들은 위 코드가 정상적으로 실행될 것이라는 것을 알고 계셨겠죠? 하지만, 그렇지 못한 분들을 위해 설명 하자면

int slave(int my_money) {
  my_money += 10000;
  return my_money;
}

slave 함수는 my_money 를 인자로 받고 있습니다. 여기서 중요한 점은 my_moneyslave 의 변수라는 것입니다. 그렇다면 slave 함수를 호출하는 부분을 볼까요.

int main() {
  int my_money = 100000;
  printf("2009.12.12 재산 : $%d \n", slave(my_money));
  printf("my_money : %d", my_money);

  return 0;
}

음, slave 함수를 호출할 때 main 함수 내부에서 선언된 my_money의 값을 slave 함수의 변수인 my_money 에 전달하고 있습니다. 즉, 각 함수 내부에서 선언된 my_money 들은 이름은 같지만 서로 다른 변수 이고, 메모리 상의 다른 위치를 점유하고 있습니다. 즉, 우리가 보기에 두 변수는 똑같은 것으로 보여도 적어도 컴퓨터가 보기에는 두 변수는 서로 다른 것들입니다.

두 번째로 주목할 점은 이 전달된다는 것입니다. 이는 아까 제가 위에서 부터 누누히 강조해 온 점이기도 한데, slave 함수를 호출할 때 slave 함수의 my_money 인자에는 값이 전달됩니다. 즉, main 함수의 my_money 의 100000 이라는 값이 slave 함수의 my_money 라는 인자에 저장되어 들어갑니다.

따라서, slave 함수에서 my_money의 값을 아무리 지지고 볶아도 main 함수의 my_money 변수에는 전혀 영향을 주지 않는다는 것이지요. 왜냐하면 slave 함수의 my_money 변수는 단지 main 함수의 my_money 와 같은 값을 가진 채로 초기화된 메모리 상의 또다른 변수 이기 때문이지요. 이건 마치

int a = b;
b++;

이라고 했는데 a 의 값이 b 와 같이 1 증가함을 바라는 것과 같습니다. 아무튼, 결과적으로 main 함수에서 두 번째 printf 문에서 main 함수의 my_money 의 값을 출력했을 때 에는 전혀 변하지 않은 100000 이 출력됩니다.

그렇다면 우리가 다른 함수의 변수의 값을 수정하고자 하는 함수를 만들고 싶다면 어떻게 해야 될까요? 우리가 앞에서 배운 내용을 생각해보면 "각 함수의 세계는 너무나 배타적이여서 각 함수는 서로에 무슨 변수가 있는지 모른다. 사실 (정확히 말하자면 각 함수의 형태(리턴형, 함수의 이름, 인자들의 형(type)) 빼고는) 서로에 대해 아는 것이 완전히 없다."

그럼, 정말로 우리는 다른 함수에서 정의된 변수의 값을 수정하는 함수는 결코 작성할 수 없는 것일까요?

답은 아니오 입니다. 놀랍게도 포인터를 이용하면 됩니다 (드디어 포인터가 쓸모 있어지나요?). 일단, 이것까지 이야기 하면 강좌가 너무 길어지므로 오늘은 이쯤에서 끝내도록 하고 어떻게 포인터로 가능할까 에 대해서 다음 강좌가 나올 때 까지 생각해봅세요.

생각해보기

문제 1

이 강좌 최상단에서 이야기 했던 마술 상자를 함수로 제작해보세요 (난이도 : 못한다면 강좌를 다시 읽어보아야 할 것입니다)

문제 2

어느날 귀족이 돈벌이가 시원치 않아져서 이전에는 일정하게 10000 달러씩 챙겼지만 이제 일정치 않은 수입을 얻게 되었습니다. 여러분은 slave 함수를 인자를 2 개를 가져서, 하나는 현재 귀족의 재산, 다른 하나는 오늘 귀족의 수입을 인자로 전달받는 새로운 함수를 만들어 보세요 (난이도 : 下)

문제 3

1 부터 n 까지의 합을 구하는 함수를 작성해보세요. 수학적인 공식을 써도 되지만 for 문으로 작성하는 것이 연습 하는데에는 도움이 될듯 합니다. (난이도 : 下 1 부터 n 까지의 합을 구하는 함수를 작성해보세요. 수학적인 공식을 써도 되지만 for 문으로 작성하는 것이 연습 하는데에는 도움이 될듯 합니다. (난이도 : 下)

문제 4

N 값을 입력 받아서 1 부터 N 까지의 소수의 개수를 출력하는 함수를 제작해보세요. (난이도 : 下)

문제 5

특정한 수 N 을 입력받아서 N 을 소인수분해한 결과가 출력되게 해보세요 (난이도 : 中)

예) factorize(10); 출력결과 : 2 × 5

factorize(180); 출력결과 : 2 × 2 × 3 × 3 × 5

문제 6

int function(int *arg) 와 같은 함수가 무엇을 뜻하는지 생각해보세요

강좌를 보다가 조금이라도 궁금한 것이나 이상한 점이 있다면 꼭 댓글을 남겨주시기 바랍니다. 그 외에도 강좌에 관련된 것이라면 어떠한 것도 질문해 주셔도 상관 없습니다. 생각해 볼 문제도 정 모르겠다면 댓글을 달아주세요.

현재 여러분이 보신 강좌는 <씹어먹는 C 언어 - <13 - 1. 마술 상자 함수(function)>> 입니다. 이번 강좌의 모든 예제들의 코드를 보지 않고 짤 수준까지 강좌를 읽어 보시기 전까지 다음 강좌로 넘어가지 말아주세요
댓글이 242 개 있습니다!
프로필 사진 없음
강좌에 관련 없이 궁금한 내용은 여기를 사용해주세요

    댓글을 불러오는 중입니다..