티스토리 뷰


 

 지난번에 이어 함수에 대한 기본을 포스팅하도록 하겠습니다.

 지난번, 함수의 리턴타입이 void 타입일 때를 제외한 모든 타입이 리턴 값이 가진다고 얘기를 했습니다. 또한 반환할 수 있는 개수는 단 하나입니다. 변수 단위로 단 하나의 변수입니다. 만약 함수에서 처리되어 반환되어야 할 값이 2개 이상이라고 한다면, 포인터를 이용하여 파라미터로 넘겨서 처리를 하는 Call by reference 를 이용해야 합니다. 리턴 값이 2개 이상이 되는 경우 중 가장 흔하게 쓰이는 케이스로 Swap 함수가 있을 것입니다.

 참고로 함수의 파라미터가 포인터형이나 더블 포인터형이면 해당 파라미터는 함수내에서 값이 바뀌어 올 가능성이 매우 큽니다.

 이제 본격적으로 함수에 대해 알아보도록 하겠습니다. 

#include <stdio.h> int func(int* first, int second); int main() { int a = 10, b = 5; int res; res = func(&a, b); printf("a: %d b: %d\n", a, b); printf("res: %d\n", res); return 0; } int func(int* first, int second) { int sum = *first + second; int mul = *first * second; *first = 20; second = 10; return sum; }

 위 소스의 결과는 아래와 같습니다.

<실행 결과>

 a: 20  b:10

 res: 15

 소스코드를 분석하시는데 큰 어려움은 없으실거라고 생각됩니다. 또 지금은 함수에 대해 설명하고 있으니, 제가 만든 함수의 프로토 타입을 먼저 보고 가겠습니다.

 물론 기본 c 라이브러리에서 제공하는 함수들도 프로토 타입이 있지만, 유저가 직접 작성한 사용자 정의 함수 또한 프로토 타입이 있습니다. 3 라인에 있는 부분이 사용자 정의 함수 프로토 타입입니다.  

int func (int* first, int second)

 사실 이 함수는 테스트용으로 제작되었기 때문에 굉장히 잘 못 짜여진 함수입니다. 사용자가 함수를 작성할 때 가장 유의해야 할 점은 함수명과 파라미터가 한 눈에 알아보기 쉽게 되어 있어야 합니다. 이 함수는 func라는 이름으로 first와 second라는 파라미터를 받고 있는데, 이것만으로는 무얼 하는 함수인지 전혀 알 수 없는 상황이 됩니다.

  제가 이 함수를
       int sum (int* first, int second)
 라고 했으면 더 좋았을 것이며, 
       int SumOfTwoArgs (int* ret_value, int number)
 
라고 했으면 더 좋았을 것입니다. 소스코드를 보시면 아시겠지만 첫 번째 파라미터의 값이 바뀌므로 ret이라는 프리픽스를 붙였습니다.

 또 여기서 설명드리고 싶은 것은 로컬 변수의 무시무시함(?)입니다. 로컬 변수는 함수 func의 수행때만 존재하는 변수이고, func의 수행이 끝나는 즉시 모든 메모리와 값이 사라지게 됩니다. 여기서 로컬변수로 mul을 사용하고 있는데요. 저 mul은 func의 수행이 끝나는 시점에 아예 존재자체가 없었던 변수가 됩니다.

 물론 로컬변수가 사라진다는건 다들 알고 계실테고, 이걸로 헷갈리지 않는다라고 생각하실텐데... 잠시후 많이 혼동하실수 있는 로컬변수의 함정에 대해 말씀드리겠습니다.

 결과값을 보다시피 a는 call by reference에 의해 주소 값을 넘겨주어 처음 10에서 20으로 바뀐 것도 확인할 수 있습니다. 이렇듯 함수의 파라미터가 배열이 아닌 단일변수일 때 포인터이면, (그것이 특히 int형이라면) 거의 100% 함수내에서 값이 바뀌어 리턴되는 변수일 것입니다. (그렇지 않다라면 불필요한 포인터 참조이므로, 잘못 짠 함수가 되는 것입니다.)

 

 이제 아래는 로컬변수의 함정에 대해 작성한 코드예제입니다. 

#include <stdio.h>
#include <string.h>

char* ReverseString (char* string);

int main()
{
    char str[20] = "HelloWorld!";
    char *rev;

    printf("str in main: %s\n", str);    

    rev = ReverseString(str);

    printf("rev in main: %s\n", rev);

    return 0;
}

char* ReverseString (char* string)
{
    char reverse[20]={0,};
    int i, j, len = strlen(string);

    for (i=0,j=len-1; i<len; i++,j--)
    {   
        reverse[j] = string[i];
    }   

    printf("rev in ReverseString: %s\n",reverse);

    return reverse;
} 

<실행 결과>

 왜 이렇게 됐을까요?

 !

d

l

\0 

 

 

 

 

 

 

 

 


↑ reverse[0]의 주소                             (ReverseString 내부)

 

 reverse라는 포인터에 문자열을 뒤집어서 저장을 하고 reverse를 반환했는데, 어째서 돌아온 값에는 쓰레기값이 들어있을까요?

 이유는 간단합니다. reverse는 로컬 포인터 변수이기때문에 함수의 수행이 끝남과 동시에 존재 자체가 없어집니다. 이때 리턴되는 값은 위의 표에서 보듯 reverse[0]의 주소값을 '복사'해서 리턴하는 것입니다. 이 주소값이 메인으로 돌아왔을 때는 물론 같은 주소를 가리키고는 있지만, 안에 있는 값들은 모두 사라진 이후가 되는겁니다.

 이럴때에 해결방법은 reverse를 포인터형으로 선언하여 malloc 함수를 통해 메모리를 할당하여, 해당 메모리에 값을 채우는 것입니다. 그리고 리턴되는 값은 할당한 메모리의 시작주소가 되는것이지요.

    char *reverse = malloc(sizeof(char) * 20);
    int i, j, len = strlen(string);

 위와 같이 말입니다. malloc으로 할당 된 메모리는 free 하지 않는 한 사라지지 않는 영역이기 때문에, 함수의 수행이 종료된 이후에도 값을 유지하게됩니다.

 * 이 포인터와 배열, 메모리의 관계에 대해서는 차후 c언어 연구소에서 다루고자 하는 내용입니다. 이 내용들이 잘 이해가 가지 않으신다면 차후 좀 더 쉽게 설명할 포인터,배열에 대한 포스팅을 참조하시기 바랍니다.

 

 이제 c언어 라이브러리에 있는 함수들 중에 몇 가지 프로토타입을 보며 분석해보는 시간을 갖겠습니다.

1. strcpy

char *strcpy (char *dest, const char *src);

  •  함수명    : strcpy
  •  리턴타입 : char *
  •  파라미터 : 복사될 문자열 포인터, 복사할 문자열 포인터
  •  리턴값    : dset
  •  함수기능 : src의 문자열을 dest로 복사한다.

  => 이렇게 좋은 함수의 프로토 타입은 이름들이 직관적입니다. 복사 될 문자열 포인터 변수와 복사할 문자열을 넘겨주면, 리턴 값으로 dest의 시작주소가 리턴됩니다. 물론 이 경우, 첫 번째 파라미터 dest 역시 주소값을 넘겨주어 call by reference로도 사용 될 수 있으므로, strcpy를 이용하는 방법은 다음 두 가지가 될수 있습니다.

<사용법>

    char * string;
    strcpy (string, "Hello");
    char * string;
    string = strcpy (string, "Hello");

 

 * 두 방법은 같은 결과를 가지게 됩니다.

2. malloc

voidmalloc(size_t size);

  •  함수명    : malloc
  •  리턴타입 : void *
  •  파라미터 : 메모리를 할당할 사이즈
  •  리턴값    : 할당된 메로리의 시작 주소
  •  함수역할 : 파라미터로 넘겨받은 사이즈만큼의 sequential한 메모리를 할당해준다.

  => 참고로 void * 라는 것은, 특정 타입(int, char 등)의 포인터로 미리 정해둔 것이 아니라 그냥 빈 메모리형태라는 의미입니다. 따라서 int형 포인터든 float형 포인터든 원하는 타입으로 형변환하여 사용할 수 있습니다. 우리는 함수내부가 어떻게 구성되었는지는 알 필요가 없습니다. 이름에서 보다시피 m alloc. (memory allocate) 즉, 메모리 할당함수이므로, malloc(size)라는 사용법만 알아도 사용할 수 있는 것입니다.

<사용법>


    char *string;
    string = (char *) malloc (sizeof(char) * 30);

 

 이렇게 함수에 대한 기본과, 프로토 타입을 직접 분석하는 시간을 가져보았는데요. 아직 많이 부족하지만, 점차 컨텐츠를 축적시켜 나가면서 참고할 수 있는 많은 자료들을 업로드 할 수 있도록 하겠습니다.

 이것으로 함수에 대한 기본적인 설명에 대해서는 마무리를 짓고요. 필요한 설명이 더 필요하다고 하면 추가로 포스팅을 할 수 있도록 하겠습니다.

 


* 용어 정리

  • 리턴 = 반환
  • 리턴타입 = 반환형
  • 리턴값 = 반환값
  • call by reference = 참조에 의한 호출
  • 프리픽스 (prefix) = 접두어
  • 프로토타입 = 원형
  • 로컬변수 = 지역변수


* 참조

 

 

신고
크리에이티브 커먼즈 라이선스
Creative Commons License

'C, C++ > C, C++' 카테고리의 다른 글

c/c++ sprintf, snprintf 함수  (3) 2014.01.21
C언어 qsort() 함수  (3) 2014.01.02
배열의 개수를 세는 _countof 매크로  (0) 2013.12.31
c언어 - 함수포인터 (Function pointer)  (12) 2013.12.30
c언어 함수에 대한 기본 (2)  (0) 2013.06.14
c언어 함수에 대한 기본 (1)  (0) 2013.06.11

댓글
댓글쓰기 폼