2019-1 st 프로그래밍입문 (1) 11 장. 변수, 함수의활용및동적메모리 박종혁교수 서울과학기술대학교컴퓨터공학과 UCS Lab Tel: 970-6702 Email: jhpark1@seoultech.ac.kr
목차 변수의활용 변수의특성 auto 와 register extern static 함수의활용 재귀함수 함수포인터 동적메모리 2
변수의특성 (1/4) 3
변수의특성 (2/4) 변수의영역 (scope) 변수가사용될수있는범위 변수가선언된위치에의해결정 블록범위 : 지역변수 - 이름이같은변수가여러개있을때가장가까운블록내의변수가우선적으로사용된다 파일범위 : 전역변수 4
변수의특성 (3/4) 변수의생존기간 (lifetime) 변수가언제생성되고소멸되는지 자동할당 - 블록에들어갈때메모리가생성되고블록을빠져나갈때메모리가소멸 - 메모리의스택 (stack) 영역에생성 정적할당 - 프로그램이시작될때메모리가생성되고, 프로그램이종료될때메모리가소멸 - 정적메모리 (static memory) 영역에생성 동적할당 - 프로그래머가원하는시점에메모리를할당하고, 원하는시점에해제 - 메모리의힙 (heap) 영역에생성 5
변수의특성 (4/4) 변수의연결특성 (linkage) 변수를하나의소스파일에서만사용할수있을지, 프로그램전체에서사용할수있는지를결정 전역변수만연결특성을가질수있다 내부연결 - 전역변수를하나의소스파일에서만사용하도록제한 - static 키워드를이용 외부연결 - 전역변수를프로그램전체에서사용할수있게만든다 - 전역변수는디폴트로외부연결특성을갖는다 - 다른소스파일에선언된전역변수를참조하려면전역변수에대한 extern 선언이필요하다 6
기억부류지정자 생존기간과연결특성에영향을주기위한키워드 auto, register, static, extern auto, register, static 은지역변수의생존기간과할당위치에영향 static 와 extern 은전역변수와함수의연결특성을지정 auto, register 은지역변수에만사용 7
auto auto 로선언된지역변수는블록에들어갈때자동으로생성되고, 블록을빠져나갈때자동으로소멸된다 지역변수는디폴트로 auto 로간주된다 auto 로지정된변수를자동변수라고한다 8
register 변수를메모리에할당하는대신 CPU 의레지스터에할당한다 변수를레지스터에할당하면변수에좀더빠르게접근할수있다 레지스터변수로선언해도변수가레지스터에할당되지않을수도있다 레지스터변수에대해서는주소구하기연산자를사용할수없다 9
One Definition Rule (1/2) 함수는프로그램전체에서반드시한번만정의해야한다. 반면에선언은여러번할수있다 10
One Definition Rule (2/2) 변수에도 ODR 이적용된다 변수도한번만정의할수있으며, extern 선언은여러번할수있다 변수의선언 - 변수의데이터형과이름을알려주지만메모리를할당하지않는다 변수의정의 - 변수의메모리를할당하고초기화한다 변수는선언과정의가동시에이루어진다 extern 선언 extern 은변수에대해서정의는하지않고선언만하게만든다 '~ 라는변수가있다 ' 라고알려주지만, 메모리를할당하지는않는다 extern 은전역변수에만사용할수있다 11
전역변수의 extern 선언 (1/5) 전방선언 (forward declaration) 12
전역변수의 extern 선언 (2/5) 전역변수의 extern 선언은파일범위를넘어서는외부연결특성을제공한다 13
전역변수의 extern 선언 (3/5) 같은프로그램내의소스파일이여러개일때는소스파일마다전역변수의 extern 선언이필요하다 14
전역변수의 extern 선언 (4/5) 전역변수의 extern 선언은함수안에넣어줄수도있다 해당함수에서만전역변수를사용할수있다 15
전역변수의 extern 선언 (5/5) 변수의 extern 선언시초기화를하면변수에대한메모리가할당된다 ( 즉, 변수의선언 ( 정의 ) 로간주된다 ) 전역변수의선언문세가지 16
예제 : 전역변수의 extern 선언 test.c main.c 17
static static 지역변수 지역변수의생존기간에영향을준다 자동할당인지역변수의생존기간을정적할당으로변경한다 지역변수가프로그램시작시생성되고, 프로그램종료시소멸되게만든다 static 전역변수 전역변수의연결특성에영향을준다. 전역변수가내부연결특성을갖게만든다 static 함수 함수의연결특성에영향을준다 함수가내부연결특성을갖게만든다 18
예제 : static 지역변수 19
static 지역변수사용예 : 누산기프로그램 이전연산의결과를 lhs 로사용 void accumulator(char op, int operand) { static int result = 0; accumulator 함수가호출될 switch (op) 때마다이전연산의결과를 { 계속해서이용할수있다. case '+': result += operand; break;... default: return; } printf( %c %d = %d\n, op, operand, result); } 20
예제 : accumulator 함수의정의및호출 21
accumulator 함수의구현 22
static 전역변수 전역변수의연결특성을내부연결로지정한다 선언된소스파일에서만전역변수를사용하도록제한한다 23
static 키워드 24
함수의외부연결특성 함수는디폴트로 extern 으로선언되어외부연결특성을제공한다 함수선언의 extern 은보통생략한다 25
예제 : 다른소스파일에정의된함수의호출 func.c main.c 26
static 함수 static 함수는함수가정의된소스파일밖에서는호출할수없다 27
함수와전역변수를사용하기위한가이드라인 1. 소스파일은기능단위로나누어작성한다 - 관련된함수와전역변수를모아서소스파일을구성한다 - 소스파일명도의미있는이름으로정하는것이좋다 2. 소스파일에서외부로노출해야하는함수나전역변수는 extern 으로정의한다 - 이때 extern 키워드는생략할수있다 - 헤더파일에함수선언과전역변수의 extern 선언을넣어준다 - 소스파일명이 test.c 면헤더파일명은 test.h 로지정한다 3. 소스파일내부에서만사용되는함수나전역변수는 static 으로정의한다 4. 프로그램의나머지부분에서 test.c 에있는함수나전역변수를사용하려면헤더파일인 test.h 를포함한다 28
재귀함수의개념 재귀함수 (recursive function) : 자기자신을다시호출하는함수 재귀기법 (recursion) 어떤문제를비슷한유형의다른문제로바꾸어처리하는방법 분할정복알고리즘 (divide and conquer) 을구현할때주로사용 - 큰문제를비슷한종류의작은문제들로나누어처리 재귀함수는무한히자기자신을호출해서는안되며반드시종료조건이필요하다 29
정수의팩토리얼구하기 int get_factorial(int num) { 종료조건 if (num <= 1) return 1; return num * get_factorial(num - 1); } 자기자신을호출한다. 30
예제 : 재귀함수의정의및호출 31
두정수의최대공약수구하기 int get_gcd(int x, int y) { if (x % y == 0) 종료조건 return y; return get_gcd(y, x % y); } 32
예제 : get_gcd 함수의정의및호출 33
재귀함수와반복문 반복문을사용하는것이재귀함수보다성능이좋다 재귀함수를호출하면함수가리턴되기전에여러번반복적으로호출되므로, 함수호출시오버헤드가크다 함수호출의깊이가너무깊어지지않도록주의한다 재귀기법은문제를해결하기위한알고리즘을단순화한다 34
프로그램의메모리레이아웃 함수에도메모리주소가있다. 텍스트세그먼트에있는함수코드도메모리의특정번지로로드되서실행중에함수의주소로사용된다 함수포인터 함수의주소를저장하는포인터 35
함수포인터의선언 (1/2) 36
함수포인터의선언 (2/2) 포인터수식어와포인터변수명을반드시 ( ) 로묶어주어야한다 37
함수포인터의초기화 아직가리키는함수가없으면널포인터로초기화한다 함수의주소를구하려면주소구하기연산자 (&) 와함께함수의이름을적어준다 함수의주소를구할때는 & 연산자없이함수이름만사용해도된다 38
함수포인터를이용한함수호출 역참조연산자를이용해서함수를호출할수있다 역참조연산자없이함수포인터를직접함수이름인것처럼사용할수도있다 39
함수포인터의변경 함수포인터도변수이므로값을변경할수있다 함수포인터가다른함수를가리키게만들수있다 함수포인터의원형과함수포인터가가리키는함수의원형이같아야한다 pf 와원형이같은함수 이제 pf 는 add 를가리킨다. pf 가가리키는 add 를호출한다. 40
예제 : 함수포인터의선언및사용 41
함수포인터형 (1/2) 42
함수포인터형 (2/2) 함수포인터형의변수를선언할수있다. 함수포인터변수 매개변수가구조체형일때는구조체정의후에함수포인터형을정의해야한다 typedef struct point { int x, y; } POINT; 구조체를먼저정의해야한다. typedef void(*pfprint)(const POINT*); 매개변수가구조체형인경우 43
예제 : 함수포인터형의정의및사용 44
함수포인터배열 (1/3) 원형이같고함께사용되는함수들을함수포인터배열에원소로저장하고사용할수있다 45
함수포인터배열 (2/3) 원형이같은함수들의주소를함수포인터배열에저장한경우, 함수포인터배열을이용해서함수들을호출할수있다 46
함수포인터배열 (3/3) 47
예제 : 함수포인터배열의사용 48
함수포인터의활용 : qsort 함수 (1/2) 퀵정렬 정렬할배열의원소중하나를선택한다음그값을키 (key) 라고한다 정렬할배열의원소들을키보다작은값, 키보다큰값의두그룹으로나눈다 두그룹에대해서각각다시퀵정렬을수행한다 그룹내에값이하나만남을때까지계속이작업을반복한다 49
함수포인터의활용 : qsort 함수 (2/2) qsort : 표준 C 라이브러리함수 ptr : 정렬할배열의시작주소 ( 기본형배열, 구조체배열등 ) count : 정렬할배열에들어있는원소의개수 size : 배열원소의바이트크기 compare : 배열의원소들과키값을비교할때호출할함수의주소 - 함수원형이정해져있다. - e1, e2 : qsort 함수에인자로전달된배열원소를가리키는포인터 - e1 이가리키는원소와 e2 가가리키는원소를비교해서 e1 > e2 면 0 보다큰값리턴, e1 < e2 면 0 보다작은값리턴, e1 == e2 면 0 리턴 50
qsort 함수 : int 배열의정렬 qsort(arr, ARR_SIZE, sizeof(arr[0]), compare_int); 배열의시작주소 배열의크기 배열원소의바이트크기 배열원소의비교함수 // int 배열의원소를비교하는함수 int compare_int(const void *e1, const void *e2) { // e1, e2 는 int 의주소이므로 const int* 형으로형변환해서사용한다. const int *p1 = (const int*)e1; const int *p2 = (const int*)e2; return (*p1 - *p2); } 51
예제 : qsort 함수를이용한 int 배열의정렬 52
콜백 (callback) 함수 프로그래머가정의한함수의주소를라이브러리함수를호출할때전달해서특정조건일때호출하도록등록 53
qsort 함수 : CONTACT 구조체배열의정렬 qsort(arr, size, sizeof(contact), compare_by_name); 구조체배열의시작주소 // 이름순정렬 int compare_by_name(const void *e1, const void *e2) { // e1, e2 는 CONTACT 의주소이므로 const CONTACT* 형으로형변환 const CONTACT *p1 = (const CONTACT*)e1; const CONTACT *p2 = (const CONTACT*)e2; return strcmp(p1->name, p2->name); } 54
예제 : CONTACT 구조체배열의정렬 55
동적메모리의개념 56
정적메모리와동적메모리 57
동적메모리의필요성 프로그램을작성하는시점에배열의크기를미리알수없을때 int 배열size; 크기를 // int 변수에배열의입력받는크기경우 printf(" 배열의크기? "); scanf("%d", &size); int arr[size]; 배열크기에변수를사용할수없다. 메모리낭비 int 최대arr[100]; 크기를가정해서배열을선언하는경우 버퍼오버런의위험이있다. 동적메모리를사용하는경우 메모리낭비없이꼭필요한만큼메모리를할당할수있다. 메모리의할당과해제시점을프로그래머가마음대로선택할수있다. 동적메모리는메모리사용에있어서프로그래머에게최대한의자유를보장한다. 58
동적메모리의할당과해제 (1/4) 동적메모리를할당하려면 malloc 함수를사용한다 할당된메모리의주소 할당할메모리의바이트크기 malloc 함수는특정바이트크기의메모리를할당하고그주소 (void* 형 ) 를반환한다 malloc 함수가할당한메모리를어떻게사용할지는프로그래머가마음대로정할수있다 void 포인터는역참조연산을할수없으므로, 특정포인터형으로형변환해야한다 malloc 함수가리턴하는주소를포인터변수에보관해야한다 59
동적메모리의할당과해제 (2/4) 60
동적메모리의할당과해제 (3/4) malloc 함수는동적메모리를할당할수없으면 NULL 을리턴한다 동적메모리의주소를저장하는포인터는배열의원소를가리키는포인터이다 arr 가가리키는배열의원소에접근할때 arr[i] 로접근한다 61
동적메모리의할당과해제 (4/4) 동적메모리를해제하려면 free 함수를사용한다 해제할메모리의주소 메모리누수 동적메모리가사용중이아닌데해제되지않고계속남아있는상황 사용이끝난동적메모리는반드시 free 함수를호출해서해제해야한다 동적메모리를할당할때언제해제할지를판단해서해제하는코드를함께작성하는것이좋다 62
예제 : 동적메모리를이용해서입력받은정수를정렬하는코드 63
동적메모리를사용하는과정 (1/2) 동적메모리의주소를저장할포인터를준비한다 동적메모리를어떤용도로사용할지에따라포인터의데이터형을정하고, 널포인터로초기화한다 동적메모리를할당할때는 malloc 함수를사용한다 malloc 함수의인자로는할당할메모리의바이트크기를지정한다 64
동적메모리를사용하는과정 (2/2) 동적메모리를사용할때는배열의원소를가리키는포인터처럼사용한다 arr[i] 처럼인덱스를이용한다 동적메모리는사용이끝나면 free 함수로해제한다 더이상해제된동적메모리를가리키지않도록포인터를널포인터로만든다 65
동적메모리사용시주의사항 (1/4) 동적메모리를해제하고나면포인터를널포인터로만들어야한다 int *p = (int*)malloc(sizeof(int) * 100);... 해제된메모리에 free(p); 접근하면실행에러 *p = 123; free(p); p = NULL; *p = 123; 메모리 0 번지에접근하므로실행에러 허상포인터 잘못된주소에접근하므로프로그램이죽을수도있고엉뚱한변수의값이바뀔수도있다. 메모리 0 번지접근시예외가발생하므로프로그램이항상죽는다. 포인터를안전하게사용하려면포인터가가리키는대상이없을때항상 NULL 을저장한다 포인터를사용할때는 NULL 이아닌경우에만사용한다 if (p!= NULL) *p = 123;. p 가널포인터가아니면안전하게사용할수있다. 66
동적메모리사용시주의사항 (2/4) 해제된동적메모리를다시해제해서는안된다 int *p = (int*)malloc(sizeof(int) * 100); // p 사용 free(p); // 사용이끝난메모리는해제한다.... free(p); 해제된메모리를다시해제하면실행에러 free 호출후포인터를널포인터로만들면문제가발생하지않는다 - free 함수는매개변수로널포인터가전달되면아무것도하지않는다 free(p); // 사용이끝난메모리는해제한다. p = NULL; // p 를널포인터로만든다. free(p); free 함수는널포인터가전달되면아무것도하지않는다. 67
동적메모리사용시주의사항 (3/4) free 함수는동적메모리를해제할때만사용해야한다 지역변수나전역변수를가리키는포인터로 free 함수를호출하면실행에러가발생한다 int x; int *p = &x; // p 는지역변수를가리킨다. free(p); 지역변수를가리키는포인터로 free 를호출하면실행에러가발생한다. 68
동적메모리사용시주의사항 (4/4) 동적메모리의주소를잃어버리지않도록주의해야한다 void test_memory(int cnt) { int i; int *p = (int*)malloc(sizeof(int)*cnt); for (i = 0; i < cnt; i++) p[i] = 0; } 함수가리턴할때 p 가소멸되므로동적메모리의주소를잃어버리게된다. 동적메모리누수 int* test_memory(int cnt) { int i; int *p = (int*)malloc(sizeof(int)*cnt); for (i = 0; i < cnt; i++) } p[i] = 0; return p; int main(void) { int *p = test_memory(5); } 동적메모리의주소를리턴하는경우 // p 사용 free(p); p = NULL; 동적메모리의주소를함수를호출한곳으로리턴하고 p 소멸 동적메모리의주소를받아온다. 함수를호출한쪽에서사용이끝난동적메모리를해제한다. 69
동적메모리의활용 (1/4) 프로그램실행중에구조체를동적메모리에할당하고, 그주소만포인터배열에저장하고사용할수있다 CONTACT arr[100] = { 0 }; 구조체변수를 100 개생성하는경우 44 바이트 100 개, 4400 바이트가필요 CONTACT *arr[100] = { NULL }; 포인터 ( 주소 ) 를 100 개생성하는경우 4 바이트 100 개, 400 바이트가필요 70
동적메모리의활용 (2/4) 사용자가연락처등록기능을선택하면, 그때 CONTACT 구조체를동적메모리에할당하고, 그주소만포인터배열의원소로저장하고사용한다 int cnt = 0; while (cnt < MAX) {... arr[cnt] = (CONTACT*)malloc(sizeof(CONTACT));... 구조체포인터 } for (i = 0; i < cnt; i++) { printf("%6s %11s %d\n", arr[i]->name, arr[i]->phone, arr[i]->ringtone); } 구조체포인터이므로 -> 로멤버에접근한다. 71
동적메모리의활용 (3/4) 72
동적메모리의활용 (4/4) 동적메모리해제는동적메모리할당과 1:1 로대응된다 동적메모리를 3 번할당했으면, 3 번해제해야한다 생성된구조체의개수 for (i = 0; i < cnt; i++) { free(arr[i]); cnt번만큼동적 arr[i] = NULL; 메모리를해제한다. } 73
예제 : 구조체포인터배열과동적메모리 // arr[i] 는 CONTACT 포인터 74
75
참고문헌 천정아, Core C Programming, 연두에디션 (2019) C 가보이는그림책, ANK Co., Ltd., 성안당 (2018) Greg Perry, Dean Miller 어서와 C 언어는처음이지, 천인국옮김, 인피니티북스 (2015) KELLEY ( 역 : 김명호외 ), A Book on C, 홍릉과학출판사 (2003) 윤성우, 열혈 C 프로그래밍, 오렌지미디어 천인국, 쉽게풀어쓴 C 언어 Express, 생능출판사 서현우, 뇌를자극하는 C 프로그래밍, 한빛미디어 강성수, 쾌도난마 C 프로그래밍, 북스홀릭 고응남, C 프로그래밍기초와응용실습, 정익사 76