[C] :: 함수와 모듈화
처음으로 배우는 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); } |
-외부 : 한 파일내에 존재하는 함수끼리 같은 변수가 필요할 때
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 |
8 |
32 |
4 |
16 |
2 |
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의 값
일반 프로그래밍 및 컴파일러 오류
프로그래밍 오류
- 전역,지역변수의 이름을 같게 선언하는 것
- 포인터를 무분별하게 사용하는 것
- 함수의 파라미터를 포인터로 선언하고 파라미터를 넘길때 주소 연산자를 적지 않는 것
- 재귀 함수를 정의 할때 함수의 종료 조건을 기술 하지 않는 것