7. Functions

앞서 C가 제공하는 조건부 실행의 방법을 알아보았다. 여기서는 비조건부 실행의 방법, 주로 함수에 대해 알아볼 것이다.

함수를 쓰는 이유는 모듈화코드 분리에 있다.

  • 중복 코드를 줄이기 위해서.
  • 컴파일 시간을 줄이기 위해서.
  • 미래의 코드 재사용성을 쉽게 하기 위해서.
  • 깔끔한 인터페이스를 위해.
  • 중간 변수들을 사용하는 알고리즘을 표현하는 자연스러운 방법을 위해.

함수 외에도 C에서 제공하는 비조건부 실행은 다음이 있다.

  • exit, _Exit, quick_exit, abort : 프로그램 종료
  • goto : 함수 몸체 내에서 제어 흐름 변경
  • setjmp, longjmp : 호출부 쪽으로 비조건부 리턴
  • 시그널 핸들러 raise를 통한 시그널 통신

7.1. Simple functions.

함수 선언부와 정의부에서 ()는 인자 목록을 묶는다. 모든 함수는 프로토타입이 있어야 한다. 프로토타입에는 저장소 한정자 extern을 붙일 수 있고, 인자 이름을 생략할 수 있다. 인자가 없거나 리턴값이 없는 경우엔 void를 쓴다. 함수를 호출할 때 전달하는 인자가 프로토타입에서 받는 인자와 타입이 맞지 않더라도 묵시적으로 형변환이 가능하다면 그 인자는 형변환되어서 전달된다.

Takeaway 1.7.1.1. 모든 함수는 프로토타입이 있어야 한다.

이에 대한 예외는 가변 인자 리스트를 받는 printf 같은 함수들이다.

Takeaway 1.7.1.2. 함수는 진입 지점은 하나뿐이지만, return 문은 여러 개 가질 수 있다.

Takeaway 1.7.1.3. 함수의 return 문은 리턴 타입과 맞아야 한다.

Takeaway 1.7.1.4. 함수의 { } 블록의 끝에 도달하는 것은 표현식 없이 return하는 것과 같다.

Takeaway 1.7.1.5. 함수의 { } 블록의 끝에 도달하는 것은 void 함수에만 허용된다.

7.2. main is special.

main 함수는 모든 C 프로그램의 진입점이다. 두 가지의 프로토타입이 존재하며 둘 중 하나는 구현되어야 한다.

int main(void);
int main(int argc, char* argv[argc + 1]);

다른 인터페이스를 제공할 수도 있지만 포터블한 프로그램을 만들기 위해서는 그렇게 하지 말자.

Takeaway 1.7.2.1. main의 리턴값으로 EXIT_SUCCESS나 EXIT_FAILURE를 쓰자.

main 함수는 리턴값을 가질 필요가 없다.

Takeaway 1.7.2.2. main 함수의 끝에 도달하는 것은 return EXIT_SUCCESS;와 동일하다.

exit 함수는 프로그램을 종료한다.

_Noreturn void exit(int status);

Takeaway 1.7.2.3. exit(status) 호출은 main 함수에서 return status; 를 호출하는 것과 같다.

Takeaway 1.7.2.4. exit 함수는 결코 실패하지 않으며 호출자에게 리턴하지 않는다.

_Noreturn (noreturn)은 리턴하지 않는 함수에 붙인다.

Takeaway 1.7.2.5. 모든 커맨드 라인 인자는 문자열로 변환된다.

Takeaway 1.7.2.6. main의 인자 중 argv[0]은 프로그램 실행에 쓰인다.

보통 프로그램 이름이 쓰이나 규정된 내용은 아니다.

Takeaway 1.7.2.7. main의 인자 중 argv[argc]는 0이다.

7.3. Recursion.

직간접적으로 그 자신을 호출하는 함수를 재귀함수라 하고, 이를 재귀라 한다.

예제로 최대공약수를 구하는 함수는 다음과 같이 쓸 수 있다.

size_t gcd2(size_t a, size_t b) {
    assert (a <= b);
    if (!a) return b;
    size_t rem = b % a;
    return gcd2(rem, a);
}

Takeaway 1.7.3.1. 함수의 모든 사전 조건을 명시하라.

Takeaway 1.7.3.2. 재귀 함수에서는, 종료 조건을 먼저 체크하라.

이를 빼먹으면 무한 재귀함수가 된다.

최대공약수를 구하는 위의 함수는 b가 a 이상이어야 한다는 조건이 문제이다. 이를 위해 간단한 래퍼 함수를 쓸 수 있다.

size_t gcd(size_t a, size_t b) {
    assert (a);
    assert (b);
    if (a < b) return gcd2(a, b);
    else return gcd2(b, a);
}

Takeaway 1.7.3.3. 재귀함수의 사전 조건을 래퍼 함수에서 보장하라.

다른 재귀 함수의 예는 피보나치 함수이다.

size_t fib(size_t n) {
    if (n < 3) return 1;
    else return fib(n - 1) + fib(n - 2);
}

이 함수의 수행 시간은 지수함수이다.

Takeaway 1.7.3.4. 중복 재귀는 지수적 수행 시간을 일으킬 수 있다.

미리 계산된 모든 값들을 저장해둬서 중복된 계산을 피할 수 있다. 이것만으로 알고리즘의 수행 시간은 선형으로 바뀐다.

size_t fibCacheRec(size_t n, size_t cache[n]) {
    if (!cache[n - 1]) {
        cache[n - 1] = fibCacheRec(n - 1, cache) + fibCacheRec(n - 2, cache);
    }
    return cache[n - 1];
}

size_t fibCache(size_t n) {
    if (n < 3) return 1;
    size_t cache[n];
    cache[0] = 1;
    cache[1] = 1;
    for (size_t i = 2; i < n; ++i) {
        cache[i] = 0;
    }
    return fibCacheRec(n, cache);
}

Takeaway 1.7.3.5. 나쁜 알고리즘은 잘 동작하는 구현을 만들 수 없다.

Takeaway 1.7.3.6. 알고리즘을 개선하는 것은 동작 성능을 극적으로 바꾼다.

요점 정리

  • 함수는 그것이 어떻게 호출될지를 결정하는 프로토타입을 갖는다.
  • main 함수의 종료와 exit 함수의 호출은 같다.
  • 함수 호출 각각은 각각의 지역 변수를 가지며 재귀적으로 호출될 수 있다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중