티스토리 뷰



C언어에서 함수 만큼이나 중요한 요소가 있을까 싶을 정도로 C언어에서 꼭 이해가 필요한 영역입니다. 함수를 사용하기 위해서는 선언(Declaration)과 정의(Definition)가 있어야 하는데 사용할 함수가 동일한 소스 코드에 위치하는 내부 함수의 경우에는 선언부를 생략하고 정의만 하기도 하지만 선언부를 소스 코드 상단이나 헤더 파일에 기술하고 하단부에 함수의 몸체(Body)를 정의하는 방식을 주로 사용합니다.

void swap(int *i1, int *i2);

int main()
{
   int a = 10;
   int b = 20;

   swap(&a, &b);
   printf("\na = %d , b = %d", a, b);
}

void swap(int *i1, int *i2)
{
    int tmp;

    tmp = *i2;
    *i2 = *i1;
    *i1 = tmp;
}

위의 코드에서 "void swap(int *i1, int *i2);"는 함수 몸체가 없는 선언부로 함수 프로토타입(Function Prototype)이라 합니다. 함수가 같은 소스코드에 없는 경우(printf와 같은 대부분의 라이브러리 함수가 해당) 이러한 함수 프로토타입을 통해서 함수의 리턴 타입과 이름, 파라미터의 타입과 개수등을 확인할 수 있습니다. C 프로그램에서 너무도 자주 확인하는 stdio.h 헤더 파일에는 이러한 라이브러리 함수의 프로토타입과 기타 선언들이 그 내용을 이루고 있습니다. 

함수 선언(프로토타입)과 정의는 리턴 타입, 함수 이름, 파라미터의 개수와 타입은 일치해야 하지만 파라미터 이름은 일치하지 않아도 됩니다. 이와 같은 함수 선언에 따라 파라미터의 타입을 일치 시켜서 함수를 호출합니다. 위의 예제에서도 파라미터 2개가 모두 정수형 포인터를 요구하고 있으므로 호출하는 곳에서도 변수의 주소를 파라미터로 넘겨 주었습니다.

27. 다음 프로그램의 출력 결과는 무엇인가?
    #define sq(x) x*x
    int main() {
        int z = 4;
        printf("%d %d\n", sq(z), sq(z+1));
        return 0;
    }
① 16 25 ② 16 9 ③ 16 16 ④ 25 25 ⑤ ERROR

위의 문제는 실제 프로그래밍 과정에서도 자주 실수하는 부분입니다. sq(x)는 겉 모습은 함수 호출처럼 보이지만 함수가 아닙니다. sq(x)는 매크로입니다. #이 붙은 문장들을 전처리문장(Preprocessor)이라 하는데 컴파일러가 본격적으로 소스 코드에 대한 사전 처리를 수행한다해서 "전처리기"라 합니다. #define은 단순하게 상수(Constant)를 정의하기도 하지만 함수 형태로 정의해서 효율적인 코딩을 돕는 매크로 기능을 부여합니다.

함수 호출이 아니라 괄호 안에 변수를 단순히 대체하는 형태이기 때문에 위의 예제에서는 "printf("%d %d\n", sq(z), sq(z+1));"코드가 전처리 과정에서 "printf("%d %d\n", z*z, z+1*z+1);"로 대체 된다고 이해하면 됩니다. z=4이므로 대입해 보면 4*4=16과 4+1*4+1=9입니다. 두번째 값에 있는 중간에 있는 곱하기를 먼저 수행되므로 값이 9가 되는 거죠. 그런데 #define문장을 "#define sq(x) (x)*(x)"로 했다면 어떻게 될까요? "printf("%d %d\n", z*z, (z+1)*(z+1));"로 값을 풀어보면 9가 아니라 25가 됩니다. 매크로는 함수가 아닙니다.

33. f(1, 16)의 값은 무엇인가?
    int f(int a, int b) {
        int mid;
        if (a == b) {
            return 1;
        }
        else {
            mid = (a * 2 + b) / 3;
            return f(a, mid) + f(mid + 1, b) + 1;
        }
    }
① 15 ② 16 ③ 31 ④ 32 ⑤ 33

위의 문제는 스스로를 호출하는 재귀함수(Recursion Function)에 대한 이해를 묻는 문제입니다. 재귀함수를 만들 때는 반드시 그 종료점을 명확히 해야 하는데 위의 예제의 경우에는 파라미터 두개의 값이 같을때 1을 리턴하면서 재귀 함수를 끝내게 됩니다. 

f(1, 16)을 대입한 경우 콜그래프(Call Graph)의 일부는 위의 그림과 같습니다. 무언가를 코드로 옮길때 그 일의 규칙성을 찾으면 그 만큼 코드의 안정성은 높아지고 예측 가능한 결과를 가져올 수 있습니다. 위의 코드는 이진 검색과 유사한 알고리즘을 보이고 있는데 차이점이라면 모든 항목을 방문한다는 차이점이 있습니다. 조금 관찰해 보면 f 함수를 호출했을때 a와 b 값이 동일하면 1을 리턴하지만 값이 같지 않으면 재귀 호출로 이어지면서 최종 값은 항의 개수를 n이라 할때 2n -1의 결과를 가져옵니다. (1,2)는 항의 개수가 2이므로 2*2-1 = 3, (3,6)은 항의 개수가 4이므로 2*4-1 = 7 이고 (1,6)은 2*6 -1 = 11의 결과입니다. 결과적으로 (1, 16)은 2 * 16 - 1 = 31이 됩니다.


댓글
댓글쓰기 폼
«   2023/01   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함