|| ["AcceleratedC++/Chapter9"] || ["AcceleratedC++/Chapter11"] || ||[[TableOfContents]]|| = Chapter 10 Managing memory and low-level data structures = 지금까지는 vector, string등 STL이 기본적으로 제공하는 자료구조를 통해서 프로그래밍을 하였다. 이제 그 동작원리를 알아볼 차례이다. '''low-level'''이라는 표현은 이런 내용들이 STL구현의 근간을 이루며, 하드웨어의 동작 방식과 매우 흡사하기 때문이다. == 10.1 Pointers and arrays == 배열(array) vector와 유사하지만 vector만큼 편리하지는 않다. 포인터(pointer) 임의 접근 반복자로서, 배열의 요소들을 접근하기 위해서는 필수, 다른 용도로도 많이 이용된다. === 10.1.1 포인터 === || 포인터(pointer) || 주소를 나타내는 값 || || 주소 연산자(address operator) || 객체의 주소를 리턴한다. || || 역참조 연산자(dereference operator) || 포인터 p가 가리키는 객체를 리턴한다. || 보통 프로그래머가 포인터를 초기화시키는 값으로 이용하는 값은 0이다. 흔이 이를 '''널 포인터(null pointer)'''라고 부른다. 포인터도 타입을 갖는데 일반적으로 type-name * 으로 정의한다. {{{~cpp int *p; // *p는 int 타입을 갖는다. int* p; // p는 int*라는 것을 강조하는 표현이다. C컴파일러는 * 주변의 공백을 무시하기 때문에 상기의 2개 표현은 완전히 동일하다. }}} '''피해야할 정의 방식''' {{{~cpp int* p, q; // p는 int* 인 포인터 형식, q는 int 형을 가리킨다. }}} '''예제''' {{{~cpp //pointer_example.cpp #include using std::cout; using std::endl; int main() { int x = 5; // `p' points to `x' int* p = &x; cout << "x = " << x << endl; // change the value of `x' through `p' *p = 6; cout << "x = " << x << endl; return 0; } }}} === 10.1.2 함수에 대한 포인터 === '''※ 함수포인터를 처음으로 다루는 사람은 조금 어려울 지도 모르겠습니다. 좀 어렵더라도 C, C++에서 많이 이용되는 테크닉이니 익히는 건 필수이겠지요?''' 6.2.2 절에서 다른 함수의 인자로 함수를 전달하는 것을 보았다. 함수에 대해서 우리가 할 수 잇는 것은 그 함수의 주소를 취하거나, 실행하는 것 밖에는 없다. 함수에 대한 포인터를 인자로 전달하고 역참조 연산자를 통해서 그 포인터에 접근하면 우리는 원래의 함수에 접근하는 것이 가능하다. {{{~cpp int (*fp)(int); int next(int n) { return n+1; } fp = &next; fp = next; // 상기의 2가지 표현 모두 next함수의 포인터를 fp에 대입하는 것이 가능하다. int i = 0; int n =1; i = (*fp)(i); i = fp(i); // 마찬가지로 이 2개의 표현모두 유효한 표현이된다. }}} 6.2.2절에 존재하는 write_ayalysis의 선언구문을 살펴보도록 하자. {{{~cpp void write_analysis(std::ostream& out, const std::string& name, double analysis(const std::vector&), const std::vector& did, const std::vector& didnt); }}} 상기에서 3번째 매개변수를 살펴보도록 하자. {{{~cpp double analysis(const std::vector&) }}} C++은 상기와 같은 표현으로 매개변수를 지정하더라도 이를 자동으로 다음과 같은 형으로 자동 형 변환시킨다. {{{~cpp double (*analysis)(const std::vector&) }}} 따라서 우리가 일반적으로 사용하는 함수의 표현만으로도 매개변수로 함수를 전달시키는 것이 가능한 것이다. 하지만 이러한 자동 형 변환은 함수의 리턴형에는 적용되지 않는다. 따라서 함수를 리턴하는 경우에는 명식적으로 포인터임을 나타내야할 필요가 있다. {{{~cpp //올바른 표현 typedef double (*analysis_fp)(const vector&); analysis_fp get_analysis_ptr(); // 이와 같이 이용하는 것이 가능합니다. //올바르지 않은 표현 double (*get_analysis_ptr()) (const vector&); }}} 상기의 코드에서 프로그래머가 원한 기능은 get_analysis_ptr()을 호출하면 그 결과를 역참조하여서 const vector&를 인자로 갖고 double 형을 리턴하는 함수를 얻는 것입니다. 그렇지만 언뜻보기에도 코드가 2번째의 경우 코드가 상당히 복잡하다는 것을 알 수 잇습니다. 이런식의 문법은 많이 사용되지는 않습니다. (Addition 1에서 설명합니다.) 함수 포인터는 흔히 다른 함수의 인자로 이용된다. {{{~cpp template In find_if(In begin, In end, Pred f) { while (begin != end && if f(*begin)) //여기서 Pred는 Pred(*begin)이 의미를 갖는 모든 타입이 가용합니다. ++begin; return begin; } bool is_negative(int n) { return n<0; } vector::iterator i = find_if(v.begin(), v.end(), is_negative); // &is_negative 를 사용하지 않는 이유는 자동형변환으로 포인터형으로 변환되기 때문임. }}} === 10.1.3 배열 === '''배열(Array)''' 표준 라이브러리가 아닌 기본언어의 일부로서 하나 이상의 같은 타입의 객체들로 이루어진 시퀀스입니다. 단 배열 요소의 갯수는 컴파일 시에 알 수 있어야합니다. ''※ C99 표준에서는 변수로 크기를 받아서 동적으로 할당하는 일이 가능합니다. '' 배열은 클래스 타입이 아니기 때문에 배열의 크기를 나타내는 size_type과 같은 요소는 없습니다. 대신에 C Standard Definition 이하 '''''' 에 '''size_t'''로 지정된 unsigned 타입으로 배열의 크기를 사용해야합니다. {{{~cpp const size_t NDim = 3; const size_t PTriangle = 3; double coords[NDim]; double coords1[PTriangle]; }}} 상기와 같은 표현은 const로 지정된 변수로 변수를 초기화하기 때문에 컴파일시에 그 크기를 알 수 있다. 또한 상기와 같은 방식으로 코딩을 하면 coord에 의미를 부여할 수 잇기 때문에 장점이 존재한다. 또한 배열의 이름은 그 배열의 요소의 첫번째 요소를 가리키는 포인터형으로 초기화 되기 때문에 배열과 포인터는 밀접한 상관 관계를 갖고 잇다. {{{~cpp *coord = 1.5; //coord 배열의 첫번째 요소를 1.5로 할당한다. 즉, coord[1] = 1.5; 와 동일한 표현이 된다. }}} === 10.1.4 포인터 산술 연산 === 포인터도 일종의 반복자이다. 이사실에서 배열의 포인터를 이용한 접근을 생각해볼 수 잇다. coord가 coord의 0번째 요소를 가르키므로 || coord+1 || 1번째 요소 || || coord+2 || 2번째 요소 || || coord+3 || 3번째 요소. 배열에서는 유효하지 않지만 포인터 그 자체로는 유효한 포인터이다. || || coord+4 || 유효하지 않은 포인터 || || coord-1 || -1번째 요소. 배열에서는 유효하지 않지만 포인터 그 자체로는 유효한 포인터이다. || {{{~cpp vector v; copy(coords, coords + NDim, back_inserter(v)); }}} coord+NDim 은 coord의 마지막 요소에서 1개가 더 지난 값을 가리키므로 상기의 표현은 유효한 표현식이다. p, q가 같은 배열의 요소들의 포인터라면, p-q는 p와 q사이에 있는 요소들의 거리를 나타내는 정수가 된다. 그러나 이 차이를 나타내는 값은 구현 시스템 마다 다를 수 잇고, 음수가 나타내는 경우가 있기 때문에 ''''''에는 '''ptrdiff_t'''라는 약칭을 통해서 그 데이터 형을 제공한다. a가 n개 요소의 배열이라면, a+i가 유효하기 위해서는 0<=i<=n, a+i가 a의 요소를 가리키기 위한 필요 충분 조건은, 0<=i strlen''' {{{~cpp size_t strlen(const char* p) { // 쉬우니 기타 설명은 생략 size_t size = 0; while (*p++ != '\0') ++size; return size; } }}} 상기의 내용을 바탕으로 string에서 우리는 다음과 같은 초기화를 행하는 것이 가능하다. {{{~cpp string s(hello); string s("hello"); string s(hello, hello+strlen(hello)); }}} 상기의 3가지 표현은 모두 같다. == 10.3 Initializing arrays of character pointers == {{{~cpp //letter_grade.cpp #include #include using std::string; string letter_grade(double grade) { // range posts for numeric grades static const double numbers[] = { 97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0 }; // names for the letter grades static const char* const letters[] = { "A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F" }; // compute the number of grades given the size of the array // and the size of a single element static const size_t ngrades = sizeof(numbers)/sizeof(*numbers); // given a numeric grade, find and return the associated letter grade for (size_t i = 0; i < ngrades; ++i) { if (grade >= numbers[i]) return letters[i]; } return "?\?\?"; } }}} static 키워드를 사용하여서 배열의 초기화에 소요되는 시간을 단축한다. 요소 배열은 변화하지 않느 값이므로 const 키워드 이용 '''array_pointer / sizeof(*array_pointer)''' 를 이용하면 array가 가지고 있는 요소의 갯수를 알 수 있다. 자주쓰는 표현이므로 잘 익힌다. == 10.4 Arguments to main == 대부분의 운영체제는 프로그램이 실행될때 인자로서 문자열을 주는 것이 가능하다. 이것은 main함수가 int, char** 의 2가지의 인자를 가지기 때문이다. {{{~cpp #include using std::cout; using std::endl; int main(int argc, char** argv) { // if there are command-line arguments, write them if (argc > 1) { cout << argv[1]; // write the first argument // write each remaining argument with a space before it for (int i = 2; i != argc; ++i) cout << " " << argv[i]; // `argv[i]' is a `char*' } cout << endl; return 0; } }}} char**는 (char*)를 요소로 갖는 배열이라고 생각하면 된다. 즉 문자열 리터럴을 요소로 갖는 배열이다. 기타의 내용은 간단하므로 생략. == 10.5 Reading and writing files == === 10.5.1 표준 에러 스트림 === C++에서는 일반적인 표준 출력 스트림 뿐만아니라 표준 에러 스트림을 통해서 프로그램의 주석의 내용들. 즉 프로그램이 실행되는 동안의 상태정보를 출력하는 것이 가능하다. 대부분의 운영체제에서는 이런 2가지의 출력을 분리하여 처리하는 방법을 제공한다. || cerr || 버퍼링을 사용하지 않고, 즉각적인 출력을 한다. 오버헤드가 크다. || || clog || 버퍼링을 이용하고, 적당한 시기에 출력한다. || '''※ 출력 버퍼링에 관한 내용은 자세하게 나온 책이 많으므로 참고하면 좋을 듯. C책이기는 하지만 터보 C 정복을 보면 출력 스트림의 버퍼링에 관한 자세한 설명이 있다. ''' === 10.5.2 여러 입력 파일과 출력 파일 다루기 === 파일의 입출력을 위해서 iostream을 파일 입출력에 맞도록 상속시킨 ofstream, ifstream객체들을 이용한다. 이들 클래스는 ''''''에 정의 되어있다. 에서 사용하는 파일 이름은 char* 형이며, string 타입이 아니다. 이는 fstream library 가 만들어진 시기가 STL이 만들어진 시기 보다 앞서기 때문이다. {{{~cpp //copy_file.cpp #include #include using std::endl; using std::getline; using std::ifstream; using std::ofstream; using std::string; int main() { ifstream infile("in"); ofstream outfile("out"); string s; while (getline(infile, s)) outfile << s << endl; return 0; } }}} 만약 파일의 이름으로 string 형을 이용하고 싶다면 string형의 멤버함수인 c_str() 을 이용해서 사용하는 것이 가능하다. {{{~cpp string file("out"); ifstream infile(file.c_str()); }}} {{{~cpp //concat_files.cpp #include #include #include using std::cerr; using std::cout; using std::endl; using std::getline; using std::ifstream; using std::string; int main(int argc, char **argv) { int fail_count = 0; // for each file in the input list for (int i = 1; i < argc; ++i) { ifstream in(argv[i]); // if it exists, write its contents, otherwise generate an error message if (in) { string s; while (getline(in, s)) cout << s << endl; } else { cerr << "cannot open file " << argv[i] << endl; ++fail_count; } } return fail_count; } }}} 단순한 프로그램이므로 설명은 생략함. 한번 쳐보자. == 10.6 Three kinds of memory management == {{{~cpp int* invalid_pointer() { int x; return &x; //함수의 종료와 함께 x가 해제되므로 리턴되는 값은 무효한 메모리상의 공간을 가리킨다. } }}} {{{~cpp int* pointer_to_static() { static int x; return &x; //유효하다. static 으로 함수 안에서 선언하면 함수가 처음 실행될때 메모리 공간이 한번 할당되고 그 함 수가 종료되더라도 해제되지 않고 프로그램이 종료될때 해제된다. 따라서 메모리가 해제되지 않은 static 변수를 리턴하는 것이기에 유효하다. } }}} || 자동메모리 관리 || 지역변수, 지역변수를 참조형, 포인터형으로 리턴하면 그 리턴값은 무효한 값이된다. [[HTML(
)]] 사용하고 싶다면 '''static''' 키워드를 이용해야한다. || || 정적메모리 할당 || 정적 변수가 존재하는 블록이 처음 실행되는 동안 할당되어 프로그램이 실행되는 동안 계속 유효한 상태로 남는다. || || 동적메모리 할당 || new, delete 키워드를 이용해서 프로그래머가 원하는 시기에 메모리상에 할당하고 해제를 할 수 있다. || === 10.6.1 객체 할당 및 해제 === 객체의 타입을 T라고 가장하고, 메모리 상에 동적으로 할당하기 위해서는 {{{~cpp new T(args) }}} 와 같은 표현을 이용하면 된다. 이렇게 할당된 메모리 공간은 프로그래머가 반드시 해제해야할 책임을 갖는다. === 10.6.2 배열 할당 및 해제 === 객체의 타입을 T, 갯수를 음이아닌 정수 n이라 가장하고, 메모리 상에 동적으로 할당하기 위해서는 {{{~cpp new T[n] }}} 와 같은 표현을 이용하면 된다. 이렇게 배열로 할당되었을때 리턴되는 값은 T* 형으로 그 배열의 첫번째 요소의 포인터가 된다. 배열의 요소는 '''디폴트 생성자로 초기화'''된다. 이렇게 생성된 배열의 요소는 '''delete[]''' 키워드로 해제가 가능하다. {{{~cpp char* duplicate_chars(const char* p) { size_t length = strlen(p) + 1; char* result = new char[length]; copy(p, p+length, result); return result; }}} 간단한 소스이므로 설명생략 ---- ["AcceleratedC++"]