550 likes | 755 Views
3 장 . 포인터 , 배열 , 구조체. Internet Computing Laboratory @ KUT Youn-Hee Han. 1. 포인터. 포인터 변수 (Pointer Variable) int *p; Something pointed by p is of type integer 핸들 (Handle) 이라고도 한다 . 변수에는 주소 (Address) 값이 들어간다 . Dereference (Indirection) Operator: Asterisk, * 타입 변환 (Type Casting).
E N D
3장. 포인터, 배열, 구조체 Internet Computing Laboratory @ KUT Youn-Hee Han
1. 포인터 • 포인터 변수 (Pointer Variable) • int *p; • Something pointed by p is of type integer • 핸들 (Handle)이라고도 한다. • 변수에는 주소 (Address) 값이 들어간다. • Dereference (Indirection) Operator: Asterisk, * • 타입 변환 (Type Casting) int *p; *p =15; int *p; p=(int *)malloc(sizeof(int)); *p =15; 주소 값 변수 int Date, Month; int *p; float *q; Data Structure
1. 포인터 • 동적 변수공간 할당 • int *p; • p = (int *) malloc(sizeof(int)); • *p = 15; • *p는 힙 메모리 변수, p는 스택 메모리 변수 • 익명 변수 (Anonymous Variable) 주소 값 변수 Address 800 p 800 p=(int*)malloc(sizeof(int)); ??? *p=15; 800 15 Data Structure
1. 포인터 • 정적 메모리 할당 (Static Memory Allocation) • 동적 메모리 할당 (Dynamic Memory Allocation) Address 800 p 800 if (pi != NULL) { delete pi; } p=(int*)malloc(sizeof(int)); ??? 800 *p=15; 15 Year 800 free(p); 15 2003 Operating System Uses It Data Structure
1. 포인터 • 널 (Null) 포인터 • ‘현재 아무 것도 가리키지 않고 있다’는 의미 • free (p); p가 가리키는 변수 공간을 반납 p = NULL; p를 따라가지 않도록 • 접근 위반 (Access Violation) • free(p)에 의하여 p가 가리키는 변수공간은 운영체제에 반납되지만 포인터 변수 p의 자체 공간은 프로그램이 끝날 때까지 반납이 안됨. • 즉, free(p) 이후에도 p 안에는 800이라는 주소값이 그대로 존재 • 운영체제는 800 주소에 해당하는 메모리를 활용하여 Year라는 변수를 저장하기 위하여 사용 가능 • 변수 p 자체는 정적으로 할당된 변수임 • p의 값이 여전히 800이라고 해서 해당 포인터를 따라가서 그 값을 건드린다면 큰 문제 • # define SAFE_FREE ( a ) { free ( a ) ; a = NULL ; } Data Structure
1. 포인터 • LValue (Left Value) vs. RValue (Right Value) • ‘L’ in LValue can be thought of as “location”, meaning a variable has a location that data or information can be put into. • ‘R’ in RValue can be thought of as “read” value, meaning a data value of the variable, that is, what information it contains • int x; x = 5; // This is fine, 5 is an RValue, x can be an LValue. 5 = x; // This is illegal. A literal constant such as 5 cannot be a lvalue. • LValue is contrasted with a constant. A constant has some data value, that is an RValue. But, it does not have an LValue. • *p = 15; *p는 등식의 좌변 값(LValue)으로 사용가능 • data = *p; *p는 등식의 우변 값(RValue)으로도 사용가능 Data Structure
1. 포인터 • Address Operator: Ampersand, & • ‘Address of’ • 변수 Date의 주소 값을 포인터 변수 p에 할당 • 변수에 대한 두가지 접근방법: Date = 12; 또는 *p = 12; int Date, month; int *p; p = &Date; P=&Date; *P=12 Data Structure
1. 포인터 • *&Date = 15; • *(&Date)는 ‘변수 Date의 주소 값이 가리키는 것’ • 변수 Date의 주소 값이 가리키는 것은 변수 자체 • 변수를 주소 값으로 매핑(Mapping)시키는 함수가 앰퍼샌드(&) • 그 주소가 가리키는 값으로 매핑시키는 함수가 애스터리스크(*). • 역함수이므로 *&Date = Date와 동일한 의미 *(&Date) 주소 값 변수 200 15 Date &Date Data Structure
1. 포인터 • Generic Pointer • 포인터는 그것이 가리키는 변수의 타입에 따라 분류 • 가리키는 변수에 따라 읽어와야 할 데이터의 분량이 달라짐. • void *Ptr; 선언시에는 가리키는 대상을 명시하지 않음 • Ptr = (float *)malloc(sizeof(float)); • 타입변환 연산자에 의해 실행시 포인터의 타입을 할당 • float *Ptr; 로 선언되었다면 (float *)는 없어도 된다 • Code Readability 효과를 기대하기 위해 사용하는 것이 바람직 Data Structure
1. 포인터 • Dangling Pointer • int *p, *q; p = (int *) malloc(sizeof(int)); *p = 15; q = p; free (p); p = NULL; *q = 30; Data Structure
1. 포인터 • Garbage: 반납도 접근도 할 수 없는 공간 • int *p; *q; p = (int *)malloc(sizeof(int)); q = (int *)malloc(sizeof(int)); *p = 5; *q = 20; p = q; Data Structure
1. 포인터 • 상수변수 • int a = 24; const int* Ptr = &a; • 상수 포인터 • 포인터가 항상 일정한 메모리 주소를 가리킴 • int* const Ptr = new int; *Ptr = 25; • int a; *Ptr = &a; (이것은 오류) #include "stdafx.h" int main(int argc, char* argv[]) { int a = 24; const int* Ptr = &a; *Ptr = 30; // a = 30; return 0; } #include "stdafx.h" int main(int argc, char* argv[]) { int a = 24; int* const Ptr = new int; *Ptr = 30; Ptr = &a; return 0; } Data Structure
2. 참조 호출과 값 호출 • Call by Reference • Pass by Reference • 참조 호출 • Call by Value • Pass by Value • 값 호출 • Actual Parameter • Formal Parameter • 함수의 리턴 값은 ‘Pass by Value’를 따른다. Data Structure
2. 참조 호출과 값 호출 • Call by Value void fcn(int i) { i = 5; } void main( ) { int n = 3; fcn(n); printf("%d", n); } Data Structure
2. 참조 호출과 값 호출 • Return - Call by Value • 함수실행 결과 리턴 값 • 복사에 의해 전달 됨 • 호출함수에게 리턴 되는 것은 변수 b 가 아니고, 변수 b를 복사한 값 • 지역변수는 함수 실행 종료와 함께 그 수명이 끝남. • 호출함수로 되돌아 온 순간에는 더 이상 존재하지 않음 • 따라서 지역변수를 리턴 할 수는 없음 int fcn(int& a) { int b = a; ... return b; } Data Structure
2. 참조 호출과 값 호출 • Call by Reference void fcn(int *pi) { *pi = 5; } void main( ) { int n = 3; fcn(&n); printf(“%d”,n); } Data Structure
2. 참조 호출과 값 호출 • Pass by Value (by Copy Constructor – Shallow Copy) • Pass by Reference • 대용량 데이터: 참조호출에 의해 복사에 소요되는 시간을 줄임 void fcn(bookclass b) { cout << b.Number; 책의 일련번호를 프린트하기 } void main( ) { bookclass MyBook; bookclass에 속하는 MyBook 객체선언 fcn(MyBook); } void fcn(bookclass *b) { cout << (*b).Number; 책의 일련번호를 프린트하기 } void main( ) { bookclass MyBook; bookclass에 속하는 MyBook 객체선언 fcn(&MyBook); } Data Structure
2. 참조 호출과 값 호출 • Alias (&) • Address 연산자가 아님. • 에일리어스(Alias, Reference, 별명)을 의미 • 호출함수에서 던져준 변수 n을 ‘나로서는 일명 pi’로 부르겠다. • 참조호출의 효과(본명이나 별명이나 동일한 대상) • 왜 Alias를 사용하는가 • 포인터 기호 없이 간단하게 참조 호출 • 호출함수에서는 무심코 변수 자체를 전달 • 피호출 함수에서 에일리어스로 받으면 참조 호출, 그냥 받으면 값 호출 void fcn(int &pi) { pi = 5; } void main( ) { int n = 3; fcn(n); cout << n; } Data Structure
2. 참조 호출과 값 호출 • Swapping Example void swap (int x, int y) { int temp; temp = x; x = y; y = temp; } void main( ) { int a = 20; int b = 40; swap(a, b); cout << a; cout << b; } void swap (int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; } void main( ) { int a = 20; int b = 40; swap(&a, &b); cout << a; cout << b; } void swap (int& x, int& y) { int temp; temp = x; x = y; y = temp; } void main( ) { int a = 20; int b = 40; swap(a, b); cout << a; cout << b; } Data Structure
3. 배열 • 직접 접근 • 인덱스 계산에 의해 해당 요소의 위치를 바로 알 수 있음 • i 번째 요소의 주소 • &A[i-1] = &A[0] + (i - 1) * sizeof(Element Type) Data Structure
3. 배열 • 2차원 배열: 행 우선(Row-major Order) • 배열의 특성 • 크기가 고정된다. • char name[10]; • char name[200]; • 배열 변수 자체는 상수 포인터 • 허용된 배열 공간 바깥을 접근하는 것은 컴파일러가 걸러주지 못한다. char a = ‘k’; char name[3] = {‘k’, ‘I’, ‘m’} name = &a; //error! Data Structure
3. 배열 • 포인터 산술 연산 • 배열 인덱스와 포인터 산술 연산 Data Structure
3. 배열 • 배열 과 포인터 • 배열 이름은 배열의 시작주소 값을 지님. 즉 포인터에 해당 • 배열은 프로그램 시작 시에 메모리 위치가 고정 • 따라서 배열변수는 일종의 상수 포인터에 해당 #include <stdio.h> int main(void) { int a[5] = {10,20,30,40,50}; int b=60; int *c; printf("a[0]: %d, a[1]: %d, b: %d \n", a[0], a[1], b); c = a; printf("c[0]: %d, c[1]: %d, b: %d \n", c[0], c[1], b); printf("*c: %d, *(c+1): %d, b: %d \n", *c, *(c+1), b); //a = &b; return 0; } 주석 해제했을 때의 에러내용은? Data Structure
3. 배열 • 배열의 전달 • 배열변수 이름, 즉 포인터 값이 복사되어 넘어감. • Call by reference 효과 • 따라서 피 호출함수에서 작업을 가하면 이는 원본을 건드리는 참조호출 효과 int SumUp(int A[ ], size) { int Sum = 0; for (int i = 0; i < size; i++)Sum += A[i]; return Sum; } void main( ) { int Total; int Score[5]; Score[0] = 98; Score[1] = 92; Score[2] = 88; Total = SumUp(Score, 3); } Data Structure
3. 배열 • 배열의 전달 Data Structure
3. 배열 • 배열의 전달 • 포인터로 직접 받아서 처리도 가능 • 상수 배열 • 읽기를 위한 접근만 허용 – 배열의 내부 요소 변경 못함 • int SumUp(const int A[ ], size) • int SumUp(const int *A, size) int SumUp(int *A, size) { int Sum = 0; for (int i = 0; i < size; i++) { Sum += *A; ++A; } return Sum; } void main( ) { int Total; int Score[5]; Score[0] = 98; Score[1] = 92; Score[2] = 88; Total = SumUp(Score, 3); } 하지만, A값 자체는 변경 가능 (ex. A++) Data Structure
3. 배열 • 정적배열과 동적배열 • 정적 배열은 스택에(변수 선언에 의함) • 동적배열은 힙에 (malloc, new 등 함수 실행에 의함) • 정적 배열 크기는 컴파일 시에, 동적 배열 크기는 실행 중에 결정 • 동적 배열도 실행 중에 그 크기를 늘릴 수는 없음 void Function1(int Size) { int MyArray[Size]; 정적 배열 선언 (컴파일 오류) ... } void Function2(int Size) { int *MyArrayPtr = new int[Size]; 동적 배열 선언 ... delete[ ] MyArrayPtr; } Data Structure
4. 구조체 • 필드, 레코드, 파일 필드 파일 레코드 Data Structure
4. 구조체 • 구조체 선언 및 사용 방법 • struct car { char Title[12]; int Year; int Price; }; • struct car MyCar; • typedef struct { char Title[12]; int Year; int Price; } carType • carType MyCar; • carType MyCar {"RedSportCar", 2005, 2000}; • carType CarArray[100]; typedef A B Ex.]typedef int* prtType;prtType p; Data Structure
4. 구조체 • 구조체와 포인터 • carType MyCar {"RedSportCar", 2005, 2000}; carType *p; p=&MyCar; • 주의점 • (*p).Year와 *p.Year는 다르다. • 연산자 우선순위에 의하여 . (dot) 연산자가 * (Asterisk) 연산자보다 우선 평가 • (*p).Year와 P->Year는 같다. Data Structure
4. 구조체 • 구조체내부에 포인터 변수 사용 개념적 재정의 • typedef struct { int *OwnerAge; int *CarPrice; } OwnerAndPriceType; OwnerAndPriceType P; P.OwnerAge = &Age; P.CarPrice = &Price • 복사본을 만들지 않음 • 원본을 그대로 유지 • 원본을 가리키는 포인터만 재 구성 • 원본이 바뀌면 자동으로 내용이 바뀜 Data Structure
4. 구조체 • 함수파라미터로 구조체 전달 • Pass by Value • 필드 단위의 복사 (Shallow Copy) • FunctionCall(carType ThisCar) { ... } main( ) { carType MyCar; FunctionCall(MyCar); } • Pass by Reference 가능 • 호출함수: FunctionCall(&MyCar); • 피 호출함수: FunctionCall (carType *ThisCar) Data Structure
5. 활성화 레코드 (Activation Record) • 프로그램 수행시 메모리의 구성 (시스템 마다 틀림) • An Activation Record corresponds to a call to a function which has not yet terminated with a return. Data Structure
5. 활성화 레코드 (Activation Record) • 메인함수 호출에 따른 활성화 레코드 • int a; 전역 변수 void main( ) { int b; char *s; 지역변수(스택) s = malloc(10); 10 바이트짜리 동적변수(힙) b = fcn(5); free (s); } Data Structure
5. 활성화 레코드 (Activation Record) • 일반 함수 호출에 따른 활성화 레코드 • int fcn(int p) { 파라미터(스택) int b = 2; b = b + p; return (b); } Data Structure
5. 활성화 레코드 (Activation Record) • Context Switching • 새로운 함수호출은 Context Switching 수반 • 새로운 활성화 레코드 생성 • 연속적인 함수호출에 따라 활성화 레코드 스택이 생성 • 함수 종료시 제일 위의 활성화 레코드가 사라짐으로써 직전의 상황을 복원 • Stack Overflow • 무한 루프내에서 함수 호출을 계속할 때 • 재귀적 호출에서 빠져나오지 못할 때 • Heap Overflow? • 무한 루프내에서 동적 메모리 할당 (new int)를 계속 할 때 • 더 이상 할당할 힙 공간이 없을 때 NULL 을 반환 • 힙 메모리 소진을 대비한 코드int *ptr = new int; if (ptr == NULL) ….. Data Structure
6. 디버깅을 위한 매크로 • 디버깅을 위한 코드 #include <assert.h> float SquareRoot (float t) { assert (t >= 0); return sqrt(t); } • 디버깅이 완벽히 수행된 이후 완성 코드 #define NDEBUG #include <assert.h> float SquareRoot (float t) { assert (t >= 0); return sqrt(t); } Conditional Macro Data Structure
Homework • 3장 연습문제 (pp.122~130) • 문제 2: 풀이 과정 설명 포함 • 문제 9: 문제를 먼저 풀고 프로그램 작성하여 확인 • 문제 25: 출력 내용 및 이유 설명 • 문제 27: 출력 내용 및 이유 설명 • 문제 34: 구체적 설명 제시 • 문제 48: 수행가능한 완벽 소스 코드 - Average 함수 내에서 b 에 대하여 NULL 값이 들어오는 지 체크하는 assert 매크로 활용하는 내용 포함 • 가능한한 모든 문제에 대하여 코드 작성하고 수행 모습 캡쳐!!! • 제출 메일의 제목 [철저하게 지킬것!] • 자료구조-2차-이름-학번 • 기한 • 4월 10일 23시 59분 59초 Data Structure
Appendix Data Structure
1. 프로세스의 메모리 배치 • 프로세스 이미지 • 프로세스는 일정한 메모리를 배정 받아 사용 • 프로그램 실행에 필요한 어셈블러 코드, 변수가 저장 • 원칙적으로 한 프로세스는 다른 프로세스의 메모리 영역에 접근 불가 • C 프로그램과 이미지의 내용 #include <stdio.h> #include <stdlib.h> extern char **environ; //extern 변수 int init_global_var = 3; //초기화된 global 변수 int unint_global_var; //초기화되지 않은 global 변수 int main(int argc, char **argv) { int auto_var; //automatic 변수 (local variable) static int static_var; //static 변수 register int reg_var; //register 변수 char *auto_ptr; //automatic 변수 (local variable) auto_ptr = malloc(10); return 0; } Data Structure
1. 프로세스의 메모리 배치 • 프로세스의 메모리 영역과 저장되는 변수 종류 하위 메모리 상위 메모리 Data Structure
1. 프로세스의 메모리 배치 • 참고: http://kucg.korea.ac.kr/~sjkim/teach/2001/cse037/l04.html #include <stdio.h> void test(void) { static int s_count=0; int a_count=0; s_count++; a_count++; printf("static count=%d\tauto count=%d\n", s_count, a_count); } void main(void) { int i; for(i=0; i<5; i++) test(); } http://myhome.hanafos.com/~kukdas/doc/c_lect/c_lect-7.html Data Structure
2. 스택과 힙 • 스택 (Stack) • 현재 호출되어 실행중인 함수의 코드와 환경 정보를 저장 • 예) • main()에서 printf()를 호출하였다면 main()이 차지하는 영역 위에 printf()를 처리하기 위한 메모리가 할당되고 printf()가 수행 • print()가 리턴되면 printf()와 관련된 영역은 삭제되고 프로세서의 수행은 main()으로 돌아감 • 함수 내부에서 임시로 사용되는 automatic 변수는 스택 영역에 할당 • automatic 변수 – 자동으로 생성되었다가 자동으로 없어지는 변수 • 힙 (Heap) • 스택은 영역 사용하던 메모리가 함수의 종료와 함께 사라짐 • 이 문제를 해결하기 위하여 리턴되어도 사라지지 않도록 한 영역이 힙 • malloc() 함수를 사용한 역영은 힙에 저장 Data Structure
2. 스택과 힙 int main() { char *prt; ptr = func(); } char *func() { char arr[10]; return arr; } int main() { char *prt; ptr = func(); // ptr 사용 free(ptr); // ptr 사용 종료 } char *func() { char *arr; arr = malloc(10); return arr; } Data Structure
3. 참조 호출과 값 호출 • A copy constructor • is a constructor that takes as an argument the type of itself, e.g. molecule::molecule(molecule &x) • complex c1; // normal constructor called • complex c2 = c1; // copy constructor needed • This is a special case only because a default copy constructor is provided by the compiler • if you don’t write one, the default simply does a shallow copy of the object (copies pointers) Data Structure
3. 참조 호출과 값 호출 • Copy Constructor • A copy constructor is called whenever a new variable is created from an object. This happens in the following cases (but not in assignment). • A variable is declared which is initialized from another object, eg, • Point q("Mickey"); // Normal constructor is used to build q. • Point r(q); // copy constructor is used to build r. • Point p = q; // copy constructor is used to initialize in declaration. • p = q; // Assignment operator, no copy constructor. • At function call, a parameter is initialized from its corresponding argument. • f(p); // copy constructor initializes formal value parameter. • An object is returned by a function. • If there is no copy constructor defined for the class, C++ uses the default copy constructor which copies each field, ie, makes a shallow copy (member-wise copy). • 프로그래머가 직접 관리하는 사용자 복사 생성자를 사용하는 것이 좋음. Data Structure
3. 참조 호출과 값 호출 • Copy Constructor Syntax //=== file Point.h ============================================= class Point { public: . . . Point(const Point& p); // copy constructor . . . //=== file Point.cpp ========================================== . . . Point::Point(const Point& p) { x = p.x; y = p.y; } . . . //=== file my_program.cpp ==================================== . . . Point p; // calls default constructor Point s = p; // calls copy constructor. p = s; // assignment, not copy constructor. Data Structure
3. 참조 호출과 값 호출 • Deep Copy vs. Shallow Copy • 프로그래머가 직접 복사 생성자를 코딩하여 Deep Copy 방법을 구현 Data Structure
3. 참조 호출과 값 호출 • Deep Copy vs. Shallow Copy: Example class atom { private: int x, y, z; public: x(); y(); z(); }; class molecule { private: int n; atom *atom_array; public: // constructor molecule(int natoms); }; molecule::molecule(int natoms){ n = natoms; atom_array = new atom[natoms]; } Data Structure
3. 참조 호출과 값 호출 • Deep Copy vs. Shallow Copy: Example Shallow Copy! Data Structure