• [C] :: 함수와 모듈화

    2018. 1. 2. 19:59

    by. 위지원


    처음으로 배우는 C프로그래밍을 보고 공부한 내용


    함수와 모듈화가 필요한 이유


    - 시간이 지날수록 인간의 생산성과 밀접하게 관련해 있는 SW가 전체 프로젝트의 주비용으로 자리매김하고 있음

    - 하드웨어는 제조 기술의 발전과 관련이 있기때문에 가격이 급락함 ex.마이크로칩이 10년사이에 500달러에서 1달러로 저렴해짐

    - 실제 개발보단 유지보수가 오래걸리고 비용도 큼



    프로그램이 쉽게 유지 되기 위해선


    - 프로그램을 얼마나 쉽게 읽을 수 있고, 이해할 수 있는지에 달림

    - 함수 내부 구조를 잘 구성해서 모듈화를 잘하면 유지보수 때 아주 좋음



    변수의 유효 범위(Scope)


    - 함수는 아래 그림과 같은 닫힌 상자로 표현할 수 있음 ( 다른 함수들이 못보게 숨겨져있다는 캡슐화같은 사실을 강조 )

    - 범위에 따라 지역변수와 전역변수로 나뉘어짐




    - 아래 사진을 보면 firstnum은 scope가 전역이고 secnum은 지역, 결과를 보면 차이점을 알 수 있음


    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
    #include <stdio.h>
     
    int firstnum;
    void valfum();
     
    int main(){
        int secnum;
        firstnum = 10;
        secnum=20;
     
        printf("\nFrom main() : firstnum = %d",firstnum);
        printf("\nFrom main() : secnum= %d",firstnum);
     
        valfun();
     
        printf("\nFrom main() again : firstnum = %d",firstnum);
        printf("\nFrom main() again : secnum= %d",firstnum);
     
        return 0;
    }
     
    void valfun(){
        int secnum;
        secnum=30;
     
        printf("\nFrom valfun()  : firstnum = %d",firstnum);
        printf("\nFrom valfun()  : secnum= %d",firstnum);
     
        firstnum=40;
    }
     
    cs


    [ 실행 결과 ]




    - 전역 변수는 프로그래머가 함수에 의해 제공되는 일반적인 안전장치를 '넘어서게' 함

    - 일반적으로 매우 제한적이고 잘 정의된 상황을 제외하고는 전역 변수를 사용하는 것은 나쁜 습관!



    변수의 기억 장소 부류


    - 변수는 지역,전역처럼 공간 차원에 추가로 시간 차원을 갖음

    - 메모리가 변수를 위해 예약된 시간의 길이를 나타내며 기억 장소 부류( storage class )에 의해 결정 되고 이는 총 4개로 나뉘어져 변수 앞에 선언

    - auto (default) : 따로 선언하지 않으면 auto

    - static (전역 정적) : 실행시 메모리에 한번 올라감 , 한 소스파일에서만 사용할 수 있게 함

    - extern (전역 외부) : 전역 변수의 유효 범위를 전역 변수가 선언된 하나의 소스파일에서 다른 소스 파일까지 확장 시키는것이 목적

    - register



    - 지역 변수 기억 장소 부류

    - 아래 코드를 보면 testauto()함수를 호출 할때마다 num 변수가 0으로 계속해서 초기화 하고있으며 이걸 실행 시간 초기화(run-time initialization)이라고 함


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include <stdio.h>
     
    void testauto();
     
    int main(){
     
        int count;
        for(count =1; count <=3; count++)
            testauto;
     
        return 0;
     
    }
     
    void testauto(){
     
        int num= 0;
     
        printf("The value of the automatic variable num is %d\n",num);
        num++;
     
    }
    cs



    - int num을  아래와 같이 static으로 변경하면 아래와 같이 초기화 되지 않음

    static int num= 0;



    - 정적 변수는 함수 밖에 선언하느냐 함수 안에 선언하느냐로 외부,내부로 또 나뉠 수 있음

    -내부 : 지역변수 처럼 쓰는데 값이 변경되는것을 원하지 않을 때


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    void sub_func();
     
    int main(void) {
     
        sub_func();
        sub_func();
        sub_func();
     
        return 0;
    }
     
    void sub_func() {
     
        static int count = 0;
        int total = 0;
     
        count++;
        total++;
     
        printf("count = %d, total = %d\n", count, total);
    }

    cs

    -외부 : 한 파일내에 존재하는 함수끼리 같은 변수가 필요할 때


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    void sub_func();
     
    static int count = 0;
     
    int main(void) {
     
        sub_func();
        sub_func();
        sub_func();
     
        return 0;
    }
     
    void sub_func() {
     
        int total = 0;
     
        count++;
        total++;
     
        printf("count = %d, total = %d\n", count, total);
    }
    cs



    - extern,register 모두 위와 같은 결과

    - register를 제외한 나머지는 컴퓨터의 메모리 영역에 예약되고 register는 cpu에 직접 위치한 고속 기억 장소인 register에 저장

    - 일반적으로 컴퓨터 운영 체제처럼 컴퓨터의 동작과 직접 상호작용하는 특수 목적의 프로그램만 register변수를 사용하며 그외에는 거의 사용X

    - register변수를 지원하지 않거나 저장 범위를 초과하면 자동적으로 auto 기억 장소 부류로 전환

    - register 변수는 메모리가 아니라 cpu에 존재하기 때문에 주소 연산자 &을 사용하지 못함


    - register는 고속 연산에 사용된다 아래는 그 예제


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int main(void){
        //register int i;
        int i;
        clock_t startTime, endTime, result;
     
        startTime=clock();
     
        for ( i=0; i<=MAX; i++)
            printf("%d\n",i);
     
        endTime=clock();
     
        result = endTime - startTime;
     
        //printf("register class : %1f초\n",(double)result/1000);
        printf("auto class : %1f초\n",(double)result/1000);
     
        return 0;
    }
    cs


    1차


            


    2차


                    


    3차(...?)


                    


    4차(...??????????????) 뭘까...흠


            


    - 프로그램은 아래와 같이 여러 파일로 구성 될 수 있다. 이때 extern을 사용하면 다른 파일에서도 해당 변수를 사용할 수 있음

    - extern은 새로운 메모리를 할당 받는게 아니라 단순히 이미 존재하는 전역 변수들의 유효 범위만을 확장하는 것

    - extern을 추가하면 여기서도 그 변수를 사용하겠다~ 하는 식으로 확장을 시켜주는 것

    - main()에서 사용할 수 없음

    - extern 선언문에서 초기화 할 수 없음



    extern file_02부분 잘못됬다 ㅠㅠ 원래 아래것


    1
    2
    3
    4
    5
    float interest;
    Extern int price;
     
    int func3(){...}
    int func4(){...}
    cs

     

    참조에 의한 전달


    - 값에 의한 전달(pass by value) : 함수를 호출하고 값을 전달하는 방식

    - C의 독특한 장점 : 서로 다른 함수들 사이에서 어떤 변수나 전달인자가 같은 이름으로 선언 되었어도, 서로 개별적인것으로 작성되었다고 인정

    - 참조에 의한 전달(pass by reference)  : 함수로 주소를 전달 하는 방식 , 주소 연산자 & 이용

    ex. &num : num의 주소


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #include <stdio.h>
     
     
    int main(){
     
        int num;
     
        num=22;
        printf("num = %d\n",num);
        printf("The address of num is %u\n",&num);
     
        return 0;
    }
     

    cs



    - 포인터 변수 : 주소를 저장할 수 있는 변수 ex. messAddr  = &message

    - 간접 연산자 * : 포인터에 저장된 주소가 가리키고 있는 변수 ex. *messAddr은 messAddr에 저장된 주소가 가르키고 있는 변수 , messAddr이 가리키고 있는 변수

    -포인터가 가지고 있는 주소로 가면 실제 변수의 값이 존재 (아래사진처럼) , 이렇게 하면 저장 된 주소가 가르키는 변수의 저장공간(데이터형)을 알 수 있음



    - 앞에 변수형을 지정하면 해당 주소의 value 값을 말하는 것

    - *변수의 크기는 모두 같으며 (int형이든 char형이든 ...) 컴파일러에 따라만 다르다 ** os bit수가 아니라 컴파일러 설정에 따라 달라지는것임!!


    컴파일러 종류 (bit)

    변수의 크기 (byte)

    64 

    32

    16 





    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int main(){
     
       int *milesAddr;
       int miles;
     
       miles = 22;
     
       milesAddr = &miles;
     
       printf("The address stored in milesAddr is %u\n",milesAddr);
       printf("The value pointed to by milesAddr is %d\n\n",*milesAddr);
     
       *milesAddr = 158;
     
       printf("The value in miles is now %d\n",miles);
     
       return 0;
    }
     
    cs


    함수에서의 주소 전달


    - 포인터 변수만을 파라미터로 갖을 수 있는 함수를 정의하여 값이 전달되는 것을 확인


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int main(){
     
       void newval(float *);
       float testval;
     
       printf("\nEnter a number :");
       scanf("%f",&testval);
     
       printf("The address that will be passed is %u\n\n"&testval);
     
       newval(&testval);
     
       return 0;
    }
     
    void newval(float *xnum){
        printf("The address received is %u\n",xnum);
        printf("The value pointed to by xnum is : %5.2f \n",*xnum);
    }
    cs


    - 포인터 변수를 이용하여 해당 주소의 있는 value 값을 변경할 수 있음


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    int main(){
     
        void newval(float *);
        float testval;
     
        printf("\nEnter a number: ");
        scanf("%f"&testval);
     
        printf("\nFrom main(): The value in testval is : %5.2f \n",testval);
     
        newval(&testval);
     
        printf("\nFrom main(): The value in testval has been changed to : %5.2f \n", testval);
     
        return 0;
    }
     
    void newval(float *xnum){
        printf("\nFrom newval(): The value pointed to by xnum is : %5.2f \n"*xnum);
     
        *xnum = *xnum +20.2;
     
    }
    cs




    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    int main(){
     
       void calc(float,float,float,float *,float *);
       float firstnum,secnum,thirdnum,sum,product;
     
       printf("Enter three numbers: ");
       scanf("%f %f %f"&firstnum, &secnum, &thirdnum);
     
       calc(firstnum,secnum,thirdnum,&sum,&product);
     
       printf("\nThe sum of the entered numbers is : %6.2f",sum);
       printf("\nThe product of the entered numbers is : %6.2f",product);
     
       return 0;
    }
     
    void calc(float num1,float num2,float num3,float *sumaddr,float *prodaddr){
     
        *sumaddr = num1+num2+num3;
        *prodaddr = num1 * num2 * num3;
     
    }
    cs



    재귀


    - C에서는 함수가 호출될 때마다, 인자와 지역 변수들을 위해 새로운 메모리를 할당하기 때문에 자기 자신을 호출하는 것이 가능 -> self referential 또는 recursive

    - 자기 자신을 호출할때 direct recursion(직접 재귀)

    - A가 B를 호출하고 B가 다시 A를 호출하면 infirect or mutual recursion(간접,상호재귀)

    - 예를 들어 팩토리얼 4는 4*3*2*1 인데 이것을 슈도코드와 코드로 작성하면 아래와 같음


    If n =0

    factorial =1

    else

    factorial = n*factorial(n-1)


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int main(){
        int n,result;
        int factorial(int);
     
        printf("\nEnter a number :");
        scanf("%d"&n);
        result = factorial(n);
        printf("\nThe factorial of %d is %d\n",n,result);
     
        return 0;
    }
     
    int factorial(int n){
     
        if(n==0)
            return (1);
        else
            return (n * factorial(n-1));
    }


    - 아래는 함수를 호출할 때 생성되는 스택의 내용으로, 스택은 데이터를 빠르게 저장하고 가져오기 위해서 사용되는 메모리의 일부 영역

    - LIFO구조로 밑에서부터 위로 쌓임 1,2,3은 순서가 아니라 변수 n의 값





    일반 프로그래밍 및 컴파일러 오류


    프로그래밍 오류

    - 전역,지역변수의 이름을 같게 선언하는 것

    - 포인터를 무분별하게 사용하는 것

    - 함수의 파라미터를 포인터로 선언하고 파라미터를 넘길때 주소 연산자를 적지 않는 것

    - 재귀 함수를 정의 할때 함수의 종료 조건을 기술 하지 않는 것




    '2018년 > C, Java, FileSystem' 카테고리의 다른 글

    [File System] ::트라이(trie)  (0) 2018.01.22
    [File System] ::B* tree, B+ tree  (0) 2018.01.22
    [File System] ::B Tree  (0) 2018.01.22
    [File System] :: 화일의 기본개념  (0) 2018.01.15
    [C] :: 배열, 주소, 포인터  (0) 2018.01.08