본문 바로가기

c

C Functions #2

저장 클래스

  • C 언어에서는 변수와 함수의 이름(식별자)에 대해 저장 클래스라는 속성이 있으며, 이는 저장 기간, 범위, 그리고 연결성을 결정.
  • 저장 클래스는 프로그램 내에서 식별자의 존재 기간(저장 기간), 참조 가능 위치(범위), 그리고 연결성을 정의.

저장 클래스 지정자

  • C에서는 네 가지 저장 클래스 지정자를 제공 : auto, register, extern, static.
    • 자동 저장 기간(auto): 기본적으로 함수의 지역 변수는 자동 저장 기간을 가지며, 함수 블록에 진입할 때 생성되고, 종료할 때 파괴됩니다.
    • 정적 저장 기간(static, extern): 정적 저장 기간을 가진 변수와 함수는 프로그램이 실행되는 동안 지속됩니다.
      • static: 정적 변수는 정의된 블록 내에서만 참조 가능하지만, 함수 종료 후에도 값을 유지합니다.
      • extern: 전역 변수와 함수에 적용되며, 프로그램 내 다른 파일에서도 참조할 수 있도록 합니다.

3. 저장 기간

  • 자동 저장 기간: 변수는 블록에 진입할 때 생성되고, 블록이 종료될 때 파괴됩니다. 일반적으로 지역 변수는 자동 저장 기간을 가집니다.
  • 정적 저장 기간: 프로그램이 실행되는 동안 계속 유지됩니다. 전역 변수, 함수, 그리고 static 키워드로 선언된 지역 변수가 이에 해당합니다.

4. 범위

  • 함수 범위: 함수 내에서만 사용 가능한 식별자 범위입니다. 예: 레이블.
  • 파일 범위: 함수 외부에서 선언된 식별자는 파일 끝까지 참조 가능합니다. 예: 전역 변수, 함수 정의.
  • 블록 범위: 블록 내에서만 참조 가능한 식별자 범위입니다. 예: 지역 변수.
  • 프로토타입 범위: 함수 프로토타입의 매개변수 목록에서만 사용되는 범위입니다.

5. 연결성

  • 연결성은 식별자가 다중 소스 파일 프로그램에서 어떻게 접근될 수 있는지를 결정합니다. 예를 들어, extern 키워드를 사용하면 다른 파일에서도 해당 식별자를 사용할 수 있습니다.

6. 소프트웨어 공학적 권장 사항

  • 지역 변수는 함수 내에서만 사용되어야 하며, 전역 변수는 특별한 경우를 제외하고는 피해야 합니다.
  • 외부 블록의 변수 이름을 내부 블록에서 덮어쓰지 않도록 주의해야 합니다.
// Fig. 5.16: fig 05_16.c
// Scoping. 범위 설정.
#include <stdio.h>

void useLocal(void); // function prototype
void useStaticLocal(void); // function prototype
void useGlobal(void); // function prototype

int x = 1; // global variable, 전역 변수

int main(void)
{
    int x = 5; // local variable to main, main 의 지역변수

    printf("local x in outer scoe of main is %d\n", x);

    { // start new scope, 새로운 범위 시작
        int x = 7; // local variable to new scope , 새로운 범위의 지역 변수

        printf("local x in inner scope of main is %d\n", x);
    } // end new scope , 새로운 범위 종료

    printf("local x in outer scope of main is %d\n", x);

    useLocal(); // useLocal has automatic local x
    // useLocal 은 자동 지역 x 를 사용한다.
    useStaticLocal(); // useStaticLocal has static local x
    // useStaticLocal 은 정적 지역 x를 가진다.
    useGlobal(); // useGlobal uses global x
    // useGlobal 은 전역 x를 사용한다.
    useLocal(); // useLocal reintializes automatic local x
    // useLocal 은 자동 지역 x를 재초기화한다.
    useStaticLocal(); // static local x retains its prior value
    // 정적 지역 x는 이전 값을 유지한다.
    useGlobal(); // global x also retians its value
    // 전역 x도 값을 유지한다.

    printf("\nlocal x in main is %d\n", x);
}

// useLocal reinitializes local variable x during each call
// useLocal 은 각 호출시, 지역 변수 x를 재초기화한다.
void useLocal(void)
{
    int x = 25; // initialized each time useLocal is called
    // useLocal 이 호출 될 때 마다 초기화 된다.

    printf("\nLocal x in useLocal is %d after entering useLocal\n", x);
    ++x;
    printf("local x in useLocal is %d before exiting useLocal\n", x);
}

// useStaticLocal initializes static local variable x only the first time
// the function is called; value of x is saved between calls to this
// function

// useStaticLocal 은 함수가 처음 호출될 때만 정적 지역 변수 x를 초기화한다.
// 함수가 호출될 때마다 x의 값이 저장된다.
void useStaticLocal(void)
{
    // initialized once
    static int x = 50;

    printf("\nlocal static x is %d on entering useStaticLocal\n", x);
    ++x;
    printf("local static x is %d on exiting useStaticLocal\n", x);
}

// function useGlobal modifies global variable x during each call
void useGlobal(void)
{
    printf("\nglobal x is %d on entering useGlobal\n", x);
    x *= 10;
    printf("global x is %d on exiting useGlobal\n", x);
}
local x in outer scoe of main is 5
local x in inner scope of main is 7
local x in outer scope of main is 5

Local x in useLocal is 25 after entering useLocal
local x in useLocal is 26 before exiting useLocal

local static x is 50 on entering useStaticLocal
local static x is 51 on exiting useStaticLocal

global x is 1 on entering useGlobal
global x is 10 on exiting useGlobal

Local x in useLocal is 25 after entering useLocal
local x in useLocal is 26 before exiting useLocal

local static x is 51 on entering useStaticLocal
local static x is 52 on exiting useStaticLocal

global x is 10 on entering useGlobal
global x is 100 on exiting useGlobal

local x in main is 5
메인 외부 범위의 지역 x는 5입니다
메인 내부 범위의 지역 x는 7입니다
메인 외부 범위의 지역 x는 5입니다

useLocal에 들어갈 때 지역 x는 25입니다
useLocal에서 나갈 때 지역 x는 26입니다

지역 정적 x는 useStaticLocal에 들어갈 때 50입니다
지역 정적 x는 useStaticLocal에서 나갈 때 51입니다

global x는 useGlobal에 들어갈 때 1입니다
global x는 useGlobal에서 나갈 때 10입니다

useLocal에 들어갈 때 지역 x는 25입니다
useLocal에서 나갈 때 지역 x는 26입니다

지역 정적 x는 useStaticLocal에 들어갈 때 51입니다
지역 정적 x는 useStaticLocal에서 나갈 때 52입니다

global x는 useGlobal에 들어갈 때 10입니다 
global x는 useGlobal에서 나갈 때 100입니다

메인에서 지역 x는 5입니다

재귀 :  함수가 자신을 직접 또는 간접적으로 호출하여 문제를 해결하는 프로그래밍 기법

 

재귀 함수는 기본 사례를 처리하고, 복잡한 문제는 두 부분으로 나눠 해결한다.

함수는 해결할 수 있는 부분과 더 작은 버전의 문제를 재귀적으로 처리하는 부분으로 나누며, 이 과정을 반복하여 문제를 점차 단순화한다. 결국, 모든 재귀 호출이 기본 사례로 수렴하고, 최종적으로 원래 문제의 해답을 얻을 수 있다.

 

// Fig. 5.18: fig 05_18.c
// Recursive factorial function.
#include <stdio.h>

unsigned long long int factorial(unsigned int number);

int main(void)
{
    // during each iteration, calculate
    // factorial(i) and display result
    // 각 반복에서, 팩토리얼을 계산하고 결과를 표시
    for (unsigned int i = 0; i <= 21; ++i) {
        printf("%u != %llu\n", i, factorial(i));
    }
}

// recursive definition of fuction factorial
// 함수 팩토리얼의 재귀적 정의
unsigned long long int factorial(unsigned int number)
{
    // base case
    if (number <= 1) {
        return 1;
    }
    else { // recursive step, 재귀 단계
        return (number * factorial(number - 1));
    }
}
0 != 1
1 != 1
2 != 2
3 != 6
4 != 24
5 != 120
6 != 720
7 != 5040
8 != 40320
9 != 362880
10 != 3628800
11 != 39916800
12 != 479001600
13 != 6227020800
14 != 87178291200
15 != 1307674368000
16 != 20922789888000
17 != 355687428096000
18 != 6402373705728000
19 != 121645100408832000
20 != 2432902008176640000
21 != 14197454024290336768

 

  • 재귀 호출 단계: 21! -> 20! -> 19! -> ... -> 2! -> 1!
  • 반환 단계: 1! -> 2! -> 3! -> ... -> 20! -> 21!

부호 없는 long long int를 사용하더라도,  21을 넘는 팩토리얼을 계산할 수 없다.

// Fig. 5.19: fig05_19.c
// Recursive fibonacci function, 재귀 피보나치 함수
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

unsigned long long int fibonacci(unsigned int n); // function prototype

int main(void)
{
    unsigned int number; // number input by user

    // obtain integer from user
    printf("%s", "Enter an integer: ");
    scanf("%u", &number);

    // calculate fibonacci value for number input by number
    // 사용자 입력 숫자에 대한 피보나치 값 계산
    unsigned long long int result = fibonacci(number);

    // display result
    printf("Fibonacci(%u) = %llu\n", number, result);
}

// Recursive definition of function fibonacci
// 피보나치 함수의 재귀 정의
unsigned long long int fibonacci(unsigned int n)
{
    // base case
    if (0 == n || 1 == n) {
        return n;
    }
    else { // recursive step
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

 

Enter an integer: 0
Fibonacci(0) = 0
Enter an integer: 1
Fibonacci(1) = 1
Enter an integer: 2
Fibonacci(2) = 1
Enter an integer: 3
Fibonacci(3) = 2
Enter an integer: 10
Fibonacci(10) = 55
Enter an integer: 20
Fibonacci(20) = 6765
Enter an integer: 30
Fibonacci(30) = 832040
Enter an integer: 40
Fibonacci(40) = 102334155


 

  • 연산자 피연산자 평가 순서: C 언어에서 연산자의 피연산자가 평가되는 순서는 연산자 우선순위와 결합 규칙에 따라 결정되며, 이는 컴파일러에 따라 달라질 수 있다. 예를 들어, &&, ||, ? :, 쉼표( , ) 연산자의 피연산자는 왼쪽에서 오른쪽으로 평가되지만, 다른 연산자의 경우 평가 순서는 명시되지 않아 예측하기 어렵다. 따라서 이러한 순서에 의존하는 프로그램은 이식성 문제가 발생할 수 있습니다.

 

  • 피보나치 수 계산의 지수 복잡성: 피보나치 수를 재귀적으로 계산할 때, 각 재귀 호출이 기하급수적으로 증가하여 지수 복잡성을 가지게 됩니다. 예를 들어, 20번째 피보나치 수를 계산하려면 약 100만 번, 30번째 피보나치 수를 계산하려면 약 10억 번의 재귀 호출이 필요합니다. 이는 성능에 큰 부담을 주며, 더 나은 알고리즘이 필요할 수 있습니다.

 

  • 재귀 대 반복: 재귀와 반복은 각각의 장단점이 있으며, 재귀는 더 자연스럽고 이해하기 쉬운 프로그램을 작성하는 데 유리할 수 있습니다. 그러나 재귀는 함수 호출 오버헤드와 메모리 사용량이 많아 성능에 부정적인 영향을 줄 수 있습니다. 반복은 일반적으로 성능 면에서 더 효율적입니다.

 

  • 재귀의 단점: 재귀는 반복적으로 함수 호출을 발생시켜 오버헤드가 크고, 메모리 소모도 큽니다. 하지만, 문제를 자연스럽게 해결하고 이해하기 쉬운 프로그램을 작성하는 경우에는 재귀가 선호될 수 있습니다.

 

'c' 카테고리의 다른 글

C data structure #1  (0) 2024.08.20
C File Processing #2  (0) 2024.08.15
C File Processing #1  (0) 2024.08.15
C Formatted Exercises  (0) 2024.08.14
C Formatted Input/Output #2  (0) 2024.08.14