C언어 함수 선언 - ceon-eo hamsu seon-eon

함수(function)란?

- 함수란 영어 function에서 온 말인데 이 뜻은 "기능"을 뜻한다. 

즉, 기능을 구현하는 부분을 따로 떼어 구현하는 것으로, 구조화 프로그램의 중요한 개념이라 하겠다.

함수의 기본 형태

 함수의 형태는 우선 우리에게 친숙한 main 함수를 예로 들어보겠다.

C언어 함수 선언 - ceon-eo hamsu seon-eon

함수는 윗 그림과 같이 반환 자료형과 함수이름, 인수목록으로 나뉜다.

반환 자료형이란 메인함수의 사용에서 return 0; 부분을 보면 0을 반환한다는 말인데

정수형을 반환하므로 여기서 int로 쓰였다.

그리고 함수 이름은 main 함수의 경우 프로그램 실행시 가장 먼저 실행되는 부분이므로

꼭 필요한 함수로 변경할 수 없지만 사용자 정의 함수의 경우에는 

일반적인 명명규칙에 따라 사용자가 지정해줄 수 있다.

 인수 목록은 main 함수에는 보통 아무것도 쓰지 않지만, 사실 괄호안의 void가 생략된 형태다.

.cpp파일로 저장하고 컴파일을 해보면 warning이 뜨는 것을 확인할 수 있다.

그리고 함수 몸체부분에서는 프로그래머가 구현하고 싶은 것을 구현하면 된다.

함수의 종류

함수의 종류에는 사용자 정의 함수와 라이브러리 함수가 있다.

① 사용자 정의 함수

 사용자가 구현하고 싶은 기능을 구현하는 것이다. 

변수의 경우처럼 명명규칙(언더바_나 알파벳으로 시작 등등..)에 따라 함수의 특성을 살린 이름을 지으면 된다.

예) add(), printScreen(), multiple()

② 라이브러리 함수

우리가 흔히 써오던 printf(), scanf() 같은 함수인데 이 두 함수는 stdio.h파일 안에 정의되어있다.

앞으로 C언어를 배우면서 더 많은 라이브러리 함수를 배울 것이다.

함수의 사용법

함수를 사용하려면 변수와 처럼 main()위에 선언을 해 주어야 한다.

선언과 동시에 구현을 해주어도 된다.

윗 그림의 두 경우 모두 가능하다는 것이다.

함수의 호출

함수를 선언하고 구현하였다면 호출을 해야하는데

호출하면 다음과 같은 과정이 이루어진다.

① 번이 함수의 호출 부분인데 라인 3에 정의 된 대로 int형 변수 둘을 받을 수 있다.

인수의 개수는 정의와 항상 같아야 하고, 인수의 자료형은 틀려도 에러는 발생하지 않지만 

정확한 계산값을 원한다면 정의 부분의 자료형과 일치시켜줘야한다.

먼저 함수의 정의와 호출부의 형태가 일치하는지 확인한 후 제어권은 호출한 함수로 옮겨진다.

② 함수의 제어권이  add()함수로 옮겨가면 전달받은 인수의 대입이 이루어진다.

num1과 num2는 차례대로 3과 4의 값을 가지는데, 인수 a = 3; b = 4;가 이루어지는 것이다.

③ 인수를 받아왔으면 이제 함수의 몸체로 가서 프로그래머가 구현한 작업을 하게 되는데

이 함수의 경우 따로 구현한 부분이 없으므로 return문으로 가게 된다.

a+b의 값을 리턴하는 것이다. 즉, 3+4 = 7을 반환하는 것이다. 이 반환하는 값이정수형이므로

int add(...) 라고 정의되어야 하는 것이다.

④ 제어권이 다시 main()함수로 돌아오면서 add(num1, num2)부분은 반환값인 7로 바뀌어져서

result = 7; 의 형태로 대입이 이루어진다.

안녕하세요 피터입니다.

오늘은 C언어의 함수 (Function)에 대해 알려드리겠습니다.

개요

수학에서는 함수 이런식으로 표기합니다.

y = f(x)

여기에서 x 는 함수의 입력값이 되고, y출력값이 됩니다.

C언어에서 함수(Function)도 이와 유사하다고 볼 수 있습니다.

입력값을 가질 수 있으며, 이에 대응되는 값을 출력할 수 있습니다.

문법(Syntax)

함수 선언 (Function Declaration)

함수는 다음과 같이 선언할 수 있습니다.

DataType FunctionName(DataType, ...);

위와 같은 문장(문장이기 때문에 ; 세미콜론으로 끝납니다)을 함수 선언 또는 함수 원형이라고 합니다.

맨 앞쪽에 있는 DataTypeReturnType이며 함수의 출력값입니다.

FunctionName에서 함수 이름을 지정할 수 있으며, 함수 이름 뒤의 ( ) 안에는 0개 이상매개변수(Parameter)를 넣을 수 있습니다.

각각의 매개변수들은 , (콤마)로 구분하며 개수의 제한은 없습니다.

다만 너무 많은 매개변수를 넣으면 사용하기가 힘들겠죠.

함수 이름을 정하는 것은 변수 이름을 정하는 것 만큼이나 굉장히 중요합니다.

이름은 고유한 함수의 식별자로 방대한 소스코드 내에서 코드의 흐름(Flow)를 추적하는데 있어서 이정표와 같은 역할을 하기 때문입니다.

때문에 함수 이름이나 변수 이름을 대충 지었다가는 여러분의 코드를 보는 사람들로 하여금 코드의 미로에 갇혀 헤매이게 만들지도 모릅니다.

그러니 함수 이름을 정할 때는 의미가 잘 전달될 수 있도록 신중하게 결정해주시기 바랍니다.

올바르게 함수명과 변수명을 짓는 방법에 대해서는 추후에 포스팅하겠습니다.

함수 선언문 내에는 실제로 동작에 관련된 코드가 들어있지 않기 때문에 함수를 선언(Declaration)했다면 반드시 함수를 정의(Definition)해야합니다.

즉, 선언과 정의가 쌍을 이뤄야 합니다.

선언과 정의를 함수의 헤더(Header)바디(Body)라고도 부릅니다.

선언만 하고 정의를 않은 상태에서 해당 함수를 호출하게 되면 컴파일러에서 Link Error를 선물해줄겁니다.

이렇게 헤더와 바디를 구분해놓는 이유는 라이브러리의 경우 실제 동작 코드는 컴파일이 완료된 lib 파일이나 dll 파일 안에 존재하는데, 함수를 호출하기 위해서는 먼저 컴파일러(Compiler)에게 함수 원형을 알려줘야 합니다.

때문에 라이브러리 함수를 호출하기 전에 해당하는 함수의 원형을 선언해줘야 하는데 라이브러리에 포함된 함수가 한두개가 아니기 때문에 일일히 적기가 곤란합니다.

그래서 함수 원형만 따로 모아놓은 파일이 .h 확장자를 갖는 헤더파일(Header file)이고 이 파일만 #include로 불러오면 라이브러리의 함수를 모두 호출할 수 있게 되는 것입니다.

#include<stdio.h>

위 문장은 printf, scanf 등의 표준 입출력 함수들이 구현되어 있는 Standard Input / Output 라이브러리를 사용하기 위해 표준입출력 헤더파일을 포함한다는 의미입니다.

경우에 따라서는 함수의 선언과 동시에 구현을 하는 경우도 있습니다. 이러한 함수를 인라인 함수(Inline Function)이라고 합니다.

하지만 특별한 경우를 제외하고는 함수의 선언과 정의을 분리해서 사용하는게 좋습니다.

함수 정의

함수의 이름과 입/출력값을 지정하여 선언하였다면 이제 함수를 정의할 차례입니다. 다른 표현으로 함수를 구현한다고도 합니다.

DataType FunctionName(DataType, ...)

{

    // do something

    return value;

}

위와 같이 함수 원형 뒤에 { } 블럭에 실제 함수에서 수행할 코드를 작성해주면 됩니다.

함수 선언시에는 각 매개변수의 데이터 타입만 명시하고 이름을 생략할 수 있지만 함수 정의에서는 매개변수의 데이터 타입과 이름을 모두 명시해야 합니다.


// Declaration
int sum(int, int);

// Definition
int sum(int a, int b)
{
  return a + b;
}

위 예제는 두 개의 숫자를 매개변수로 입력받아서 두 숫자의 합을 반환하는 함수입니다.

이처럼 return 값은 특정 변수만 지정할 수 있는 것이 아니라 수식을 넣을 수도 있습니다. 그렇게 되면 수식식의 최종 결과값이 반환되게 되는 것이죠.

함수 호출

함수 정의까지 마쳤다면 이제 원하는 시점에 함수를 호출(Call) 할 수 있습니다.

아래와 같이 선언했던 함수명을 적고 ( ) 안에 매개변수에 들어갈 변수를 넣어주기만 하면 됩니다.

c = sum(a, b);

그러면 sum 함수로 프로그램이 점프(Jump)해서 실행을 하다가 return 을 만나면 다시 돌아오는 것이죠.

{ } 로 감싸여진 블록 마다 별도의 스택(Stack) 영역이 생긴다고 전에 언급 드린 적이 있었죠.

함수에서도 마찬가지입니다.

sum 함수가 호출되는 순간 sum 함수의 스택이 생성되고 스택 영역의 지역변수(Local variable)로 a와 b가

새롭게 할당

됩니다.

그리고 main 스택에서 매개변수로 넣었던 a, b 변수의 값이 복사(Copy)되는데 이 부분이 중요합니다.

변수의 이름은 같지만 서로 다른 스택 영역에 존재하기 때문에 sum 함수에서 a, b 변수의 값을 아무리 변경하여도 main 스택 영역의 변수들의 값은 그대로입니다.

sum 함수의 } 부분에서 스택이 정리되면서 sum 함수 내에 할당되었던 변수 a, b는 소멸됩니다.

함수의 처리 결과를 main 스택에서 얻기 위해서는 반환값(return value)을 받는 수밖에 없습니다.

그런데 이 반환값는 한 개밖에 선언할 수가 없죠?

하지만 프로그램을 개발하다 보면 반환값이 여러 개가 필요한 경우가 종종 발생합니다. 이럴 경우에는 어떻게 해결해야 될까요?

첫 번째로는 구조체(struct)를 이용하는 방법이 있습니다.

반환값을 여러 개의 데이터 타입으로 구성된 구조체로 선언하면 원하는 만큼 다양한 결과값을 묶어서 반환할 수 있습니다.

구조체에 관련된 내용은 나중에 자세히 말씀드리기로 하고 오늘은 두 번째 방법을 소개해드리겠습니다.

바로 앞서 배웠던 포인터(pointer)를 이용해서 해결하는 방법입니다.

함수는 매개변수로 값을 그대로 넘기냐, 값에 대한 참조를 넘기냐에 따라서 다음과 같이 두 가지로 구분할 수 있습니다.

  • Call by value
  • Call by reference

위에 있는 sum 함수 예제는 전형적인 Call by value의 예입니다.

Call by reference를 구현하기 위해서는 매개변수포인터 변수로 선언해야 합니다.

sum 함수 예제를  Call by reference로 바꿔보겠습니다.


// Declaration
void sum(int, int, int*);

void main(void)
{
  int a,b,c;
  a = 10, b = 15, c = 0;
  sum(a, b, &c);
  printf("a + b = %d\n", c);
}

// Definition
void sum(int a, int b, int* c)
{
  *c = a + b;
}

int* 타입의 매개변수 c 를 선언하고 sum 함수를 호출할 때 변수 c주소를 넘깁니다.

그렇게 하면 매개변수 c에는 main 스택에 존재하는 변수 c 의 주소가 복사되겠죠.

결과적으로 sum 함수 실행 시 *c 와 같이 * 연산자를 이용해서 해당 주소의 실제 값을 변경하면 main 스택에 있는 변수 c 의 값이 변경되게 됩니다.

이러한 원리를 이용하면 매개변수의 개수에는 제한이 없기 때문에 원하는 만큼의 반환값을 함수로부터 받아올 수가 있습니다.

-Peter의 우아한 프로그래밍

여러분의 공감과 댓글은 저에게 크나큰 힘이 됩니다. 오류 및 의견 주시면 감사하겠습니다.