윤 홍 란 hryun@sookmyung.ac.kr
03. 메모리 관리 C++ 프로그램에서 다룰 수 있는 메모리의 종류 동적 할당은 효과적으로 메모리를 사용할 수 있으나, 메모리에 대한 반환에 유의해야 함. 메모리 누수(Memory leakage) 종류 설명 정적(static) 프로그램의 시작과 동시에 할당되고, 프로그램의 종료와 동시에 해제됨. static을 사용하거나 파일 범위에서 선언된 변수는 정적이 됨. 자동(automatic) 인스턴스가 선언된 지역에 있는 블록 내에서 유효한 것으로 선언된 지점에서 생성되고, 블록을 벗어나는 순간 해제됨. auto를 사용하거나 블록 범위에서 선언된 변수는 자동이 됨. 동적(dynamic) 블록이나 프로그램과 관계없이 사용자가 지정하는 순간 할당되고, 해제됨. new, delete등의 연산자를 이용해서 할당 또는 해제됨.
C style 메모리관리 C에서의 동적 메모리 관리 calloc( ) : Contiguous Allocation. calloc( )과 malloc( )을 이용한 동적 메모리 할당 표준 library에 제공- prototype은 stdlib.h에 포함됨. calloc( ) : Contiguous Allocation. calloc(n, el_size) 각 원소가 el_size인 n개의 원소로 이루어진 배열. memory 영역은 모든 bit가 0으로 초기화 됨. void * 형의 pointer가 return됨. (또는 NULL이 return됨. - 실패했을 경우) malloc( ) : Memory Allocation. malloc(memory_size) memory의 초기화 않음. calloc( )보다 시간이 적게 걸림. void * 형의 pointer(또는 NULL)가 return됨. free(ptr) 동적으로 할당받은 공간은 함수의 실행이 끝나도 system에 반환되지 않으므로 반드시 명시적으로 반환할 것. (예) #include <stdio.h> #include <stdlib.h> int main( ) { int *a; int n; .... a = calloc(n, sizeof(int)); /* machine independent */ free(a); } 또는 , a = malloc(n * sizeof(int));
기본적인 동적 메모리 할당과 해제(1) 사용자에게 입력받은 정수의 합과 평균을 구하는 예 // 몇 개의 정수를 입력할지 물어본다. int size; cout << "몇 개의 정수를 입력하시겠소? "; cin >> size; int* arr = new int [size]; // 필요한 만큼의 메모리를 할당한다. cout << "정수를 입력하시오.”<<endl; for (int i = 0; i < size; ++i) cin >> arr[i]; // 평균을 계산하고 출력한다. int sum = 0; for (i = 0; i < size; ++i) { sum += arr[i]; } float ave = (float)sum / (float)size; cout << "합 = " << sum << ", 평균 = " << ave << "\n"; delete[] arr; // 사용한 메모리를 해제한다.
기본적인 동적 메모리 할당과 해제(2) 동적으로 메모리를 할당하기 동적으로 할당한 메모리를 해제하기
기본적인 동적 메모리 할당과 해제(4) 동적 메모리 할당을 사용하는 방법의 정리 메모리를 할당할 때는 타입과 크기를 지정한다. 그러면 컴퓨터가 메모리를 할당한 후에 그 메모리의 주소를 보관한다. 우리는 이 주소를 보관해두어야 한다. 보관해둔 주소를 통해서 메모리 공간을 사용할 수 있다. 이 때는 배열의 원소를 가리키는 포인터처럼 사용할 수 있다. 사용이 끝난 후에는 반드시 보관해 둔 주소를 알려주면서 메모리를 해제한다.
연산자 new, delete, new[], delete[] // int 하나를 담을 수 있는 크기의 메모리 공간을 할당한다. int* p = new int; // 메모리에 값을 넣어본다. *p = 337; // 사용이 끝난 메모리를 해제한다. delete p; [16-9]
동적 메모리 할당의 규칙(1) new, delete와 new[], delete[] 쌍을 맞춰서 사용하자. NULL 포인터를 해제하는 것은 안전하다. 해제한 메모리를 또 해제해서는 안 된다. char* p = NULL; delete p; // 다음과 같이 할 필요가 없다. if ( NULL != p ) // 메모리를 할당한다. short* p = new short [100]; // 메모리를 해제한다. delete[] p; delete[] p; // Error
동적 메모리 할당의 응용(1) 문자열을 뒤집어서 복사해주는 함수 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() { char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0;
동적 메모리 할당의 응용(2) 프로그램의 구조
동적 메모리 할당의 응용(3) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-16]
동적 메모리 할당의 응용(4) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-17]
동적 메모리 할당의 응용(5) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString(original,8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-18]
동적 메모리 할당의 응용(6) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString(original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-19]
동적 메모리 할당의 응용(7) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i – 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-20]
동적 메모리 할당의 응용(8) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString(original, 8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-21]
동적 메모리 할당의 응용(9) 화살표까지 실행되었을 때의 메모리 상태 char* ReverseString(const char* src, int len) { char* reverse = new char [len + 1]; for (int i = 0; i < len; ++i) reverse[i] = src[len - i - 1]; reverse[len] = NULL; return reverse; } int main() char original[] = "NEMODORI"; char* copy = ReverseString( original,8); cout << original << "\n"; cout << copy << "\n"; delete[] copy; copy = NULL; return 0; [16-22]
04. 함수의 기본 인자 기본 인자(Default Parameter) 기본 인자의 사용 방법 함수를 호출할 때 인자를 명시하지 않는 경우에 자동으로 전달되는 인자 불필요한 일부 인자의 명시를 생략할 수 있으므로 편리함. 함수의 선언과정에서 값을 명시하는 방법으로 지정 기본인자는 항상 맨 뒤의 인자부터 순차적으로 지정해야 함. 기본 인자의 사용 방법 int func(char *, int = 1, int = 100); f( "chul su" ); // default_func(“chul su”, 1, 100)과 같음 f( "chul su", 20); // default_func(“chul_su”, 20, 100)과 같음 f( "chul su", 20, 173); // 그대로 f( ); // 에러: char *에 대해서는 기본 인자가 없음. f( 10 ); // 에러: 인자가 하나만 있는 경우엔 반드시 char *
기본 인자의 올바른 사용과 잘못된 사용 기본인자의 올바른 사용 기본인자의 잘못된 사용 void f( int, int, int, int = 100); void f( int, int, int = 10, int = 100 ); void f( int, int =1, int = 10, int =100 ); void f( int = 0, int = 1, int = 10, int = 100 ); 기본인자의 잘못된 사용 void f( int, int, int = 10, int ); void f( int, int = 1, int, int = 100 ); void f( int = 0, int, int, int ); void f( int = 0, int, int, int = 100 ); void f( int, int = 1, int = 10, int ); void f( int = 0, int, int = 10, int );
기본 인자의 사용 예제[2-18] #include <iostream> int main () #include <iomanip> #include <fstream> using namespace std; void print(double value, size_t precision = 6, size_t width=0, ios_base::fmtflags align = ios_base::right, ostream &out = cout ) { if( width > 0 ) out << setw( width ) ; out << setiosflags( align ) ; out << setprecision( precision ); out << value ; out << resetiosflags( ios_base::adjustfield ) ; out << setprecision( 6 ); } int main () { double value = 1.7345; cout << "123456789012345" << endl; print( value ); cout << endl; print( value, 2 ); print( value, 2, 10 ); print( value, 2, 10, ios_base::left ); ofstream fout("test.out" ); print( value, 2, 10, ios_base::left, fout ); }
05. 레퍼런스 레퍼런스(reference) 레퍼런스의 선언 예 “개체에 대한 또 다른 명칭” 으로 정의됨(별명) 포인터와 비슷하게 동작하지만 포인터와 달리 *연산자를 이용한 역 참조(deference)를 필요로 하지 않는다. 포인터와 달리 해당 자원을 공유하는 매개일 뿐 실제의 인스턴스가 아님. 레퍼런스는 반드시 유효한 객체를 레퍼런스 하고 있어야 함. 레퍼런스는 처음 초기화 시 정해진 개체만을 참조하게 된다. 레퍼런스는 정의와 동시에 반드시 레퍼런스하고자 하는 객체에 의해 초기화되어야한다. 레퍼런스의 선언 예 int i; int & ref = i; // OK : int 타입 변수 i에 대한 레퍼런스
함수 호출에서의 인자의 전달 인자의 전달 방식 레퍼런스에 의한 호출(Call by Reference) 복사에 의한 호출(Call by Copy) 값에 의한 호출(Call by Value) 결과에 의한 호출(Call by Result) 값-결과에 의한 호출 (Call by Value-Result) 이름에 의한 호출(Call by Name) //값에 의한 호출 예제 #include <iostream> using namespace std; void f( int param ) { cout << "f gets : " << param << endl; param = param * param; cout << "f makes: "<< param << endl; } int main( ) int data = 4; cout << "Before: " << data << endl; f(data); cout << "After: " << data << endl;
참조에 의한 호출 값에 의한 호출과의 차이점 레퍼런스에 의한 호출은 형식인자와 실인자가 동일함.(별명!!) 함수의 호출과정에서 복사과정이 필요 없기 대문에 값에 의한 호출보다 높은 성능을 보이는 경우가 많음. //레퍼런스에 의한 호출 예제 #include <iostream> using namespace std; void f( int ¶m ) { cout << "f gets : " << param << endl; param = param * param; cout << "f makes: " << param << endl; } int main( ) int data = 4; cout << "Before: " << data << endl; f(data); cout << "After: " << data << endl;
06. 네임 스페이스 네임 스페이스(Namespace) 네임 스페이스의 필요성 엔티티의 명칭을 분류하는 논리적인 그룹 파일 영역(File Scope)는 전역 네임스페이스(Global Namespace)로 구분되며, 같은 네임 스페이스에서는 동일한 이름을 갖는 함수와 개체가 동시에 있을 수 없다. 네임 스페이스의 필요성 대형 프로젝트에서 명칭(이름)의 충돌을 방지하기 위함. 표준 라이브러리에서 사용된 명칭을 더 이상 사용할 수 없는 명칭 선점의 문제 네임스페이스를 이용할 경우 동일한 목적을 가진 함수나 변수, 클래스 등을 하나의 그룹으로 묶을 수 있어 프로그램의 가독성이나 논리적 연관성을 높일 수 있음.
네임스페이스 안에서 정의하기(1) 네임스페이스 안에서 변수, 함수, 구조체, 클래스를 정의하는 예 namespace Dog { struct Info { char name[20]; int age; }; Info dogs[20]; // 멍멍이 리스트 int count; // 전체 멍멍이들의 수 void CreateAll(); // 모든 멍멍이 생성 함수 } namespace Cat { class Info{ public: void Meow(); protected: Info cats[20]; // 야옹이 리스트 int count; // 전체 야옹이 들의 수 void CreateAll(); // 모든 야옹이 생성 함수 int count; // 전체 멍멍이와 야옹이들의 수
네임스페이스 안에서 정의하기(2) 네임스페이스를 사용하기 [28-2]
네임스페이스 지정하기 네임스페이스를 지정해서 네임스페이스 안에 정의한 이름 사용하기 // 모든 야옹이를 생성한다. Cat::CreateAll(); // 야옹이 배열에 접근한다. Cat::cats[0].Meow(); // 모든 멍멍이를 생성한다. Dog::CreateAll(); // 멍멍이의 개수를 얻어온다. int dog_count = Dog::count;
using 키워드 사용하기 using 키워드로 사용할 네임스페이스 지정하기 using 키워드로 사용할 이름 지정하기 using namespace Cat; // 모든 야옹이를 생성한다. CreateAll(); // 야옹이 배열에 접근한다. cats[0].Meow(); using Cat::CreateAll; // 모든 야옹이를 생성한다. CreateAll();
네임스페이스와 소스 파일 하나의 파일에 여러 개의 네임 스페이스가 등장할 수 있다. 여러 파일에 걸쳐서 하나의 네임스페이스가 등장할 수 있다.
07. 명시적 타입 변환 : C++스타일의 형변환 C++언어는 C언어보다 강력한 타입변환을 지향하며, 이를 위해서 타입변환 연산자 static_cast, const_cast, dynamic_cast, reinterpret_cast를 지원한다. C++ 스타일 타입변환 연산자의 사용법-e는 변환하고자 하는 수식 변환_연산자 < 변환_타입 >( e ) 암시적인 타입변환 과 명시적 타입변환 구분 설명 암시적 타입변환 개발자가 특별한 조작을 하지 않더라도 자동으로 타입변환을 수행하는 것 수식내에서의 타입변환 명시적 타입변환 암시적인 타입변환으로 정의되지 않은 모든 데이터 타입의 변환은 명시적으로 이루어짐
C++의 새로운 형변환 연산자 const_cast를 사용해서 변수의 const 속성이나 volatile의 속성을 제거할 수 있다. reinterpret_cast를 사용해서 위험한 형변환도 할 수 있다. const int ci = 100; int i = const_cast<int>( ci ); [27-5] int a, b; a = reinterpret_cast<int>( &b );
C++의 새로운 형변환 연산자 static_cast는 가장 일반적인 형태의 형변환에 사용한다. dynamic_cast A 타입에서 B 타입으로의 암시적인 형변환이 가능하다면 static_cast를 사용해서 B 타입에서 A 타입으로 형변환할 수 있다. dynamic_cast 변환을 수행하기 위해서 동적인 타입 정보(RTTI, Run-Time Type Information)을 이용함. RTTI를 이용해 타입을 실제 변환하기 전에 먼저 타입 변환이 가능한지의 여부를 보고, 올바르지 않은 형변환에 대해서는 NULL을 반환하거나 bad_cast 예외 객체를 던진다. double d = 30.0; char c; c = static_cast<char>( d ); [27-5]