2018년/C, Java, FileSystem

[C] :: 함수와 모듈화

위지원 2018. 1. 2. 19:59


처음으로 배우는 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의 값





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


프로그래밍 오류

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

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

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

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