Internet Computing Laboratory @ KUT Youn-Hee Han 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; 주소 값 변수 200 Date 204 Month int Date, Month; int *p; float *q; 404 ? p 408 q Data Structure
p=(int*)malloc(sizeof(int)); 1. 포인터 동적 변수공간 할당 int *p; p = (int *) malloc(sizeof(int)); *p = 15; *p는 힙 메모리 변수, p는 스택 메모리 변수 익명 변수 (Anonymous Variable) 주소 값 변수 404 800 p 408 q p Address 800 p=(int*)malloc(sizeof(int)); 800 ??? 800 15 ? *p=15; 800 15 Data Structure
p=(int*)malloc(sizeof(int)); 1. 포인터 정적 메모리 할당 (Static Memory Allocation) 동적 메모리 할당 (Dynamic Memory Allocation) C C++ 동적변수 할당 p = (int *)malloc(sizeof(int)); p = new int; 동적변수 해제 free(p); delete p; p Address 800 p=(int*)malloc(sizeof(int)); 800 ??? if (pi != NULL) { delete pi; } *p=15; 800 15 Year free(p); 800 15 2003 Operating System Uses It Data Structure
1. 포인터 널 (Null) 포인터 접근 위반 (Access Violation) ‘현재 아무 것도 가리키지 않고 있다’는 의미 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 ; } 404 800 p 408 q 404 NULL p 408 q 800 2003 Year 800 2003 Year 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, & int Date, month; int *p; ‘Address of’ 변수 Date의 주소 값을 포인터 변수 p에 할당 변수에 대한 두가지 접근방법: Date = 12; 또는 *p = 12; int Date, month; int *p; p = &Date; 200 Date 204 Month 200 Date 204 Month 200 12 Date 204 Month P=&Date; *P=12 404 ? p 408 q 404 200 p 408 q 404 200 p 408 q Data Structure
1. 포인터 *&Date = 15; *(&Date)는 ‘변수 Date의 주소 값이 가리키는 것’ 변수를 주소 값으로 매핑(Mapping)시키는 함수가 앰퍼샌드(&) 그 주소가 가리키는 값으로 매핑시키는 함수가 애스터리스크(*). 역함수이므로 *&Date = Date와 동일한 의미 *(&Date) 주소 값 변수 200 15 Date &Date Data Structure
1. 포인터 Generic Pointer Ptr = (float *)malloc(sizeof(float)); 포인터는 그것이 가리키는 변수의 타입에 따라 분류 가리키는 변수에 따라 읽어와야 할 데이터의 분량이 달라짐. 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)); 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 Call by Value Actual Parameter 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 int fcn(int& a) { int b = a; ... 함수실행 결과 리턴 값 복사에 의해 전달 됨 호출함수에게 리턴 되는 것은 변수 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 (&) 왜 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. 배열 포인터 산술 연산 배열 인덱스 배열 인덱스와 포인터 산술 연산 int Buffer[1024]; for (int i = 0; i < 1024; i++) Buffer[i] = 5; 포인터 산술연산 for (int* p = Buffer; p < &Buffer[1024]; p++) *p = 5; 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. 구조체 필드, 레코드, 파일 정지희 여 19 011-388-3031 박하영 02-445-5059 김무성 남 031-330-6432 정건호 019-301-3001 레코드 Data Structure
4. 구조체 typedef A B 구조체 선언 및 사용 방법 struct car { char Title[12]; int Year; int Price; }; struct car MyCar; typedef struct { } 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 Reference 가능 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. High Address Heap 미사용 공간 Stack Global Variables Low Address Machine Code (프로그램 소스 코드, 스트링 상수) Data Structure
5. 활성화 레코드 (Activation Record) 메인함수 호출에 따른 활성화 레코드 int a; 전역 변수 void main( ) { int b; char *s; 지역변수(스택) s = malloc(10); 10 바이트짜리 동적변수(힙) b = fcn(5); free (s); } 힙 *s 미사용 공간 스택 (main 함수의 활성화 레코드) Local Variables b, s Return Address 204번지 Value Parameters Return Value 전역변수 a 기계코드 main( ), fcn( ) Data Structure
5. 활성화 레코드 (Activation Record) 일반 함수 호출에 따른 활성화 레코드 int fcn(int p) { 파라미터(스택) int b = 2; b = b + p; return (b); } 힙 *s 미사용 공간 스택 (fcn 함수의 활성화 레코드) Local Variables b Return (귀환) Address 230번지 Value Parameters p: 5 Return Value 7 (main 함수의 활성화 레코드) b, s Return Address 204번지 전역변수 a 기계코드 main( ) fcn1( ) 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. 디버깅을 위한 매크로 Conditional Macro 디버깅을 위한 코드 디버깅이 완벽히 수행된 이후 완성 코드 #include <assert.h> float SquareRoot (float t) { assert (t >= 0); return sqrt(t); } 디버깅이 완벽히 수행된 이후 완성 코드 #define NDEBUG 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 프로그램과 이미지의 내용 프로세스는 일정한 메모리를 배정 받아 사용 프로그램 실행에 필요한 어셈블러 코드, 변수가 저장 원칙적으로 한 프로세스는 다른 프로세스의 메모리 영역에 접근 불가 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
unint_global_var, static_var 1. 프로세스의 메모리 배치 프로세스의 메모리 영역과 저장되는 변수 종류 메모리 영역 변수 환경변수 영역 environ 코드 영역 어셈블된 프로그램 코드 데이터 영역 (global, static) 초기화된 영역 init_global_var = 3 초기화 안된 영역 (0으로 할당) unint_global_var, static_var 스택 argc, argv, auto_var, reg_var, auto_ptr 힙 malloc()이 할당한 10바이트 하위 메모리 상위 메모리 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) 힙 (Heap) 현재 호출되어 실행중인 함수의 코드와 환경 정보를 저장 예) main()에서 printf()를 호출하였다면 main()이 차지하는 영역 위에 printf()를 처리하기 위한 메모리가 할당되고 printf()가 수행 print()가 리턴되면 printf()와 관련된 영역은 삭제되고 프로세서의 수행은 main()으로 돌아감 함수 내부에서 임시로 사용되는 automatic 변수는 스택 영역에 할당 automatic 변수 – 자동으로 생성되었다가 자동으로 없어지는 변수 힙 (Heap) 스택은 영역 사용하던 메모리가 함수의 종료와 함께 사라짐 이 문제를 해결하기 위하여 리턴되어도 사라지지 않도록 한 영역이 힙 malloc() 함수를 사용한 역영은 힙에 저장 Data Structure
2. 스택과 힙 int main() { int main() { char *prt; 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 Data Structure
3. 참조 호출과 값 호출 Deep Copy vs. Shallow Copy: Example class atom { private: int x, y, z; public: x(); y(); z(); }; class molecule { int n; atom *atom_array; // 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
3. 참조 호출과 값 호출 Deep Copy vs. Shallow Copy: Example Deep Copy! Data Structure
3. 참조 호출과 값 호출 Deep Copy vs. Shallow Copy: Example Data Structure
3. 참조 호출과 값 호출 Copy Constructor vs. Assignment Operator 할당 연산자의 Default 행동양식 It is also shallow copy! So it is needed for you to define your operator= 복사 생성자 할당 연산자 triangleClass T1; T1.Base = 10; T1.Height = 20; triangleClass T2 = T1; triangleClass T1, T2; T2 = T1; Data Structure
3. 참조 호출과 값 호출 Assignment Operator Implementation class TFoo : public TSuperFoo { TBar* fBar1; TBar* fBar2; } TFoo& TFoo::operator=(const TFoo& that) { if (this != &that) { TBar* bar1 = 0; TBar* bar2 = 0; try { bar1 = new TBar(*that.fBar1); bar2 = new TBar(*that.fBar2); catch (...) { delete bar1; delete bar2; throw; TSuperFoo::operator=(that); delete fBar1; fBar1 = bar1; delete fBar2; fBar2 = bar2; return *this; The difference between the copy constructor and assignment operator is that the copy constructor is a constructor — it is to turn raw storage into an object of a specific class. An assignment operator, on the other hand, copies state between two existing objects. In other words, an assignment operator has to take into account the current state of the object when copying the other object's state into it. For many classes, the current state of the object doesn't matter and both functions do the same thing. But for some classes (including the one in our example), the current state does matter, and the assignment operator is more complicated. Data Structure
3. 참조 호출과 값 호출 Copy Constructor vs. Assignment Operator의 Default 행동양식 It depends on… the contents of the object type whether there is an assignment operator defined. When a Class contains objects… Copy constructor and assignment operator call the contained object’s copy constructor and assignment operator RECURSIVELY Data Structure