반응형


1. 커널 모드와 유저 모드

-메모리 영역은 사용자에 의해서 할당되는 메모리 공간인 유저 영역과 운영체제라는 하나의 소프트웨어를 실행시키기 위해서 필요한 메모리 공간인 커널 영역으로 나뉜다.

-사용자가 사용하는 메모리 영역은 유저 영역이지만 C언어는 메모리 참조가 용이하기 때문에 안정성 제공 측면에서 커널 모드와 유저 모드가 사용된다.

-기본적으로 유저 모드로 동작하다가 Windows 커널이 실행되어야 하는 경우에 커널 모드로의 전환이 일어난다.

-커널 모드와 유저 모드의 차이는 유저 모드에서 동작할 때 커널 영역으로의 접근이 금지된다. 커널 모드일 때는 모든 영역의 접근이 허용된다.

-Windows 에서 운영체제 차원에서 제공되는 시스템 함수들 중 일부는 호출시 커널 모드로 동작한다.

-주의할 점은 모드의 전환은 시스템에 부담을 준다.

-커널 모드와 유저 모드를 제공하는 대상은 Windows운영체제가 아닌 프로세서(Process)이다. 즉 메모리 보호 기능이 CPU에 달려있다.


http://blog.naver.com/cmw1728/220475950348


리눅스 시스템콜

리눅스는 유저 모드와 커널 모드 두개의 실행영역을 가진다. 유저 모드는 일반 응용 프로그램이 실행되는 영역이고, 커널 모드는 커널(:12) 명령이 실행되는 영역이다. 하드웨어에 대한 모든 제어는 커널 모드에서 이루어진다. 두개의 모드로 나눈 이유는 다중 사용자 / 다중 프로세스 운영체제에서 자원에 대한 관리와 보안 을 위함이다.

응용 프로그램은 커널 모드에 진입을 할 수 없으므로 하드웨어에 대한 접근을 할 수 없다. 이는 메모리 할당, 파일 읽기/쓰기와 같은 주요한 작업을 할 수 없음을 의미한다. 하지만 이래서는 제대로 된 응용 프로그램을 만들 수 없 을 것이다.

리눅스 커널은 "간접 접근 방식"으로 이 문제를 해결 한다. 직접 하드웨어에 접근하는 대신 커널에게 자원의 사용을 요청을 하고, 커널이 자원의 사용을 허락하는 방식이다.

이때 커널이 응용 프로그램에 제공하는 함수가 시스템콜이다.


http://www.joinc.co.kr/w/man/12/%EC%8B%9C%EC%8A%A4%ED%85%9C%EC%BD%9C







커널모드란??


커널모드를 검색하다가 이해가 안되던 도중 pdf 파일을 하나 발견하고 내용이 쉽게 이해되 바로 설명하며 포스팅을 써보았습니다.


일단 커널모드란?


리눅스 커널은 프로그램을 실행할 때 크게 두 가지의 모드에서 실행을 한다. 커널 모드 (kernel mode)와 사용자 모드(user mode)가 그것인데, 이 두 모드에 대해서 간단히 설명 을 하자면 커널 모드에서의 프로그램들은 직접적인 하드웨어 요청이나 중요한 시스템 요 청을 할 때 사용되는 모드이다. 일반적으로 사용자가 직접적으로 하드웨어 장치를 사용한 다면 큰 문제가 발생할 수도 있는 데 이것을 방지하기 위해서 커널 모드로 프로그램이 실 행되면서 하드웨어 제어를 커널에서만 할 수 있도록 한 것이다. 원래 사용자가 어느 한 프로그램을 실행하면 사용자 모드에서 실행이 되다가 그 프로그 램이 하드웨어 장치를 사용해야 할 경우 사용자 모드에서 실행되고 있던 프로그램이 커널 모드로 제어권이 넘어가서 하드웨어 장치를 사용하게 된다. 하드웨어 장치를 다 사용하게 되면 다시 프로그램은 사용자 모드로 다시 제어권이 넘어가서 나머지 프로그램을 실행하 게 된다.


설명이 쉽게 나와있는데 이걸로 이해가 안된다면

아래를 보자

*혼자 이해한거라 틀릴 수도 있어요.


1.일반 사용자가 프로그램을 사용한다.



2.일반 사용자는 하드웨어를 직접 제어하게되면 문제가 발생할수 있기에 커널 모드로 프로그램이 실행되면서 하드웨어 제어를 커널에서만 할 수 있도록 하였다.



3.프로그램이 하드웨어 사용이 다 끝나면 다시 그 프로그램은 사용자 모드로 제어권이 넘어가게 된다.


대충 이해 됬으면 개념도를 보도록 하자




이해가 다 되었어도 개념도를 보니 다시 멍해지는 기분이다...

천천히 한번 보도록하자

자료사진과 보고 참고한 곳은 : http://www.scitech.co.kr/upload/book_image/s_402/ESDP-Ch06.PDF

이걸로 많이 참고했습니다.


포스팅은 여기까지.

http://blog.naver.com/stillstick/220463132509



반응형
반응형


예전에 한번 비슷한 글을 레퍼런스 한적이 있는데 같은건지는 모르겠으나.. 괜찮은 글이라서 ㄱ ㄱ


씹어먹는 C++ 토막글 ② - 람다(lambda) 함수



출처 : http://itguru.tistory.com/196



안녕하세요? 이 글은 지난번에 우측값 레퍼런스에 관련한 글에 이어서 두 번째로 쓰는 C++ 토막글 입니다. C++ 토막글에서는 주로 C++ 11 에 추가된 최신 기술들을 다루고 있는데요, 아직 국내에 자료가 많이 부족하다 보니 체계적으로 쓰인 외국 자료들을 번역하는 형태로 제공하고 있습니다. 이 글은 http://ciere.com/cppnow12/lambda.pdf 에 올라온 pdf 자료를 바탕으로 번역된 글입니다. 사실 이 pdf 는 내용은 없고 소스만 있는 발표 자료이지만, 제가 발표자가 되었다고 가정해서 내용을 소개해보고자 합니다. 이 글이 C++ 11 의 강력한 기술인 Lambda 를 이해하는데 많은 도움이 되기를 바라겠습니다 :) 



  서론
 

어떤 벡터의 원소들의 모든 곱을 계산하는 코드를 구성한다고 생각해봅시다. 아마 가장 초보적으로 이 코드를 구성하는 방법은 아마 아래와 같을 것입니다. 


vector<int>::const_iterator iter = cardinal.begin();
vector<int>::const_iterator iter_end = cardinal.end();
int total_elements = 1;
while( iter != iter_end )
{
total_elements *= *iter;
++iter;
}

위는 반복자(iterator) 를 이용해서 cardinal 이라는 vector<int> 의 각 원소들을 순차적으로 참조해가며 total_elements 에 곱해나가는 코드입니다. 아주 직관적이고 단순하지만, C++ 을 처음 배우는, 아직 C++ 의 기능을 전부 접해보지 못한 초보자 수준의 코드이겠죠? 

만일 "나는 C++ 쫌 해" 정도 되는 사람이라는 Functor 를 이용해서 아래와 같은 코드를 짜냈을 것입니다.

int total_elements = 1;
for_each( cardinal.begin(), cardinal.end(), product<int>(total_elements) );
template <typename T>
struct product
{
product( T & storage ) : value(storage) {}
template< typename V>
void operator()( V & v )
{
value *= v;
}
T & value;
};

위 코드는 C++ 고수 답게 for_each 와 Functor 를 이용한 코드를 짜냈습니다. for_each 를 사용해서 이전 코드의 while 문 부분을 싸그리 없앨 수 있지만, 이를 위해 필요한 Functor 를 구성하는 코드가 훨씬 깁니다. 마치 배보다 배꼽이 더 큰 격이군요. 물론 전체적인 코드의 질이 높아졌다고 볼 수 있지만, Functor 을 이용하기 위해 product 라는 구조체를 생성하면서 구질구질하게 생성자도 만들고, 또 void operator() 도 정의해주어야겠죠. 상당히 귀찮은 일이 아닐 수 없습니다.

int total_elements = 1;
for_each( cardinal.begin(), cardinal.end(), [&total_elements](int i){total_elements *= i;} );

자. 그럼 위 코드를 한번 봅시다. 짧고 간결하며, 무엇 보다도 while 문이나 functor 와 같은 구질구질한 코드 없이 깔끔하게 for_each 의 특징을 그대로 살려주었다고 볼 수 있습니다. 즉 Functor 에 들어갈 내용을 product 라는 구조체를 정의하면서 쭉 써내려갈 내용을 한 번에 깔끔하게 정리해놓은 것이지요. 이것이 바로 Lambda 의 위력입니다.

간단히 Functor 를 이용한 코드와 Lambda 를 이용한 코드를 비교해 보아도 그 차이를 실감할 수 있을 것입니다.


// Functor 사용

struct mod

{

mod(int m) : modulus(m) {}

int operator()(int v){ return v % modulus; }

int modulus;

};

int my_mod = 8;

transform( in.begin(), in.end(), out.begin(),

mod(my_mod) );


// Lambda 사용

int my_mod = 8;

transform( in.begin(), in.end(), out.begin(),

[my_mod](int v) ->int

{ return v % my_mod; } );



 

 람다(Lambda) 의 구성

 


자 그럼 Lambda 를 사용하기 위해 Lambda 를 어떻게 C++ 에서 정의하는지 살펴보도록 합시다.

람다는 위 그림과 같이 4 개의 부분으로 구성되어 있습니다. 그 4 개의 부분은 각각 개시자 (introducer), 인자(parameters), 반환 타입 (return type), 그리고 함수의 몸통 (statement) 라 합니다. 일단, 람다 맨 처음에 나타나는 [] 는 개시자로, 그 안에 어떤 외부 변수를 써 넣는다면 람다 함수가 이를 Capture 해서, 이 변수를 람다 내부에서 이용할 수 있게 됩니다 (이에 대한 이야기는 뒤에서...) 위 경우 my_mod 라는 변수를 람다 내부에서 이용할 수 있게 됩니다. 


그 다음의 () 는 람다가 실행시 받을 인자들을 써 넣습니다. 위 람다는 int 형의 v_ 를 인자로 받는 군요. 여기는 그냥 실제로 함수에서 사용하는 인자 리스트와 동일하게 적어주면 됩니다. 이제, 그 옆으로 보면 -> 가 있고 반환 타입을 적어주시면 됩니다. 위 람다의 경우 int 를 리턴합니다. 마지막으로 람다 내부에서 실행할 내용을 적어주면 되는데, 위 람다의 경우 v_ 와 my_mod 를 모듈러 연산해서 그 결과를 리턴하네요. 


만일 우리가


[my_mod](int v_)->int{return v_ % my_mod;}


위와 같이 코드 상에 Lambda 를 썼다고 해봅시다. 그러면 런타임시 이름은 없지만, 메모리 상에 임시적으로 존재하는 클로져 (Closure) 객체가 생성됩니다. 이 클로져 객체는 함수 객체(function object)처럼 행동합니다. (이러한 연유로 람다를 람다 함수라고 부르는 경우가 있습니다 - 사실 엄밀히 말하면 클로져 객체지 함수는 아닙니다)


그렇다면


[](){ cout << "foo" << endl; }()


를 실행하였을 때 어떠한 결과가 나올까요? 일단 


[](){ cout << "foo" << endl; }


로 임시적인 클로져 객체가 생성되었는데 () 를 붙여서 바로 이 임시 클로져 객체를 실행시켜 버리지요. 위 람다는 Capture 하는 변수들도 없고, 인자로 받는 것도 없고 리턴 타입도 없고 (참고로 리턴 타입이 void 일 경우 -> 를 생각 가능합니다) 함수 몸통만 덜렁 있기에 특별히 생각할 것도 없이 함수 몸통만 덜렁 실행되서




라고 나오게 됩니다.


그러면 조금 더 복잡한 예제를 살펴볼까요?


[](int v){cout << v << "*6=" << v*6 << endl;} (7);


는 어떨까요. 


  [](int v){cout << v << "*6=" << v*6 << endl;}


부분에서 인자로 v 를 받는 클로져가 생성되었는데, (7) 로 이 클로져에 인자로 7 을 전달시키면서 실행시켜버립니다. 따라서 모두가 예상 하였던 결과인



가 나오겠네요.


람다 자체가 함수 처럼 자유롭게 사용할 수 있는 것이기 때문에 인자로 (당연히) 레퍼런스 들도 전달 가능합니다. 예를 들어


int i = 7;

[](int & v){ v*= 6; } (i);

cout << "the correct value is: " << i << endl;


를 실행해보면



가 나옵니다.


참고로 받는 인자가 없을 경우, 예컨대 


[](){ cout << "foo" << endl; } 


의 경우 인자 () 를 생략 할 수 있습니다. 즉, 


[]{ cout << "foo" << endl; }


도 동일한 의미입니다. (하지만 [] 는 지울 수 없습니다! ) 


  Capture 
 


사실 많은 경우 우리의 람다 안에서 람다 밖에 있는 변수들에게 접근하고 싶을 때가 있을 것입니다. 물론 "그렇다면 그 변수들을 그냥 인자로 받아버리면 되자나!" 라고 반문할 수 도 있겠지만, for_each 나 fill, transform 등의 C++ 의 파워풀한 STL 을 수행하기 위해서는 인자들을 맞추어 주어야 하는데 이 때문에 함수 내부로 전하고 싶어도 전달하지 못하는 인자들이 있기 마련 입니다. 


따라서 이를 방지하기 위해, 람다 내부와 소통할 수 있는 또다른 문, Capture 를 제공하고 있습니다. Capture 하고자 하는 내용은 앞에서 말했듯이 [] 안에 들어오게 되는데, 대표적으로 아래의 4 개의 형태들이 있습니다.


  1. [&]() { . . . } 외부의 모든 변수들을 레퍼런스로 가져온다. (함수의 Call - by - reference 를 생각)
  2. [=]() { . . . } 외부의 모든 변수들을 값으로 가져온다. (함수의 Call - by - value 를 생각)
  3. [=, &x, &y] { . . . }, [&, x, y] { . . . } 외부의 모든 변수들을 값/레퍼런스로 가져오되, x 와 y 만 레퍼런스/값으로 가져온다
  4. [x, &y, &z] { . . . } 지정한 변수들을 지정한 바에 따라 가져온다. 

그렇다면 한 번 예제를 살펴볼까요.

int total_elements = 1;
vector<int> cardinal;

cardinal.push_back(1);
cardinal.push_back(2);
cardinal.push_back(4);
cardinal.push_back(8);

for_each( cardinal.begin(), cardinal.end(),[&](int i){ total_elements*= i; } );

cout << "total elements : " << total_elements << endl;

위 코드에서 cardinal 에는 1, 2, 4, 8 이라는 원소들이 들어있고 그것을 for_each 를 통해 순회하면서 total_elements 에 곱하게 됩니다. 이 때 Capture 는 & 로 이므로 total_elements 를 Capture 할 수 있고, 람다 외부 변수인 total_elements 를 성공적으로 바꿀 수 있었던 것이죠. 위 코드를 실행하면 예상하던대로



가 나오게 됩니다.

이번에는 조금 더 복잡한 예제를 살펴보도록 합시다. 

template< typename T >
void fill( vector<int> & v, T done )
{
int i = 0;
while( !done() )
{
v.push_back( i++ );
}
}

vector<int> stuff;
fill( stuff,
[&]()->bool{ return stuff.size() >= 8; } );

for_each (stuff.begin(), stuff.end(), [](int i) {cout << i << " " ;});

참고로 클로져 객체는 분명 특정 타입의 객체 이므로 위와 같이 template 에서 typename T 로 받을 수 있습니다. 위의 fill 함수는 특정 타입 T 의 변수 done 으로 클로져 객체를 받았습니다. 이 때, 클로져 객체 자체는 이미 stuff 를 Capture 해서 stuff 에 대한 레퍼런스를 계속 가지고 있는 상태이고, fill 의 while 문에서 돌면서 stuff 의 크기가 8 이하 일 때 까지 수행되게 됩니다. 따라서


로 출력됩니다.



void fill( vector<int> & v, T done )
{
int i = 0;
while( !done() )
{
v.push_back( i++ );
}
}

vector<int> stuff;

fill( stuff,
[&]()->bool{ int sum=0;
for_each( stuff.begin(), stuff.end(),
[&](int i){ sum += i; } );
return sum >= 10;
}
);
for_each (stuff.begin(), stuff.end(), [](int i) {cout << i << " " ;});

머리를 쫌만 굴려보면, 현재 stuff 의 원소 합이 10 이하일 때 까지 stuff 의 원소를 추가하는 람다라고 볼 수 있습니다.
당연히 그 결과는



 한 가지 흥미로운 점은 Capture 를 레퍼런스가 아닌 값으로 할 때 언제 Capture 가 되냐는 것입니다.


int v = 42;

auto func = [=]{ cout << v << endl; };

v = 8;

func();


과연 위 소스에서 v 는 func 이 처음 정의될 때, 즉 클로져 객체가 생성될 때 Capture 될까요, 아니면 func 이 실행될 때 일까요? 만일 전자라면 42 가 출력될 것이고 후자라면 8 이 출력될 것입니다. 


과연!

 

흥미롭게도 람다는 클로져 객체가 처음 생성될때 변수들의 값을 Capture 합니다.


Capture 를 값으로 할 때 주의점은 그 변수들에는 자동으로 const 속성이 붙는 다는 것입니다. 즉 값으로 Capture 시 그 변수들의 내용을 바꿀 수 없습니다. 따라서 아래와 같은 코드는


int i = 10;

auto two_i = [=]()->int{ i*= 2; return i; };

cout << "2i:" << two_i() << " i:" << i << endl;


컴파일 오류 " 'i': a by-value capture cannot be modified in a non-mutable lambda " 가 나게 됩니다. 위 코드에서 i 는 분명히 값으로 받았으므로 const 인데, i *= 2 를 통해 i 의 값을 바꾸려 하고 있으니 오류가 발생한 것입니다. 하지만, 함수 내부에서 i 의 값을 바꾸고자 하면 어떨까요? (물론 실제 외부의 i 의 값은 바뀌지 않습니다... 함수 내부에서만 - 마치 지역 변수처럼 말이죠) 


답은 간단합니다. 람다에 mutable 속성을 추가해주면 됩니다.


int i = 10;

auto two_i = [=]() mutable ->int{ i*= 2; return i; };

cout << "2i:" << two_i() << " i:" << i << endl;


이로써 람다 내부에서 i 의 값을 변경할 수 있습니다. 물론, 다시 말하지만 외부의 i 의 값이 바뀌는 것이 아닙니다. 오직 람다 함수 내에서  '어떤 다른 i ' 의 값이 두 배가 되는 것이지요. 그 결과



로 나타남을 알 수 있습니다.


이제 그럼 조금 복잡한 코드를 살펴볼까요.


class gorp

{

vector<int> values;

int m_;

public:

gorp(int mod) : m_(mod) {}

gorp& put(int v){ values.push_back(v); return *this; }

int extras()

{

int count = 0;

for_each( values.begin(), values.end(),

[=,&count](int v){ count += v % m_; } );

return count;

}

};


gorp g(4);

g.put(3).put(7).put(8);

cout << "extras: " << g.extras();


사실 위 코드는 상당히 재미있는 코드입니다. extras 함수를 호출하면 람다가 각 원소를 4 로 나눈 나머지들의 합을 구해서 더해주는데요, 한 가지 궁금한점! 과연 람다에서 어떻게 m_ 을 capture 할 수 있었을까요? 람다는 여기서 암묵적으로 (implicit) 클래스의 this 를 Capture 했기 때문에 m_ 을 접근할 수 있었던 것입니다. 


따라서 위 코드는




으로 성공적인 결과를 보여줍니다.


이렇게 this 를 암묵적으로 Capture 할 수 있기에 아래와 같은 놀라운 일도 발생할 수 있습니다.


struct foo

{

foo() : i(0) {}

void amazing(){ [=]{ i=8; }(); }

int i;

};

foo f;

f.amazing();

cout << "f.i : " << f.i;


위 코드를 언뜻 보면 i 를 값으로 capture 햇는데 어떻게 8 을 대입할 수 있냐고 물을 수 있는데, 사실 this 를 Capture 해서 this.i = 8 을 통해 mutable 없이도 값을 바꿀 수 있습니다. 왜냐하면 분명 this.i = 8 에서 상수인 this 를 변경한 것은 아니기 때문이죠.



  Capture 의 범위
 


Capture 되는 개체들은 모두 람다가 정의된 위치에서 접근 가능해야만 합니다. 예를 들어


int i = 8;

{

int j = 2;

auto f = [=]{ cout << i/j; };

f();

}


의 코드는 람다의 위치에서 i, j 모두 접근 가능하기 때문에 Capture 가능하므로 정상적으로



가 나옵니다.


그렇다면 아래 코드는 어떨까요?


int i = 8;

auto f =

[i]()

{

int j = 2;

auto m = [=]{ cout << i/j; };

m();

};

f();


바깥의 람다에서 i 를 Capture 하였기에, 바깥의 람다 몸통 안에서 i 를 사용할 수 있겠지요. 따라서 내부의 람다는 i 를 Capture 할 수 있게 됩니다. 그렇기에, 위와 동일한 결과인




가 나오게 됩니다. 하지만, 만일 바깥의 람다에서 i 를 Capture 하지 않았다면 어떨까요.


int i = 8;

auto f =

[]()

{

int j = 2;

auto m = [=]{ cout << i/j; };

m();

};

f();


그러면 예상했던 대로 컴파일 오류 error C3493: 'i' cannot be implicitly captured because no default capture mode has been specified 가 나오게 됩니다. 


조금 더 복잡한 예로 아래의 코드를 살펴봅시다.


int i = 8;

auto f =

[=]()

{

int j = 2;

auto m = [&]{ i /= j; };

m();

cout << "inner: " << i;

};

f();

cout << " outer: " << i;


일단 바깥의 람다는 i 를 값으로 Capture 하였기 때문에 바깥의 람다(f) 몸통에서는 i 에 const 속성이 붙습니다. 그런데, 내부의 람다(m) 가 그 i 를 레퍼런스로 Capture 해서 값을 변경하려고 했습니다. 그렇다면, 당연히 오류가 나겠지요. 실제로 컴파일 오류 'i': a by-value capture cannot be modified in a non-mutable lambda 가 발생하게 됩니다.


이를 해결하려면, 당연히도 mutable 속성을 붙여주면 됩니다. 


int i = 8;

auto f =

[i]() mutable

{

int j = 2;

auto m = [&, j]() mutable{ i /= j; };

m();

cout << "inner: " << i;

};

f();

cout << " outer: " << i;


i 자체가 값으로 입력 되었기 때문에 outer i 의 값은 바뀌지 않고 8 로 남아 있고, 값으로 받은 i 가 m 에 의해서 2 로 나눠지므로 4 가 됩니다. 따라서, 그 결과 




로 나오게 되죠.



 

 클로져 객체의 복사 생성자와 소멸자

 


모든 클로져 객체들은 암묵적으로 정의된 복사 생성자(copy constructor)와 소멸자(destructor)를 가지고 있습니다. 이 때 클로져 객체가 복사 생성 될 때 값으로 Capture 된 것들의 복사 생성이 일어나겠지요. 아래의 예를 한번 보도록 하겠습니다.


일단 


struct trace

{

trace() : i(0) { cout << "construct\n"; }

trace(trace const &) { cout << "copy construct\n"; }

~trace() { cout << "destroy\n"; }

trace& operator=(trace&) { cout << "assign\n"; return *this;}

int i;

};


와 같이 생성, 복사 생성, 소멸, 그리고 대입 연산을 확인할 수 있는 trace 라는 구조체를 정의해놓고 


trace t;

int i = 8;


auto f = [=]() { return i / 2; };


를 한다면 어떻게 나올까요? f 에서 t 를 사용하지 않았으므로, t 를 Capture 하지 않게 됩니다. 따라서 그냥



이 나오게 됩니다.


그렇다면 아래의 예는 어떨까요


trace t;

int i = 8;


auto m1 = [=]() { int i = t.i; };

cout << " --- make copy --- " << endl;


auto m2 = m1;


먼저 m1 을 생성하면서, 람다가 t 를 Capture 하였으므로 t 의 복사 생성자가 호출되게 됩니다. 왜냐하면 값으로 받았기 때문이지요. 만일 레퍼런스로 받았다면 복사 생성자가 호출되지 않았을 것입니다 (확인해보세요!) 그리고 아래의 auto m2 = m1; 에서 클로져 객체의 복사 생성이 일어나는데, 이 때, 클로져 객체의 복사 생성자가 값으로 Capture 된 객체들을 똑같이 복사 생성 해주게 됩니다. 따라서 또 한번 t 의 복사 생성자가 호출되겠지요. 그 결과 아래와 같이 출력됩니다.






 

 람다의 전달 및 저장

 

람다를 저장 및 전달하는 방식으로 앞에서 두 가지 방법을 보았습니다. 바로


template<typename T> void foo(T f)

auto f = []{};


이지요. 우리가 만들어낸 클로져 객체의 타입이 정확히 무엇인지 몰라도 위와 같은 방법으로 성공적으로 처리할 수 있습니다.


또 다른 방법으로는 함수 포인터를 이용하는 방법이 있는데요, 이 경우 람다가 Capture 하는 것이 없어야만 합니다. 


typedef int(*f_type)(int);

f_type f = [](int i)->int{ return i+20; };

cout << f(8);


(참고로 위 기능은 Visual Studio 2010 에서 지원되지 않습니다 - 그 후의 버전에서만 가능합니다)


위 역시 성공적으로 28 을 출력함을 알 수 있습니다.


그런데, C++ 11 에서는 클로져 객체를 전달하고 또 저장할 수 있는 막강한 기능이 제공됩니다. 바로 std::function 인데요, 그 어떤 클로져 객체나 함수 등을 모두 보관할 수 있는 만능 저장소 입니다. (참고로 std::function 은 Visual Studio 2010 에서 <functional> 을 include 해야 합니다)


std::function< int(std::string const &) > f;

f = [](std::string const & s)->int{ return s.size(); };

int size = f("http://itguru.tistory.com");


cout << size << endl; 


std::function 은 위와 같이 std::function < 반환 타입 ( 인자 ) > 와 같은 형태로 쓰며, Capture 가 있어도 상관이 없습니다. 물론 위 코드는 실행하면 




와 같이 잘 나오지요.


이 std::function 을 통해 아래와 같이 재밌는 코드도 짤 수 있습니다.


std::function<int(int)> f1;

std::function<int(int)> f2 =

[&](int i)->int

{

cout << i << " ";

if(i>5) { return f1(i-2); }

};

f1 =

[&](int i)->int

{

cout << i << " ";

return f2(++i);

};

f1(10);


이것이 가능한 이유는 만일 auto 를 이용하였더라면 auto f1 을 한 시점에서 f1 이 명확히 구현이 되어야 컴파일러에서 타입을 추정할 수 있는데, 위와 같은 경우 f1 을 구현하려면 f2 를 먼저 구현해야 하고, 또 f2 를 구현하려면 다시 f1 을 먼저 구현해야 하는 순환적인 논리 딜레마에 빠지게 됩니다. 따라서 function 을 이용해서 f1 을 선언만 해 놓은 뒤, f2 를 구현하고, 다시 f1 을 구현하면 됩니다. 


위 코드를 실행하면




와 같이 잘 나옴을 알 수 있습니다.


마찬가지로 아래와 같은 재귀 호출 함수도 구현할 수 있습니다.


std::function<int(int)> fact;

fact =

[&fact](int n)->int

{

if(n==0){ return 1; }

else

{

return (n * fact(n-1));

}

};

cout << "factorial(4) : " << fact(4) << endl;


이 역시 auto 를 이용했더라면, 처음 Capture 부분에서 Capture 하는 대상의 타입이 명확히 정해지지 않은 상태이므로 컴파일러가 타입을 추정할 수 없게 됩니다. 하지만 function 을 이용해서 성공적으로 구현할 수 있습니다. 위 계산 결과는 당연히




가 나오겠지요.



  마치며
 


C++ 에 새롭게 추가된 람다는 기존의 C++ 과 전혀 다른 새로운 개념 입니다. 하지만 람다를 이용하면 수십줄의 코드도 한 두 줄로 간추릴 수 있는, 엄청난 기능이 아닐 수 없습니다. 


이제 여러분들 손에는 람다라는 막강한 도구가 주어졌습니다. 이를 어떻게 사용하느냐는 여러분의 몫이지요 :) 


그리고 이런 훌륭한 강의를 제공해주신 Michael Caisse 님에게 감사의 말을 전합니다. 

반응형
반응형
 

하드 코딩 (Hard - coding)

하드코딩: 설정사항이나 코드 등의 시스템적으로 사용하는 변수를 변수를 사용하지 않고 

              값을 직접 소스코드에 박아서 사용하는 방식을 말한다.

              코드가 바뀌었을 경우 자동으로 반영되지 않기 때문에 이후에 버그가 발생할 위기가 많은 시한폭탄 같은 방식이다.

               

1
2
3
4
5
6
int main()
{
    const char *filename = "C:\\myfile.txt";

    printf("Filename is: %s\n", filename);
}


위 예제 코드에서 'C:\\myfile.txt' 가 하드코딩에 해당한다.

만약 myfile.txt 의 경로가 C드라이브가 아닌 다른 경로에 있을경우 어떻할 것 인가? 

그때 마다 다시 코드를 수정하고 재 컴파일하는 번거로움을 감수할 것인가?




또다는 예로 구구단을 출력하는 프로그램을 짠다고 가정할때....

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1)

int a;

for(int i = 0; i < 9; i++)
{
printf("%d * %d = %d", a, (i + 1), a * (i + 1));
}

//의 방식으로 짤 수도 있겠지만

2)
if (a == 1)
{
printf("1 * 1 = 1");
printf("1 * 2 = 2");
...
printf("1 * 8 = 8");
printf("1 * 9 = 9");
}
if (a == 2)
{
...
}
if (a == 3)
...
...
...
if (a == 9)
{
printf("9 * 1 = 9");
printf("9 * 2 = 18");
...
printf("9 * 8 = 72");
printf("9 * 9 = 81");
}

//위와 같은 방식으로 짤수도 있는데 2)번과 같은 방식의 코딩이 하드코딩이다.




한마디로 하드 코딩이란 코딩 방식의 일종으로 외부 입력에 대한 유연성이 없고 확장성이 낮은 코딩을 말한다.

하지만 단점만 존재 하는 것은 아니며 코드의 목적이 매우 직관적이라 가독성도 괜찮은 편이고

컴퓨터의 입장에서는 자잘한 체크나 유효성 검사 등이 빠지고 반복문이 줄어들어 속도가 올라가므로

정말 절대로 변경되지 않을 것이라고 자신할 수 잇는 작은 부분에 한정적으로 사용하면 나름대로 

효과를 거둘 수 있기는 하다.


하지만 요즘 같은 시대는 하루에도 수십번 자주 변경이 일어나고 

외부 입력 자체도 다양한 프로그램을 많이 개발하는 시대이기 때문에 

하드코딩은 악의 근원지라는 표현에 걸맞는 방식이다.



http://codinglove.tistory.com/79





반응형
반응형

[기본 용어 설명]

- 바이트 패딩: 

몇몇 컴파일러는 구조체의 필드를 메모리에 위치시킬때 중간에 빈공간없이 쭉 이어서 할당하는 경우도 있지만 대부분의 컴파일러는 성능향상을 위해 CPU가 접근하기 쉬운 위치에 필드를 배치하는데(OS나 컴파일러에 따라 4바이트단위 또는 8바이트 단위로 끊는작업을 함) 이를 "바이트 패딩"이라고 하며, 그러다보니 중간이 빈 공간이 들어가게 되는데 이 빈 공간을 "패딩비트"라 한다.  

 

* 바이트패딩을 사용했을때 왜 성능향상에 도움을 줄까? 그 해답은 아래 링크를 통해 확인바람

http://pangate.com/19

 

이러한 바이트 패딩 때문에 구조체 정의를 어떻게 하느냐에 따라 구조체의 크기가 달라진다. 아래 예제를 살펴보자.

 

예)







 

- 위에서 보는것 처럼 구조체 ST_TEST1, ST_TEST2의 멤버변수는 동일하다. 하지만 변수의 선언 순서에 따라 바이트패딩이 적용되서 사이즈가 다르게 된다. 

바이트 패딩 의해

ST_TEST1 은

[char c1 (1) | empty(1) | short s1(2)]   ||   [int i1(4)]                    = 총 8바이트

ST_TEST2 는

[char c1(1) | empty(3)]   ||   [int i1(4)]   ||   [short s1(2) | empty(2)] = 총 12바이트

 

* empty : 빈공간, 즉 패딩비트 [] : 4바이트씩 모아놓은것.

* 실제 ST_TEST1 정렬방법(char 뒤에 1바이트 패딩비트가 생기는지)을 확인하고 싶으면 디버깅 - 창 - 조사식에 각 구조체의 변수의 주소를 확인해보면 된다. 아래 스샷을 보면 c1과 s1 사이에 1바이트 빈공간이 존재한다.  

 

 

이러한 문제를 해결할수 있는 방법이 바로 #paragma pack 이다.  

 

사용 방법은 2가지다.

 

1. #pragma pack(1)  - #pragma pack(4)

1바이트 정렬 방식이 필요할때 필요한 구문에서

#pragma pack(1) 을 써주고 (2바이트 정렬이 필요하다면 #paragma pack(2))

사용이 끝난 시점에서 다시 #pragma pack(4) 를 통해 바이트 정렬 방식을 4바이트로 되돌려 놓는다.

 

※  

Q: 왜 굳이 1바이트 정렬 후 다시 4바이트 정렬로 되돌려야하는가??

A:  PC의 CPU에 따라 레지스터를 읽는 바이트 사이즈가 다른데 32비트 운영체제의 경우 4바이트씩 읽는다 . 이는 성능과 연관있다. (자세한 내용은 컴퓨터 구조 공부를... ) 따라서 필요시에만 정렬방법을 바꿨다가 다시 기존 바이트 정렬 방식으로 돌려 놓아야한다.

 

2. #pragma pack(push, 1)  - #paragma pack(pop)

- #pragma pack(push, 1) 을 통해 구조체 정렬방식을 가지고 있는 스택에 1바이트 정렬방식을 push 를 통해 입력하고 ( 이 선언 이후부터는 정렬방식이 1바이트 바뀜)  사용 후에 이전 정렬방식( 이전 방식이 꼭 4 라는 법은 없음)되돌릴때 pop을 통해 이전에 push를 통해 입력했던 정렬방법(1)을 없앤다. 이를 위해 #pragma pack(pop) 을 사용한다.

* push 와 pop 이 뭔지 생소하다면 스택(Stack)에 대해 공부할것.

 

사용 예)

 

- 스샷 화면 속 빨간 네모 속 ST_TEST2 사이즈를 보면 7로 변해있는것을 확인할수 있다.

#paragma pack 을 쓰기 전에는 12바이트.

 

★ 그렇다면 가장 중요한 포인트! 이 #pragma pack 어디다 쓰면 유용한 건데???

moon_and_james-11

- 네트워크를 통한 구조체 전송시 바이트 패딩은 중요하다!

 

예) 만약 A시스템(32비트) B시스템(64비트)라고 가정, A시스템에서 B시스템으로 아래와 같은 구조체 패킷을 보낸다고 하면

struct ST_TEST

{

char c1;

int i1;

};

B 시스템은 바이트 패딩에 의해 A시스템과는 달리 8바이트단위로 끊는다. 따라서 첫번째 c1값만 정상이고 나머지 값들은 이상한값이 되버린다.

따라서 A시스템, B시스템 모두 바이트 패딩을 없애야 이러한 문제를 없앨수 있는데 이 해결방법이 위에 설명했던 #paragma pack 이다.  



http://blog.naver.com/dkdldhekznal/220206729625

반응형
반응형

WinCE라는 넘은 간혹 참 당확 스럽게 하는 경우가 있다.

 

일반 적으로 Release Mode에서는 해당(내가 작업할려는 Driver혹은 Sources가 있는 어떤 거라도) Optimize 가 Enable이 된다.

 

그런데 간혹 Driver작업이나 좀더 Critical한 Kernel작업을 하다가 보면 문제를 발생 시키기도 한다.

 

예를 들어 특정 Chips에서 아래와 같은 작업이 반드시 필요하다고 한다면

A = 0;

A = 1;

A = 0;

WinCE Compiler는 워낙 탁월한(?) 넘이라 "어라 결국 A=0 이내" 하고 위의 두 Code를 날려 버리는 경우가 발생한다.

 

일반 Code라면 뭐 그렇지만 Chip Register라던가 Peri.의 Reset신호라던가 할경우 위의 3 Code가 모두 필요한 상황인데,

 

간혼 최종 Code만 남는 경우가 발생하게 된다.

 

이럴 경우 해당 드라이버의 Optimize 를 Disable 할수도 없고 (물론 CONLY_FLAGS=$(CONLY_FLAGS) /Od 으로 할수도 있지만.

 

Optimize함으로 써 얻는 이익은 모드 포기 해야한다는, 쩝...)

 

그럴 경우 해당 루틴이있는 Function에 대해서만 Opimize Disable을 할수있는 방법을 WinCE는 제공한다.(방법 을 제공한다는 것은

 

결국 저런일이 빈번하게 일어날수 있다는 것을 시인하는 것일까? )

 

방법 : 아래와 같이 하면 끝

#pragma optimize( "g", off )

void example_function(void)

{

 .....

A = 0;

A = 1;

A = 0;

}

#pragma optimize( "g", on)

 

PS : 해당 Code가 정상적으로 생성되어 컴파일 되어 있는지는 해당 파일을 Assembly 파일로 만들어서 해당 Code를 확인하면 된다.

ms-help://MS.WindowsCE.500/wcepbguide5/html/wce50confilealternatives.htm

sources에 추가 : CONLY_FLAGS=$(CONLY_FLAGS) /FA




http://seungwan76.blog.me/140109794967



반응형
반응형


람다 함수 활용(1)

지금까지 First-Class Object, Higher-Order Function, Closure와 같은 조금은 지루한 개념들을 살펴봤습니다. 이런 개념들을 설명하지 않으면 람다 함수를 함수 포인터나 함수 객체를 대체하는 Syntactic Sugar로 오해할 수도 있기 때문입니다. 또 어떻게 람다 함수를 일반 변수처럼 사용할 수 있는지 배경 지식을 설명하기 위해서였습니다. 

그럼 이 람다 함수를 어디에 사용하면 좋을까요?앞으로 4가지 활용 예를 살펴 보겠습니다.


① 지연 호출(Deferred Call)에 활용
vector와 같은 자료구조에 저장한 후에 필요한 특정 시점에 호출하는 것입니다.
아래 예제에서는 람다 함수를 이용해 Task를 만들고 TaskManager라는 컨테이너 클래스를 통해 특정 시점에 람다 함수가 실행되는 것을 나타내고 있습니다.

TaskManager manager;

 

// TaskManager에 Task로 저장한다.

manager.AddTask( [](){ cout << “task1” << endl;} );

manager.AddTask( [](){ cout << “task2” << endl;} );

 

 

// 같은 스레드 혹은 다른 스레드에서 실행시킨다.

manager.Run();



② 비동기 호출과 결과 코드의 응집성을 높이는 데 활용

비동기 처리 코드의 문제점은 비동기 요청 함수를 호출하는 곳과 결과를 처리하는 함수가 동떨어져 있어 로직 흐름을 파악하기 어렵다는 것입니다. 비동기 요청 함수를 호출할 때 결과 처리에 대한 코드를 람다 함수로 구현해 파라미터로 전달하면 코드 응집성이 높아질 수 있습니다.
아래 예제에서는 Client 클래스의 요청에 대한 처리가 비동기적으로 이뤄질 때 람다 함수를 이용해 요청 시점에 결과를 어떻게 처리할 것인지 기술하는 것을 나타내고 있습니다.

 

class Client

{

public:

// 비동기 요청에 대한 결과를 처리할 람다 함수 타입

typedef function<void(int result)> AsynResultProcessor;

 

public:

void AsyncRequest(AsynResultEvent resultEvent)

{

resultEvent_ = resultEvent;

}

 

void Process()

{

Result_ = 1004;

 

// 미리 저장된 결과 처리 람다 함수를 호출한다.

resultProcessor_(result_);

}

private:

AsynResultProcessor    resultProcessor_;

int                      result_;

};

 

Client client;

 

// 응답을 어떻게 처리할 것인지 요청 시 기술할 수 있다.

client.AsyncRequest( [](int result)

{

// 어떻게 결과를 처리할 것인지 기술

cout << result << endl;

});

 

// 특정 시점에 처리한다.

client.Process();




③ 일회성 함수를 쉽게 구현하기 위해 활용

특히 STL을 이용하는 데 유용합니다. STL 알고리즘 함수들의 입력 파라미터로 람다 함수를 넘겨주면 따로 함수 객체를 정의하는 번거로움이 사라지고 코드 응집성이 높아지므로 STL 함수의 작동을 더 쉽게 이해할 수 있습니다. 아래 예제에서는 함수 객체와 람다 함수 이용을 비교해서 보여주고 있습니다.

 

//  for_each()를 위한 함수 객체

//  일회성 호출을 위해 많은 코드가 필요하고 가독성도 떨어진다.

struct LambdaFunctor

{

void operator()(int n) const

{

cout << n << " ";

}

};

 

int main()

{

vector<int> v;

 

for (int i = 0; i < 10; ++i)

{

v.push_back(i);

}

 

// 1. 함수 객체를 이용한 코드

for_each(v.begin(), v.end(), LambdaFunctor());

 

// 2. 람다 함수를 이용한 코드

for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

 

cout << endl;

}




포스팅이 길어졌네요~ 
다음 글에서 람다 활용에 대해 마저 이야기 하고 "람다 이야기" 시리즈를 마치도록 하겠습니다.



http://tedahn.tistory.com/24







람다 함수 활용(2)

앞선 포스팅에서 람다 함수의 캡쳐(Capture) 기능에 대해 설명 했었습니다. (
여기! 있습니다)
캡쳐 기능을 사용 하면 다음과 같이 활용 할 수 있습니다.

④ 템플릿을 대체하는 데 활용
람다 함수의 캡처 기능을 사용하면 람다 함수 몸체에서 외부 변수들을 마음껏 사용할 수 있을 뿐만 아니라 클로저(Closure)가 되어 함께 묶입니다. 이 기능을 활용하면 기존에 파라미터 타입과 개수 처리를 일반화하기 위해 사용하던 템플릿 사용을 지양할 수 있습니다. 아래 두 함수가 템플릿과 람다 함수를 이용해서 원하는 시점에 호출 되게 하려면 어떻게 하면 될까요?

// 템플릿 객체와 람다 함수를 이용해 간접적으로 호출될 예제 함수들


void Function_Arg1(int arg1)

{

cout << "Function_Arg1 : " << arg1 << endl;

}

 

void Function_Arg2(int arg1, const char* arg2)

{

cout << "Function_Arg2 : " << arg1 << "," << arg2 << endl;

}



위 두 함수를 특정 파라미터와 묶어 특정 시점에 호출해서 사용하고 싶을 경우 템플릿을 이용하면 아래와 같은 방법으로 사용할 수 있습니다.

// 템플릿 객체를 이용해 함수를 인자와 함께 묶어 특정 시점에 호출하는 예


int arg1 = 1004;

const char* arg2 = "Lambda!";

 

vector<ITask*> taskList;

 

// 1. 컨테이너에 담는다.

taskList.push_back( new Task_1<void, int>(&Function_Arg1, arg1) );

taskList.push_back( new Task_2<void, int, const char*>(&Function_Arg2, arg1, arg2) );

// 2. 특정 시점에 컨테이너를 순회하며 실행시킨다.

for( auto i = taskList.begin(); i != taskList.end(); ++i )

{

(*i)->Do();

}



위와 같은 코드로 템플릿 객체를 사용하려면 다음과 같은 코드가 필요합니다.

// 파라미터 1개짜리 함수를 담기 위한 템플릿 클래스


template<typename RetType, typename ArgType1>

class Task_1 : public ITask

{

typedef function<RetType(ArgType1)> FunctionType;

 

public:

Task_1(FunctionType f, ArgType1 a1)

: function_(f), arg1_(a1)

{

}

 

virtual void Do()

{

cout << "Task_1::Do()" << endl;

function_(arg1_);

}

private:

FunctionType   function_;

ArgType1       arg1_;

};

 

// 파라미터 2개짜리 함수를 담기 위한 템플릿 클래스

template<typename RetType, typename ArgType1, typename ArgType2>

class Task_2 : public ITask

{

typedef function<RetType(ArgType1,ArgType2)> FunctionType;

 

public:

Task_2(FunctionType f, ArgType1 a1, ArgType2 a2)

: function_(f), arg1_(a1), arg2_(a2)

{

}

 

virtual void Do()

{

cout << "Task_2::Do()" << endl;



실제 C++ 프로젝트를 진행하다 보면 위와 비슷한 형태의 템플릿 클래스를 자주 구현하게 됩니다. 이 때 발생하는 문제점은 함수의 파라미터가 늘어날 때마다 템플릿 클래스를 추가해 줘야 하고 코드가 직관적이지 않다는 점입니다. BOOST_PP를 이용하면 자동화할 수 있지만 디버깅이 굉장히 어렵고 작성자만 이해할 수 있는 코드가 만들어지곤 합니다. 때론 작성자도 이해 못하죠 ;)

그럼 이런 상황에서 람다 함수를 이용하면 어떨까요? 
아래 예제를 보면 람다 함수의 캡처 기능을 이용해 깔끔하게 구현되는 것을 볼 수 있습니다. 뿐만 아니라 실행시킬 함수의 파라미터가 몇 개든 타입이 무엇이든 추가되는 코드는 없습니다. 앞으로 많은 부분에서 람다 함수를 이용해 템플릿 사용을 줄일 방안이 제안 되길 기대해 봅니다.


// 람다 함수를 이용해 함수와 인자를 묶어 특정 시점에 호출하는 예제


int arg1 = 1004;

const char* arg2 = "Lambda!"; 

...
...
typedef function<void(void)> LambdaType;


vector<LambdaType> lambdaList;

 

lambdaList.push_back( [=](){ Function_Arg1(arg1); } );

lambdaList.push_back( [=](){ Function_Arg2(arg1, arg2); } );

 

for_each( lambdaList.begin(), lambdaList.end(), [](LambdaType lambda)

{

lambda();

});




마치면서

4회에 걸쳐『Plus C++0x』람다(Lambda) 이야기를 했습니다. 막연히 람다(Lambda) 의 기능에 대해 설명 하기 보다는 이면에 깔린 배경 개념을 소개 함으로써 현대 프로그래밍 언어가 갖는 특징을 이야기 하고 싶었습니다.

다음 시리즈에서는 우측 값 참조(RValue Reference)에 대해 알아보고 C++0x에서 어떤 의미를 갖는지를 설명하려고 합니다. 람다(Lambda) 관련해서는 많은 내용을 한 번에 준비해서 쓰려니 고생스럽더군요. 이번엔 차근차근 포스팅 하면서 글을 완성할 수 있으면 좋겠네요 ;)

( 마이크로소프트웨어 6월호의 『생각의 직관적인 표현, 람다(Lambda)』를 보시면 보다 잘 정리 되고 추가 된 내용을 보실 수 있습니다. )


고자료
1. MSDN - 
http://msdn.microsoft.com/en-us/library/dd293608.aspx
2. MSDN - http://msdn.microsoft.com/en-us/library/dd293599.aspx
3. MSDN - http://channel9.msdn.com/posts/kmcgrath/Lambda-Expressions-in-C/
4. MSDN - http://blogs.msdn.com/vcblog/archive/2008/11/18/stupid-lambda-tricks.aspx
5. Wikipedia - http://en.wikipedia.org/wiki/First-class_function
6. Wikipedia -  http://en.wikipedia.org/wiki/Higher-order_function

7. VSTS 2010 Team Blog -  http://vsts2010.net/category/Language%20Development/C++0x


http://tedahn.tistory.com/25

반응형
반응형

가상함수를 생성자와 소멸자에 넣으면 호출이 안된다고 되어있어서

실험삼아 책에 나와있는 코드를 테스트 해 보았는데 호출이 되었습니다. -_-;;

그리고 생성자와 소멸자에 가상함수를 쓰면 안된다고 나와있고 거기에 대한 자세한

설명이 없더군요. 이런저런 고민을 해 보았는데 왜 쓰면 안되는지 모르겠습니다.

코드는 대충..

class A
{
public:
A(){
test();
}
~A(){
test();
}
virtual test()
{
printf("test\n");
}
};

void main()
{
A a;
}

출력
test
test

간단하게 이정도네요.
왜 호출이 되고 왜 쓰면 안되는지 설명 부탁드립니다.


----------------------------------------------------------------


이펙티브 C++ 참고하세요.
항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자

대충 요약하면 자식이 생기기도 전에 자식 함수 호출하면 안되는 거고
자식이 사라진 다음에 자식 함수 호출 하면 안되서 그렇습니다.




https://kldp.org/node/98424

반응형
반응형

#include <iostream>

class A{

public :

void add(int a){

}

void add(int a, int b){

void (A::*fn)(int) = &A::add;

void (A::*fn1)(int,int,int) = &A::add;

printf("%x\t%x", fn,fn1);

}

void add(int a, int b, int c){

}

};


int _tmain(int argc, _TCHAR* argv[])

{

A ai;

A ai1;

A ai2;

ai.add(1,3);

std::cout << std::endl;

ai1.add(1, 3);


std::cout << std::endl;

ai2.add(1, 3);


return 0;

}


동일한 함수의 주소가 나옴을 알 수 있다





반응형
반응형

http://gcstudy.tistory.com/60

deprecated declaration (VC++)

오랫만의 포스팅 이군요.


deprecated 선언에 대한 간단한 얘기를 좀 남겨볼까 합니다.


보통 C++ 를 배우고 Visual C++ 을 IDE 로 채택해서 작업을 해나가는 프로그래머들이


언뜻 언뜻 CRT 의 함수 중 보안 취약성으로 인해서 C4995 경고를 만나게 되면서


deprecated 에 대해서 인지하게 되지 않나 싶은데요.


msdn (http://msdn.microsoft.com/en-us/library/044swk7y.aspx을 보면 설명이 나오지만


나타내고자 하는 바는 단순합니다. 


deprecated 로 선언된 함수나 타입을 사용할 때 사용자에게 조심하라는 말을 전달하고 싶은 것이죠.


그런데, 프로그래밍을 시작한지 얼마 안되는 시점에서는 이 deprecated 선언의 필요성에 대해서 잘 느끼지 못하지 않나 싶습니다.


그래서 간단한 하나의 예정도를 적어 보자면..


모종의 변환 행렬을 연산해서 3D 공간 상의 Object 의 위치를 얻어 내는 함수가 있다고 가정 합시다.


개발이 진행 되고 서비스를 시작한 뒤 점차 추가적인 컨텐츠를 붙여나가던 도중에 이 함수가 특정 상황에서


내부적으로 행렬 연산을 잘 못하게 되는 버그가 발견 되었다고 칩시다.


이 경우에 가장 첫 번째로 생각하게 되는 솔루션은 해당 함수의 버그를 수정한다 이겠지만 ..


이렇게 할 경우 의도치 않은 상황이 발생할 수 있습니다.


다른 누군가가 이 함수를 가져다 사용하면서 함수의 버그를 수정하기 보다는 잘 못된 수치 만큼을 보정 작업을 추가로


삽입하는 식으로 사용하고 있을지도 모르기 때문입니다.


이런 경우에 택할 수 있는 선택지 중의 하나가 deprecated 선언으로 (이것도 나름의 문제는 있긴 하지만..)


버그가 내재된 기존의 함수를 deprecated 선언을 시키고 새로운 함수를 작성하고 이 후로는 그것을 사용하도록 유도하는 것입니다.


예를 들면,


__declspec(deprecated("리비전 넘버 100 이후로는 이 함수를 사용하지말고 foo_ver2 를 사용하세요."))

void foo() { }


이런식으로요.


반응형
반응형

http://mwultong.blogspot.com/2006/12/c-hex-16-10-hex-string-to-int-number.html

십육진수 헥사 문자열을, 숫자(정수)로 변환 예제


소스 파일명: 0.cpp

#include <stdio.h>
#include <stdlib.h>


int main(void) {

  // 헥사 문자열을 long 으로
  char *s = "0x7FFFFFFF";
  long n = strtol(s, NULL, 16);
  printf("%d\n", n);
  // 출력 결과: 2147483647



  // 헥사 문자열을 unsigned long 으로
  char *s2 = "0xFFFFFFFF";
  unsigned long n2 = strtoul(s2, NULL, 16);
  printf("%u\n", n2);
  // 출력 결과: 4294967295



  // 헥사 문자열을 64-bit int 로
  char *s3 = "0x7FFFFFFFFFFFFFFF";
  __int64 n3 = _strtoi64(s3, NULL, 16);
  printf("%I64d\n", n3);
  // 출력 결과: 9223372036854775807



  // 헥사 문자열을 unsigned 64-bit int 로
  char *s4 = "0xFFFFFFFFFFFFFFFF";
  unsigned __int64 n4 = _strtoui64(s4, NULL, 16);
  printf("%I64u\n", n4);
  // 출력 결과: 18446744073709551615

  return 0;
}


반응형
반응형

http://blog.naver.com/sorkelf/40138272917



C++11 강력해진 Enumeration과 전방선언 Strongly typed enumerations


Strongly typed enumerations


이전 C++에서의 Enumeration은 안전한 타입이 아니었다. 따로 enum값을 취하려고 해도 결과적으로는 Int형이기 때문에 얼마든지 캐스팅이 가능한 형태를 띄고 있었다.


enum Color { ClrRed, ClrBlue, ClrYellow, ClrGreen, ClrBlack };

enum Alert { AltGreen, AltYello, AltRed };


bool is_danger(Alert alert)

{

    return alert == ClrRed; // OK...다른 enum형도 비교 가능

}


영역부분에선

enum Color { Red }; 
enum Alert { Red }; // 에러

C++0x에서는 [enum class / enum struct] 형식이 추가 되어 위와 같은 문제를 해결할 수 있게 되었다.

enum class Enumeration 
{ 
     Val1, 
     Val2, 
     Val3 = 100,
     Val4 /* = 101 */ 
};


이 형태의 enumeratrion은 타입안전을 보장하며 enum 클래스 값은 명시적으로 int형으로 캐스팅 할 수 없다.

그러므로 이들은 여타 다른 enum값이나 int 형과 비교 할 수 없다 ( 컴파일 에러출력)

또한, enum 클래스의 기본타입은 기본값은 int형이나 아래와 같이 명시적으로 다른 타입을 지정 할 수도 있다.


enum class Enum2 : unsigned int { Val1, Val2};


또한 이전 처럼 클래스가 아닌 기존 enum을 사용할 수 잇으며 처음 기본 값을 지정해줄 수도 있다


enum Enum3 : unsigned long {Val1 = 1, Val2};


C++0x에서는 enum의 크기(size) 만큼 명시적으로든 암시적으로든 선언이 가능하게 되었다.

enum Enum1; //C++, C++0x 어느쪽에서도 컴파일 에러이다 (정의되어있지 않음) 
enum Enum2 : unsigned int; //C++0x에선 가능, 기본 유형을 명시적으로 지정했음 
enum class Enum3; //C++0x에선 가능, 기본 타입은 Int 형 
enum class Enum4 : unsigned int; //C++0x 에서 가능. 
enum Enum2 : unsigned short; //C++0x에서 불가, Enum2가 기본타입을 이미 위에서 명시하고 있음





Enum Class 전방선언




C++0x에서는 struct나 class 처럼 enum도 전방선언이 가능하게 되었다


아래와 같은 헤더파일이 있다고 할때

// Color.h

enum Color

    .... 

};

void draw(Color& color);


draw 함수의 선언에서는 color내의 enum형을 필요하지 않지만

C++03에서는 enum을 전방선언하는것이 불가능했기 때문에

Color의 enum형을 변경하려면 관련된 모든 파일을 재컴파일을 해야 했다


C++0x에서는 enum의 전방선언이 가능하게 되어

아래와 같이 쓰는것이 가능해졌다

// Color.h 

enum Color int; 


void draw(Color& color);

// Color.cpp 

enum Color : int 
{
    ... 
}; 

void draw(Color& color) 
    ... 
}

위와 같이 쓰면 Color의 enum형을 변경한다 해도

재컴파일은 Color.cpp만 하면된다


Enum의 전방선언을 하려면 

기본이 되는 형 (ex : int 형)을 지정해야 한다


enum E; //에러 
enum E : int//OK

Enum의 재선언을 할때에는 기본형이 같아야 한다


enum E : int
enum E : short// 에러!! 기본형이 일치하지 않음 
enum E : int// OK


또, enum의 전방선언은 영역이 존재하는 enum값인 enum클래스에서도 적용되어 있다

enum class E : int; //OK


클래스나 네임스페이스에서 선언했단 enum도 스코프를 지정해서 정의할 수 있다

struct S 
{ 
    enum E : int; 
    E e; 
}; 

enum S::E : int 
{
    ... 
};



반응형
반응형

http://cafe.naver.com/dxgameprogramming/1443






http://cafe.naver.com/mathclub/84160



 

수학에서 봤을 때.. 무한 소수란 것이 있습니다.

예를 들어서

1/3 = 0.333333......

즉 3이 연속해서 나타나죠.

이 3은 끝없이 나타나기 때문에 무한소수라고 합니다.

 

컴퓨터에서는 아쉽게도 유리수형태의 수를 표현하는 자료형이 없습니다.

컴퓨터에서는 부동소수점(Floating point number)이란 자료형이 있죠.

 

부동소수점이라는 것은 소수점 위치를 옮겨서 잡는다는 뜻을 가지고 있습니다.

1.M 이란 소수가 있다면, 이 소수점 위치를 왼쪽 또는 오른쪽으로 지수부(E)로 옮기게 됩니다.

 

소수형태의 표현이므로 컴퓨터에서 표현할 수 없는 유리수들이 많겠죠.

 

컴퓨터는 이진수 체계를 가지고 있습니다.

그러다보니 컴퓨터에서 표현할 수 없는 유리수는 우리가 수학에서 사용하는 것보다 더 많습니다.

 

0.2  를 표현하고자 합니다.  이 수는 유한소수입니다만, 컴퓨터에서는 무한소수가 됩니다.

십진법 체계에서는 분모가 2 또는 5라는 소인수만 가져야죠?  왜냐하면 2x5 = 10 이기 때문이죠.

이진법 체계에서는 분모가 2라는 소인수만 가져야 합니다.  2는 소수이기 때문이죠.

 

0.2 를 이진법으로 표현할려면.. 무한소수가 됩니다.  왜냐하면 1/5 = 0.2 이기 때문에 분모에 5라는 소인수가 존재하죠.

0.2 를 이진법으로 표현할 때에는 정수부를 무조건 1로 만들어야 합니다.

0.2 * 8 = 1.6 이죠.  그럼 여기서 출발을 합니다.

1.6을 이진법으로 표현하면..

1.100110011001....

과 같이 소수부가 1001 이 반복하게 됩니다.  (너무나도 당연하겠지만 분모가 5이므로 4의 약수로 순환고리를 갖습니다.)

 

그런데 무한한 메모리를 가진 것이 아니기 때문에.. 소수부를 무한히 표현할 수 없습니다.

그래서 우리는 여기서 오차가 발생합니다.  이것을 Round off error 라고 하며, 우리말로 반올림 오차라고 합니다.

 

이 값은 하나의 숫자 상태에서는 중요하지 않습니다.  유효자리수 대비 2^-23 이라는 아주 작은 숫자(백만분의 1 오차)이기 때문이죠.

하지만 이 값에 숫자들을 곱하거나 더하거나 한다면 반올림 오차는 증가하게 됩니다.

 

프로그램을 할 때에는 이 반올림 오차에 대해서 충분하게 고려를 해주어야 합니다.

 

보통 부동소수점 연산에서는 == 을 사용하지 않습니다.  반올림 오차 때문이죠.

x == 0.0 대신에 우리는 x < 0.00001 && x > -0.00001 과 같이 합리적인 형태로 검사를 합니다.


반응형
반응형

http://javawoo.tistory.com/30



구조체 메모리 저장방식 #pragma pack

분류없음 2011/04/23 17:24
- 클래스(구조체)의 바이트 패딩 -
멤버 변수를 메모리에서 CPU 레지스터로 한번에 읽을 수 있도록
CPU 레지스터의 읽기 블록에 맞춰 정렬하는 컴파일러의 최적화 작업
컴파일러가 패딩을 하지 않아 레지스터가 읽는 블록의 경계에 걸쳐 멤버 변수가 생긴다면
메모리를 읽을 때 두개의 블록을 읽어는 문제가 발생
CPU 레지스터는
32비트 운영체제일때 4바이트
64비트 운영체제일때 8바이트 단위로 메모리를 읽는다

구조체를 4바이트 단위로 끊어주는것을 "바이트 패딩" 이라고 한다.
 

구조체는 메모리에 어떤 식으로 저장될까
다음과 같은 소스를 보자.
 
  1. #include <stdio.h>   
  2.   
  3. typedef struct _TEST{   
  4.     char cData;   
  5.     short sData;   
  6.     int iData;   
  7. }TEST;   
  8.   
  9. int main()   
  10. {   
  11.     TEST TData={0,};   
  12.   
  13.     printf("cData size : %d\n"sizeof(TData.cData));   
  14.     printf("sData size : %d\n"sizeof(TData.sData));   
  15.     printf("iData size : %d\n"sizeof(TData.iData));   
  16.     printf("TData size : %d\n"sizeof(TData));   
  17.        
  18.     return 0;   
  19. }  


TEST 구조체 변수인 TData는 char형 데이터(1byte), short형 데이터(2byte), int형 데이터(4byte)를 가지고 있으므로 1+2+4=7byte의 크기를 가질 것 처럼 보인다. 하지만 이 소스를 컴파일하고 실행을 해보면 다음과 같은 결과가  나온다.



분명히 cData(1) + sData(2) + iData(4) = 7임에도 불고하고 TData의 크기는 8바이트라고 하니 참 이상한 일이다.

이는 현재 우리가 쓰는 32비트 컴퓨터에서 32비트 컴파일러를 사용하였기 때문에 32비트 즉, 4바이트로 데이터를 처리하는 것에 가장 최적화되어 있기 때문에 데이터를 4바이트 공간으로 저장하기 때문이다.

이 TData란 구조체는 8바이트에 다음과 같이 저장되어 있다.

cData
??
sData
sData
iData
iData
iData
iData

cData를 저장하고, 4바이트중에 3바이트가 남아있기 때문에 sData를 3바이트 중에 2바이트의 공간에 저장하고,
iData를 저장하려 하니 1바이트밖에 남아있지 않기 때문에 4바이트의 공간을 따로 만들어 저장하게 되는 것이다.

그럼 이제 위의 소스에서 변수 선언의 순서를 한 번 바꿔 보자.

 
  1. typedef struct _TEST{   
  2.     char cData;   
  3.     int iData;   
  4.     short sData;   
  5. }TEST;  


변수 선언의 순서를 바꿨을 뿐인데 신기하게도 같은 구조체의 크기가 8에서 12로 늘어나버렸다.
이 TData 구조체 변수는 다음과 같이 저장되어 있을 것이다.

cData
(1byte)
empty
(3byte)
sData
(2byte)
empty
(2byte)
iData
(4byte)

이처럼 컴파일러는 4바이트에 맞춰서 데이터를 저장하는 것을 볼 수 있다. 이것을 막으려면 어떻게 해야할까.

이것을 해결하려면 #pragma pack() 이라는 전처리어를 사용하면 된다.
구조체 하나를 더 추가한 다음 소스를 보자.

 
  1. #include <stdio.h>   
  2.   
  3. typedef struct _TEST{   
  4.     char cData;   
  5.     int iData;   
  6.     short sData;   
  7. }TEST;   
  8.   
  9. #pragma pack(1)   
  10. typedef struct _TEST2{   
  11.     char cData;   
  12.     int iData;   
  13.     short sData;   
  14. }TEST2;   
  15.   
  16. int main()   
  17. {   
  18.     TEST TData={0,};   
  19.     TEST2 TData2={0,};   
  20.   
  21.     printf("TData size : %d\n"sizeof(TData));   
  22.     printf("TData2 size : %d\n"sizeof(TData2));   
  23.        
  24.     return 0;   
  25. }  


#pragma pack(1)에서 1은 1바이트 단위로 저장하겠다는 것이다. 따라서 TData와 TData2의 내용물은 같으나 크기는 다른 것을 확인할 수 있다.

 그렇다면, 왜 모두 1바이트로 해서 메모리의 낭비가 없도록 하지 않는 것일까?
 그것은, 아까도 이야기하였듯이 32비트 CPU에서는 4바이트(32비트)의 단위로 데이터를 처리하는 것이 가장 빠르게 때문이다. 즉, #pragma pack(1) 이라고 선언해놓고 원래대로 돌려놓지 않는다면 속도저하의 문제가 생길 수 있다.
 따라서, 위의 소스에서 구조체 선언이 끝나는 부분에 #pragma pack(4)라고 선언해주어 할 것이다.

 하지만, 여기에도 문제가 있다.
 만약, 이 소스를 32비트의 PC가 아닌 다른 CPU가 장착된 장비에서 컴파일하게 된다면 어떻게 될 것인가. 예를 들면 임베디드 시스템 같은 8~16비트 CPU에서 말이다.
 소스를 일일히 찾아서 CPU에 맞게 고쳐주고 다시 컴파일해야 되는 불편함과 어려움이 생기게 된다.

 이럴때를 위해서 좀 더 우아하게 쓰는 코드가 있다.

 
  1. #pragma pack(push, 1)   
  2. typedef struct _TEST2{   
  3.     char cData;   
  4.     int iData;   
  5.     short sData;   
  6. }TEST2;   
  7. #pragma pack(pop)  


 기존의 바이트를 스택에 push하고 1바이트 단위로 처리한다음 끝나는 부분에 원래의 바이트 단위를 pop해주는 코드이다. 보통은 이렇게 사용하면 되겠다.


 

아래와 같은 코드를 사용하면 구조체 크기는 1바이트가된다.

  1. #pragma pack(push, 1)   
  2. typedef struct DATA{   
  3.     char cData;      
  4. }DATA;   
  5. #pragma pack(pop)  


반응형
반응형

http://www.devpia.com/MAEUL/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=8308


[C++0x] Lambda Expressions and Closures

 

작성일 : 2008.11.23       

작성자 : 아이오 교육센터

www.ioacademy.co.kr

 

글 순서

1. 기본개념

2. 람다 표현식(Lambda expressions)과 and 클로져(closure)

3. 람다 표현식과 리턴 타입

4. Capturing Local Variable

5. Nullary Lambda

6. Mutable lambda

 
1. 기본 개념

 STL의 알고리즘 중에는 함수 객체를 인자로 받는 것이 있습니다. for_each() 알고리즘이 대표적인 경우 입니다. 아래의 코드는 for_each()를 사용해서 주어진 구간의 모든 요소를 화면에 출력하는 코드 입니다.

01: #include <iostream>
02: #include <algorithm>
03: using namespace std;
04: 
05: struct ShowFunctor
06: {
07:         void operator()(int n) const
08:         {
09:                 cout << n << endl;
10:         }
11: };
12: int main()
13: {
14:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
15:         for_each( x, x+10, ShowFunctor() );
16: }

 

 

 그런데 위 코드에 있는 ShowFunctor 같은 함수를 자주 만들어야 한다면 좀 번거로운 일입니다. C++0x는 ShowFunctor 와 같은 함수객체를 보다 쉽게 만들 수 있는 “람다 표현식(Lambda express)”라는 개념을 제공합니다. 아래 코드는 위 코드와 동일한 일을 수행합니다. 단, 완전한 함수객체를 만들어 사용하지 않고 람다 표현식을 사용하고 있습니다.

 
1: int main()
2: {
3:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
4:         for_each( x, x+10, [](int n){ cout << n << endl;} );
5: }

 위 예제를 포함한 이 글에 나오는 모든 예제들은 VS2010CTP 를 사용해서 test 되었습니다.

 

 위 코드 중 다음의 표현이 람다 표현식 입니다.

      [](int n){ cout << n << endl;}

 람다 표현식을 보고 컴파일러는 함수객체 클래스를 정의하고 객체를 생성하게 됩니다. 즉, 위 코드는 아래의 코드와 유사한 의미를 가지고 있습니다.

 
01: class closure_object
02: {
03: public:
04:         void operator()(int n) const
05:         {
06:                 cout << n << endl;
07:         }
08: };
09: int main()
10: {
11:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
12:         for_each( x, x+10, closure_object() );
13: }
 

람다 표현식에 의해 컴파일러가 생성한 함수객체는 클로져 객체(Closure Object) 라고 부릅니다.클로져 객체는 함수호출연산자, 생성자, data멤버를 가진 함수객체와 유사하게 동작합니다.

 

람다 표현식을 간단히 살펴 보도록 하겠습니다..

 
 - [] 는 “lambda introducer” 라고 부르는데, 람다 표현식이 시작됨을 나타냅니다.
 - (int n) 은 “람다 파라미터 선언(lambda parameter declaration)” 이라고 부르는데, 클로져 객체의 함수호출 연산자(즉, ()연산자)가 가져야 하는 파라미터 목록을 컴파일러에게 알려주는 것입니다.
 - 마지막으로 “{ cout << n << endl;}” 는 클로져 객체의 함수 호출연산자의 구현부를 정의 하는 것입니다.
 - 디폴트로 람다 표현식의 리턴 타입은 void를 리턴 합니다.
 - 여러 개의 람다 표현식은 각각 자신만의 타입을 가지게 됩니다.
 

물론, 람다 표현식을 사용하지 않고 완전한 함수객체를 만들어 사용하면 되지만 람다 표현식을 사용하는 것이 좀더 편리 합니다.

 
2. Lambda Expression and Closure
 

[] 를 사용해서 익명의 함수객체를 명시하는 표현 식을 “람다 표현식(lambda expression)” 또는 “람다 함수(lambda function)” 라고 합니다. 또한, 람다표현식의 결과로 컴파일러에 의해 자동으로 생성된 익명의 함수객체(unnamed function object) 를 “클로져(Closure)” 라고 합니다.

 

람다 표현식의 구현은 여러 줄로 구성될 수 도 있습니다.

 
01: #include <iostream>
02: #include <algorithm>
03: using namespace std;
04: 
05: int main()
06: {
07:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
08: 
09:         for_each( x, x + 10, [](int n ) {
10:                 if ( n % 2 == 0 )
11:                         cout << "even ";
12:                 else
13:                         cout << "odd ";
14:         });
15: 
16:         cout << endl;
17: }
 

3. Lambda Expression and return type

 

람다 표현식은 리턴값은 가질수도 있습니다. 아래의 예제를 보겠습니다

 
01: #include <iostream>
02: #include <algorithm>
03: #include <vector>
04: using namespace std;
05: 
06: int main()
07: {
08:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
09:         vector<double> v;
10: 
11:         transform( x, x+10, back_inserter(v), [](int n) -> double {
12:                 if ( n % 2 == 0 ) return n;
13:                 return n / 2.0;
14:         });
15: 
16:         for_each( v.begin(), v.end(), [](double n) { cout << n << " ";});
17:         cout << endl;
18: }
 

“->double” 은 “lambda-return-type-clause” 라고 부르는데, 람다 표현식에서 리턴 타입을 지정하는 구문입니다.

 

아래와 같은 경우, 리턴 타입을 지정하는 것은 생략할 수 있습니다.

 

1. 람다 표현식이 리턴하는 값이 없을 경우 즉, “->void”는 생략이 가능합니다.

2. 람다 표현식이 한 개의 return 문장만을 가지고 있을 경우. 컴파일러는 해당 return 표현식 으로 부터 리턴 타입을 추론할 수가 있습니다.

 
01: #include <iostream>
02: #include <algorithm>
03: #include <vector>
04: using namespace std;
05: 
06: int main()
07: {
08:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
09:         vector<double> v1(10);
10:         vector<double> v2(10);
11: 
12:         // return 문장이 한 개 입니다. "?>double" 는 생략 가능 합니다.
13:         transform( x, x+10, v1.begin(), [](int n) { return n / 2.0;});
14: 
15:         // return 문이 없습 니다. “?>void” 는 생략 가능 합니다.
16:         for_each( v1.begin(), v1.end(), [](double d) { cout << d << " ";});
17:         cout << endl;
18: 
19:         // 2개 이상의 return 문이 있습니다. 반드시 "?>double" 을 표시 해야 합니다.
20:         transform( x, x+10, v1.begin(), [](int n)->double {
21:                 if ( n % 2 == 0 ) return n;
22:                 return n / 2.0;
23:         });
24: 
25:         for_each( v1.begin(), v1.end(), [](double d) { cout << d << " ";});
26:         cout << endl;
27: 
28:         // 역시 1개의 return 문 입니다.
29:         transform( x, x+10, v1.begin(), v2.begin(), [](int a, double d){ return a + d;});
30:         for_each( v2.begin(), v2.end(), [](double d) { cout << d << " ";});
31:         cout << endl;
32: }
 
 

4. Capturing Local Variable

 

  C++에서 함수 객체를 사용하는 이유 중의 하나는 상태를 가질 수 있다는 점입니다. 지금까지의 람다 표현식은 상태를 가지지 않았습니다. 하지만 지역변수를 캡쳐(capture) 하므로서 람다 표현식도 상태를 가질수가 있습니다. 아래 예제를 보겠습니다.

01: int main()
02: {
03:         int x[10] = { 1,3,5,7,9,11,13,15,17,19};
04: 
05:         int low = 12;
06:         int high = 14;
07: 
08:         int* p = find_if( x, x+10, [low, high](int n) { return low < n && n < high;});
09: 
10:         cout << *p << endl;
11: }
 

 Lambda introducer 인 “[]” 안에는 지역변수를 넣으 므로서 해당 지역변수를 람다 표현식안에서 사용할수있습니다. 이러한 것을 “capture list” 라고 합니다. “[low, high]”라고 표시하므로서 low, high 지역변수를 람다표현식 안에서 사용할 수 있습니다. “capture list”를 사용하지 않을 경우 람다 표현식 안에서 지역변수에 접근할 수는 없습니다.

 

결국 위 표현식은 아래코드와 같은 의미를 가지게 됩니다.

 
01: class closure_object
02: {
03: private:
04:         int min;
05:         int max;
06: public:
07:         closure_object( int l, int h ) : min(l), max(h) {}
08:         bool operator()(int n) const { return min < n && n < max; }
09: };
10: 
11: int main()
12: {
13:         int x[10] = { 1,3,5,7,9,11,13,15,17,19};
14:         int low = 12;
15:         int high = 14;
16: 
17:         int* p = find_if( x, x+10, closure_object(low, high) );
18:         cout << *p << endl;
19: }
 

디폴트 캡쳐인 “[=]”를 사용하면 모든 지역 변수를 람다 표현식에서 사용할 수 있습니다.

 
1: int main()
2: {
3:         int x[10] = { 1,3,5,7,9,11,13,15,17,19};
4:         int low = 12;
5:         int high = 14;
6: 
7:         int* p = find_if( x, x+10, [=](int n) { return low < n && n < high;});
8:         cout << *p << endl;
9: }
 

“[low, high”] 또는 “[=]” 는 모두 지역변수를 값으로(by value)로 캡쳐 합니다. 아래 와 같이 사용 하므로서 지역변수를 참조(by reference)로 캡쳐 할 수 있습니다. 즉, 람다 표현식 안에서 지역변수의 값을 변경 할 수 있습니다.

 
1: int main()
2: {
3:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
4:         int sum = 0;
5: 
6:         // x~x+10 에서짝수의합을구하는람다표현식입니다.
7:         for_each( x, x+10, [&sum] (int n) { if ( n % 2 == 0 ) sum += n;});
8: 
9:         cout << sum << endl;
10: }
 

 “[&sum]” 은 지역변수 sum 을 람다 표현식에서 참조로 사용하겠다는 의미 입니다. 즉, 아래의 코드와 동일한 의미를 가지게 됩니다.

 
01: class closure_object
02: {
03: private:
04:         int& ref;
05: public:
06:         closure_object ( int& r ) : ref(r) {}
07:         void operator()(int n) const { if ( n % 2 == 0 ) ref += n; }
08: };
09: 
10: int main()
11: {
12:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
13:         int sum = 0;
14: 
15:         for_each( x, x+10, closure_object( sum ) );
16:         cout << sum << endl;
17: }
 

 “[&]”를 사용 하므로서 모든 지역변수를 참조를 캡쳐 할 수도 있습니다.

또한 모든 지역변수를 값으로([=]) 디폴트 캡쳐를 사용하면서도 특정 변수만을 참조로 캡쳐 할 수도 있습니다.

 
01: int main()
02: {
03:         int x[10] = { 1,2,3,4,5,6,7,8,9,10};
04:         int low = 3;
05:         int high = 8;
06:         int sum = 0;
07: 
08:         // low ~ high 사이의 합을 구하는 람다 표현식 입니다.
09:         for_each( x, x+10, [=, &sum](int n) { if ( low < n && n <high) sum += n; });
10:         cout << sum << endl;
11: }
 

이번에는 멤버 함수 안에서 람다 표현식을 사용하는 경우를 생각해 봅시다..

 
01: class Test
02: {
03: public:
04:         void foo() const
05:         {
06:                 int x[10] = { 1,2,3,4,5,6,7,8,9,10};
07: 
08:                 // 멤버 data인 base를 캡쳐 하려고 한다. 하지만 error.
09:                 for_each( x, x+10, [base](int n) { cout << base + n << " ";});
10:                 cout << endl;
11:         }
12: 
13:         Test() : base(100) {}
14: private:
15:         int base;
16: };
17: 
18: int main()
19: {
20:         Test t;
21:         t.foo();
22: }
 

이 경우에는 아래처럼 [this], 또는 [=] 를 사용하면 멤버 data 를 캡쳐할 수 있습니다.

 
01: class Test
02: {
03: public:
04:         void foo() const
05:         {
06:                 int x[10] = { 1,2,3,4,5,6,7,8,9,10};
07: 
08:                 for_each( x, x+10, [this](int n) { cout << base + n << " ";}); // 또는 [=]
09:                 cout << endl;
10:         }
11:         Test() : base(100) {}
12: private:
13:         int base;
14: };
 
5. Nullary Lambda

 

파라미터를 갖지 않는 람다 표현식이 만들수 도 있습니다. 이 경우 파라미터 리스트의 ()를 생략할 수도 있습니다. 다음 코드를 살펴 봅시다.

 
01: int main()
02: {
03:         int x[10] = {0};
04:         int y[10] = {0};
05:         int i = 0;
06:         int j = 0;
07: 
08:         generate( x, x+10,[&i]() { return ++i;});
09:         generate( y, y+10,[&j] { return ++j;}); // () 를생략해도된다.
10: 
11:         for_each( x, x+10, [](int n) { cout << n << " ";});
12:         cout << endl;
13: 
14:         for_each( y, y+10, [](int n) { cout << n << " ";});
15:         cout << endl;
16: }

 

 

6. Mutable Lambda

 

클로져 객체의 함수 호출연산자(operator())는 상수 함수로 만들어 집니다. 따라서, 지역 변수를 캡쳐해 사용하는 람다 표현식은 자신의 상태를 변경 할 수 없습니다. 이경우 mutable 키워드를 사용 하면 값으로 캡쳐한 지역변수의 값을 변경할 수 있습니다.

 
01: int main()
02: {
03:         int x[10] = { 1,3,5,7,9,2,4,6,8,10};
04:         int i = 0;
05: 
06:         // error. 값으로캡쳐한지역변수의값을변경할수없다.
07:         for_each( x, x+10, [=](int n) { cout << ++i << " : " << n << endl;});
08: 
09:         // ok.
10:         for_each( x, x+10, [=](int n) mutable { cout << ++i << " : " << n << endl;});
11: }
 
boost에서 지원하던 람다개념을 C++0x에서는 언어 차원에서 지원하므로서 STL을 훨씬 편리 하게 사용할 수가 있게 되었습니다.
 





반응형
반응형


출처 : http://scor7910.tistory.com/55



Visual C++ 팀블로그에 C++0x에 대한 소개 자료중 람다(Lambda)에 관한 내용을 번역했습니다. 
차후에 나머지도 번역해서 올리겠습니다. 
번역이 만만치 않은 작업이근영....

 원본 : 

http://blogs.msdn.com/vcblog/archive/2008/10/28/lambdas-auto-and-static-assert-c-0x-features-in-vc10-part-1.aspx

 

Lambdas, auto, and static_assert: C++0x Features in VC10, Part 1

마이크로소프트 비절 스투디오 2010CTP(Community Technology Preview)에서는 C++ 0x 포함된 lambdasautostatic_assertrvalue references  라는 4가지의 개념을 제공합니다.  여기서는 처음 세가지에 대해이야기를 하겠습니다.

첫번째로 내용을 쓰기전..:

 

1.  포스트는 Visual C++ 라이브러리 개발자인Stephan T. Lavavej 님이작성했습니다그리고 Stephan T. Lavavej님이 위의 네가지 기능에 대한 구현을 담당하지 않았음을 밝힘니다.

 

2. 내용에서 VS 2010에서 Visual C++ 컴파일러를 VC10이라고 칭할  입니다(10 2010 약어가 아님).

 

3. C++0x 아직 논의 중인 차세대 C++ 표준을 의미합니다.

(표준 위원회(The Standardization Committee) 2009 C++09라는 이름으로 발표되기 원하지만 2010이나  걸릴수 있기 때문에 x 붙였다는 조크도있다네요. C++98  C++03  현재 C++ 표준을 의미 합니다.(여기서 역사이야기는 하지 않고 2003년에 발표된 C++표준은 1998년에 발표된 C++대한 단순한 서비스팩” 이었고 대부분의 사람들은 이들의 차이점에 대해 무시하였죠.  C++03  C++0x  완전히 다릅니다.

 

4.  나는 C++ 0x 완벽하고 멋지게 구성하고 있을 표준 위원회(The Standardization Committee) 감사를 표합니다그들은 또한 아래의 링크에 좋은 자료를 올려 두었습니다 

C++0x language feature status: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2705.html

C++0x library feature status: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2706.html

C++0x Working Draft: http://open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2798.pdf

 

5.  어디에나 버그는 있습니다. (많지 않기를 원하지만..), 그게  CTP 특성입니다버그가 발견되면 마이크로 소프트 커넥트 리포팅 해주세요.

 

이제 본론으로 들어가겠습니다.

 

 

 

 

 

lambdas

C++0x에는 명명되지않은(unnamed) 함수객체(function objects) 수동으로 선언과 정의를 하지않고이것을 포함하고 있는  함수객체(function objects) 사용할  있는 람다(lambda) 수식 있습니다.

아래는 람다를 사용한 "Hello, World" 예제 입니다. :

 

C:\Temp>type meow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 meow.cpp > NUL && meow

0 1 2 3 4 5 6 7 8 9

 

 

 

 []  람다-소개자 (lambda-introducer) 입니다컴파일러에게 람다 수식이시작했다는 것을 알려주는 역할을 합니다(int n) 람다-매개변수-선언(lambda-parameter-declaration) 입니다어떤 명명되지 않은(unnamed)함수 객체 클래스의 연산자( ()연산자를 의미하는듯) 실행이 되어지는지컴파일러에게 알려주는 역할을 합니다마지막으로  { cout << n << " "; }   복합-서술(compound-statement)부분이고 , 명명되지 않은 함수 객체의 몸체(정의부입니다기본적으로 명명되지 않은 함수 객체의 연산자는 void 리턴합니다.

 

 

그러면 C++0x에서 쓰여진 람다를 현재 C++ 구현한다면 어떻게 구현되는지보겠습니다.

 

C:\Temp>type meow98.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

struct LambdaFunctor {

    void operator()(int n) const {

        cout << n << " ";

    }

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), LambdaFunctor());

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 meow98.cpp > NUL && meow98

0 1 2 3 4 5 6 7 8 9

 

 

 

이제부터 명명되지않은 함수 객체 클래스의 () 연산자는 void 리턴한다” 람다는 void 리턴한다라고 말하겠습니다.  하지만람다 수식이 클래스를정의하고 생성하는 것은 중요하니 기억해 두세요.

 

물론람다의 복합-서술(compound-statement)구문은  여러줄로   있습니다.

 

C:\Temp>type multimeow.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    for_each(v.begin(), v.end(), [](int n) {

        cout << n;

 

        if (n % 2 == 0) {

            cout << " even ";

        } else {

            cout << " odd ";

        }

    });

 

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 multimeow.cpp > NUL && multimeow

0 even 1 odd 2 even 3 odd 4 even 5 odd 6 even 7 odd 8 even 9 odd

 

 

 

그리고람다는 항상 void 리턴 하지 않습니다만약 람다의 복합-서술(compound-statement) { return expression; } 되어 있다면람다의리턴 타입은 자동으로 수식의 타입으로 만들어 줍니다.

C:\Temp>type cubicmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    deque<int> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](int n) { return n * n * n; });

 

    for_each(d.begin(), d.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 cubicmeow.cpp > NUL && cubicmeow

729 512 343 216 125 64 27 8 1 0

 

 

 

여기서 n * n * n  int 타입이고 람다의 함수기 호출하 ()연산자는 int리턴합니다.

 

람다로  복작한 복합-서술(compound-statements)구문은  자동으로 리턴타입을 만들어   없습니다.  아래 코드와 같이 리턴타입을 명시해 주어야 합니다


C:\Temp>type returnmeow.cpp

#include <algorithm>

#include <deque>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    deque<double> d;

 

    transform(v.begin(), v.end(), front_inserter(d), [](intn) -> double {

        if (n % 2 == 0) {

            return n * n * n;

        } else {

            return n / 2.0;

        }

    });

 

    for_each(d.begin(), d.end(), [](double x) { cout << x << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 returnmeow.cpp > NUL && returnmeow

4.5 512 3.5 216 2.5 64 1.5 8 0.5 0

 

 







































“-> double”  부가적으로 사용할  있는 람다-리턴-타입-구문(lambda-return-type-clause )입니다.  많은 개발자들이  리턴 타입이 왼쪽에 오지 않는 건지 궁금해 하겠지만,  lambda-introducer ( [] ) 앞에 있지않으면 컴파일러는 람다 문법의 시작을 알수 없기 때문입니다.

 

만약 람다 리턴 타입 구문을 쓰지 않으면 다음과 같은 컴파일 에러가 발생됩니다

C:\Temp>cl /EHsc /nologo /W4 borkedreturnmeow.cpp

borkedreturnmeow.cpp

borkedreturnmeow.cpp(20) : error C3499: a lambda that has been specified to have a void return type cannot return a value

borkedreturnmeow.cpp(22) : error C3499: a lambda that has been specified to have a void return type cannot return a value

 

 

지금까지 보여준 람다는 데이터 멤버가 없는(stateless) 것들입니다.

여러분들은 지역변수를 캡쳐링(capturing)”해서 데이터 멤버를 가지고 있는람다를 만들  있습니다.   비어있는 람다 소개자lambda-introducer) [] 데이터 멤버가 없는 람다 라는 것을 의미 하고캡쳐리스트(capture-list)지정하여 데이터 멤버를 가지는 람다를 만들  있습니다.

 

C:\Temp>type capturekittybyvalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 0;

    int y = 0;

 

    // op>>() 입력 스트림에 개행 문자를  남겨야 하는데,

    // 이건  귀찮으므로 쓰지 않을 것을 추천합니다.

    // 대신  라인을 읽거나 읽어서 파싱하는 루틴을 만들고

    // 싶다면getline(cin,str)함수를 사용하세요.

    // 여기서는 간단하게 쓰기 위해 op>>() 썼습니다.

 

 

    cout << "Input: ";

    cin >> x >> y;

 

    v.erase(remove_if(v.begin(), v.end(), [x, y](int n) {return x < n && n < y; }), v.end());

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyvalue.cpp > NUL && capturekittybyvalue

Input: 4 7

0 1 2 3 4 7 8 9

 

 

캡쳐 리스트를 정의하지 않으면 아래의 오류 코드가 발생됩니다

C:\Temp>cl /EHsc /nologo /W4 borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp

borkedcapturekittybyvalue.cpp(27) : error C3493: 'x' cannot be implicitly captured as no default capture mode has been specified

borkedcapturekittybyvalue.cpp(27) : error C3493: 'y' cannot be implicitly captured as no default capture mode has been specified

 

 

(기본(default) 캡쳐에 대한 설명은 나중에…)

 

람다 수식은 기본적으로 명명되지 않은 함수 객체 클래스를 정의 한다는 것을기억해두시길 바랍니다.  복합-서술(compound-statement) 구문인 { return x < n && n < y; } 클래스의 ()연산자 함수 몸체에 해당됩니다.

어휘상 복합-서술(compound-statement) 구문이 어휘상 main() 함수 내부에있지만 개념상 main()함수 외부에 존재 하는  입니다.  그래서 main() 함수내부에 있는 지역변수를 바로 사용할  없고 람다 내부에서 캡쳐하여 사용해야합니다.

 

아래 예제는 위의람다 캡쳐 예제를 현재 C++ 표준으로 구현한  입니다.

 

C:\Temp>type capturekittybyvalue98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

class LambdaFunctor {

public:

    LambdaFunctor(int a, int b) : m_a(a), m_b(b) { }

 

    bool operator()(int n) const { return m_a < n && n < m_b; }

 

private:

    int m_a;

    int m_b;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 0;

    int y = 0;

 

    cout << "Input: ";

    cin >> x >> y; // EVIL! <<- 이러면 안된다는 말입니다..

 

    v.erase(remove_if(v.begin(), v.end(), LambdaFunctor(x, y)), v.end());

 

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyvalue98.cpp > NUL && capturekittybyvalue98

Input: 4 7

0 1 2 3 4 7 8 9

 

 

 

여기서,  캡쳐의 의미가 값에 의한(전달)” 이라는 것이 명확하게   있습니다지역변수의 복사본이 함수객체 내부 함수에 저장 되는 것을   있습니다.

이는 함수 객체가 캡쳐하기 위해 생성된 지역변수 보다  오래 남을  있게 합니다.

아래 사항들을 알아 두세요.

(a) 함수호출 연산자( ()연산자 ) 기본적으로 const 이기 때문에캡쳐된 복사본은 람다 내부에서 수정될  없습니다.

(b) 어떤 객체는 복사하기 비용이 많이 듭니다.  

(c) 지역변수를 변경하면 캡쳐된 복사본에는 아무런 영향을 없습니다. (값에의한 전달에 관한 내용).

 

 부분은 다음에 필요할  이야기 하겠습니다 

 

캡쳐하고 싶은 지역변수들을 모두 지정하는  대신 값에 의한 복사로  모두 캡쳐”   있습니다.  이것을 가능하게 하는 문법이 람다 소개자(lambda-introducer)  “ [=] “ 입니다. ( capture-default  =’  여러분이 대입연산자나 복사 초기화 Foo foo = bar  생각하게 하기 위함입니다;

사실 복사는 위의 예제에서 m_a(a) 같이 한번에 초기화 하기 위해 만들어 졌습니다.

 

C:\Temp>type defaultcapturekittybyvalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 0;

    int y = 0;

 

    cout << "Input: ";

    cin >> x >> y; // EVIL!

 

    v.erase(remove_if(v.begin(), v.end(), [=](int n) { returnx < n && n < y; }), v.end());

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 defaultcapturekittybyvalue.cpp > NUL && defaultcapturekittybyvalue

Input: 4 7

0 1 2 3 4 7 8 9

 

 

 

컴파일러는 람다 구문 내에 있는 x y 보고, main()함수에 있는 x y 값을 캡쳐 합니다.

위에서 (a)항목에서 말한 람다의 함수 호출 연산자( () 연산자 기본적으로const이기 때문에 캡쳐한 복사본(원본) 수정할  없지만,  람다 ()연산자 함수 내부에서 mutable(변하기 쉬운키워드를 사용하여 non-const 만들면됩니다.

 

C:\Temp>type capturekittybymutablevalue.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), [=](int& r) mutable {

        const int old = r;

 

        r *= x * y;

 

        x = y;

        y = old;

    });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << x << ", " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybymutablevalue.cpp > NUL && capturekittybymutablevalue

0 0 0 6 24 60 120 210 336 504

1, 1

 

 

v 있는  값에 이전의 x,y곱을 곱하는 동작을 합니다.

 (이전의 모든 요소들과 곱해주도록 partial_sum() 함수나이전의 요소들과바로 곱해주도록 adjacent_difference() 함수를 이용해서 구현할수  없어서예제처럼 구현이 되어있습니다.)  위에서 말한 항목 “(d) 캡쳐된 복사본을 원본의 지역변수에 적용되지 않는다”  기억하세요.

 

위에 (b),(C),(d) 약속을 우회할 방법을 알고 싶다면복사를 하지 않고 람다내부에서 값을 변경하는 것을 관찰하는 방법람다함수 내부에서 값을 바꿀 있지 않을까요이럴 경우 참조(reference) 값을 캡쳐하는 방법을 생각 했을겁니다.  이렇게 하는 방법은 람다-소개자(lambda introducer) [&x, &y]하면 됩니다( [&x, &y]  X& x, Y& y 생각하세요포인터의 전달이 아닌참조(Reference)입니다.) :

 

C:\Temp>type capturekittybyreference.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), [&x, &y](int& r) {

        const int old = r;

 

        r *= x * y;

 

        x = y;

        y = old;

    });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << x << ", " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyreference.cpp > NUL && capturekittybyreference

0 0 0 6 24 60 120 210 336 504

8, 9

 

 

위의 예제 capturekittybymutablevalue.cpp  다른 점은

(1)람다-소개자 모양lambda-introducer) [&x, &y]

(2) mutable 키워드가 없습니다.

(3) main()함수 지역 변수 x,y 값이 람다함수 내부에서 변경된 것이 main()함수에서도 적용되었습니다.

 세가지 입니다.

 

 

위의 코드를 현재 C++ 구현 한다면 아래와 같이 구현할  있습니다.:

C:\Temp>type capturekittybyreference98.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

#pragma warning(push)

#pragma warning(disable: 4512) // assignment operator could not be generated

 

class LambdaFunctor {

public:

    LambdaFunctor(int& a, int& b) : m_a(a), m_b(b) { }

 

    void operator()(int& r) const {

        const int old = r;

 

        r *= m_a * m_b;

 

        m_a = m_b;

        m_b = old;

    }

 

private:

    int& m_a;

    int& m_b;

};

 

#pragma warning(pop)

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), LambdaFunctor(x, y));

 

    copy(v.begin(), v.end(), ostream_iterator<int>(cout, " "));

    cout << endl;

 

    cout << x << ", " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 capturekittybyreference98.cpp > NUL && capturekittybyreference98

0 0 0 6 24 60 120 210 336 504

8, 9

 

 

 

(람다를 사용하면 , 컴파일러는 자동으로 C4512 경고를 꺼줍니다.)

  

지역 변수를 참조로 캡쳐하면함수 객체는 참조(reference) 자신의 참조(reference) 멤버변수에 저장합니다.

 이렇게 하면 함수 객체 () 함수에서 변경된 값이 적용이 되는 겁니다.

(함수 객체의 ()함수  const 것에 주의하세요우리는(VC++ 컴파일러팀) mutable이라 하지 않습니다. const 붙인 것은 단순히  함수객체의 멤버 데이터의 변경을 막기 위함 입니다멤버데이터들은 참조하는   변경할수 없고 멤버 데이터들이 참조하는 값을 변경할  있습니다. Const 아닌 함수 객체는 얕은 복사입니다.)

 

물론만약 람다함수 객체가 참조로 캡쳐된 지역변수들 보다 오래 생존(instantiate) 하게 되면 프로그램은 죽게 됩니다(crashtrocity :  뭔소리야?!!).

 

또한기본 캡쳐를 사용할 수도 있습니다. ;  [&]  참조로 모든 변수를 캡쳐한다 의미 합니다.

 

만약  개는 참조로하고  개는 값으로 캡쳐하고 싶을땐 어떻게 할까요?

[a, b, c, &d, e, &f, g] 방법을 생각하겠지만여러분들은 capture-default  지정하고 특정 지역변수들에 대해 오버라이드   있습니다.

아래 예제는 위에 나온 예제capturekittybymutablevalue.cpp  수정한 입니다.:

  

C:\Temp>type overridekitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    int sum = 0;

    int product = 1;

 

    int x = 1;

    int y = 1;

 

    for_each(v.begin(), v.end(), [=, &sum, &product](int& r)mutable {

        sum += r;

 

        if (r != 0) {

            product *= r;

        }

 

        const int old = r;

 

        r *= x * y;

 

        x = y;

        y = old;

    });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << "sum: " << sum << ", product: " << product << endl;

    cout << "x: " << x << ", y: " << y << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 overridekitty.cpp && overridekitty

overridekitty.cpp

0 0 0 6 24 60 120 210 336 504

sum: 45, product: 362880

x: 1, y: 1

 

 

여기서는 x,y 값으로 캡쳐하고, (람다 내부에서만 수정되어져야 하기 때문) sum, produce 참조로 캡쳐 했습니다반대로 lambda-introducer  [&, x, y]  해도 같은 같은 결과 입니다.

 

그럼 this 는 어떻게 할 까요?

C:\Temp>type memberkitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

class Kitty {

public:

    explicit Kitty(int toys) : m_toys(toys) { }

 

    void meow(const vector<int>& v) const {

        for_each(v.begin(), v.end(), [m_toys](int n) {

            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;

        });

    }

 

private:

    int m_toys;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 3; ++i) {

        v.push_back(i);

    }

 

    Kitty k(5);

    k.meow(v);

}

 

C:\Temp>cl /EHsc /nologo /W4 memberkitty.cpp

memberkitty.cpp

memberkitty.cpp(12) : error C3480: 'Kitty::m_toys': a lambda capture variable must be from an enclosing function scope

 

 

  

람다 수식 문법은 지역변수를 캡쳐하는건 허용하지만 멤버 변수의 캡쳐는 허용하지 않습니다.  대신 특별히 this 포인터를 캡쳐   있습니다. :

C:\Temp>type workingmemberkitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

class Kitty {

public:

    explicit Kitty(int toys) : m_toys(toys) { }

 

    void meow(const vector<int>& v) const {

        for_each(v.begin(), v.end(), [this](int n) {

            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;

        });

    }

 

private:

    int m_toys;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 3; ++i) {

        v.push_back(i);

    }

 

    Kitty k(5);

    k.meow(v);

}

 

C:\Temp>cl /EHsc /nologo /W4 workingmemberkitty.cpp > NUL && workingmemberkitty

If you gave me 0 toys, I would have 5 toys total.

If you gave me 1 toys, I would have 6 toys total.

If you gave me 2 toys, I would have 7 toys total.

 

 

this 캡쳐하면 m_toys멤버 변수는 암시적으로 this->m_toys 의미 한다는 것을 생각   있습니다.  (람다 수식 내부에선, this 캡쳐된 this의미하지 람다 객체의 this포인터를 의미하지 않습니다. : 람다함수의 this 포인터에는 접근할  없습니다.)

 

this 암시적으로 캡쳐할  있습니다:

C:\Temp>type implicitmemberkitty.cpp

#include <algorithm>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

 

class Kitty {

public:

    explicit Kitty(int toys) : m_toys(toys) { }

 

    void meow(const vector<int>& v) const {

        for_each(v.begin(), v.end(), [=](int n) {

            cout << "If you gave me " << n << " toys, I would have " << n + m_toys << " toys total." << endl;

        });

    }

 

private:

    int m_toys;

};

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 3; ++i) {

        v.push_back(i);

    }

 

    Kitty k(5);

    k.meow(v);

}

 

C:\Temp>cl /EHsc /nologo /W4 implicitmemberkitty.cpp > NUL && implicitmemberkitty

If you gave me 0 toys, I would have 5 toys total.

If you gave me 1 toys, I would have 6 toys total.

If you gave me 2 toys, I would have 7 toys total.

 

 

 

[&] 할수 있지만 ,  this 포함 되지 않습니다.(항상 값으로 전달됩니다.) , [&this] 안됩니다.

 

람다 소개자(lambda-introducer) 아무런 값을 넣고 싶지 않으면, lambda-parameter-declaration 전체는 생략 됩니다. :

C:\Temp>type nullarykitty.cpp

#include <algorithm>

#include <iostream>

#include <iterator>

#include <ostream>

#include <vector>

using namespace std;

 

int main() {

    vector<int> v;

 

    int i = 0;

 

    generate_n(back_inserter(v), 10, [&] { return i++; });

 

    for_each(v.begin(), v.end(), [](int n) { cout << n << " "; });

    cout << endl;

 

    cout << "i: " << i << endl;

}

 

C:\Temp>cl /EHsc /nologo /W4 nullarykitty.cpp > NUL && nullarykitty

0 1 2 3 4 5 6 7 8 9

i: 10

 

 

 

 [&]() { return i++; } 비교해서 2문자( () )  빠져 있습니다 . 

lambda-parameter-declaration  생략하는  여러분들 마음입니다.

 

 

장난 삼아 아래의 코드는 C++0x 에서 유효한 코드입니다.:

C:\Temp>type nokitty.cpp

int main() {

    [](){}();

    []{}();

}

 

 

 

위의 예제는 아무런 동작을 하지 않는  개의 람다를 생성합니다.

( 번째는 lambda-parameter-declaration 있고  번째는 없습니다.)

 

추가적으로 사용할  있는 lambda-parameter-declaration  문법적으로아래와 같이 구성되어있습니다. :

 

( lambda-parameter-declaration-listopt ) mutableopt exception-specificationopt lambda-return-type-clauseopt

 

그래서 mutable 이나 ->리턴타입  지정하고 싶으면 lambsa-introducer( [] )  사이에  공간이 필요 합니다.

 

마지막으로람다는 보통의 함수 객체를 만들어 내기 때문에 , 함수 객체들을 tr1::function  보관  수도 있습니다.:

C:\Temp>type tr1kitty.cpp

#include <algorithm>

#include <functional>

#include <iostream>

#include <ostream>

#include <vector>

using namespace std;

using namespace std::tr1;

 

void meow(const vector<int>& v, const function<void (int)>& f) {

    for_each(v.begin(), v.end(), f);

    cout << endl;

}

 

int main() {

    vector<int> v;

 

    for (int i = 0; i < 10; ++i) {

        v.push_back(i);

    }

 

    meow(v, [](int n) { cout << n << " "; });

    meow(v, [](int n) { cout << n * n << " "; });

 

    function<void (int)> g = [](int n) { cout << n * n * n << " "; };

 

    meow(v, g);

}

 

C:\Temp>cl /EHsc /nologo /W4 tr1kitty.cpp > NUL && tr1kitty

0 1 2 3 4 5 6 7 8 9

0 1 4 9 16 25 36 49 64 81

0 1 8 27 64 125 216 343 512 729

 

 

반응형
반응형
http://blog.naver.com/kaydenkross/30167616348

자식 디폴트 복사생성자는
부모 디폴트 복사상성자를 자동으로 호출한다.
 
하지만 자식복사 생성자를 사용자 정의로 만들어주면 이 복사생성자는 부모의 디폴트 생성자를 호출한다.

자식클래서에서 어떤 생성자를 만들건간에, 반드시 멤버이니셜 라이져로 부모 생성자를
호출해 줘야만한다. (이니셜라이져 사용안할시에는 부모의 디폴트 생성자를 호출)

자식 디폴트 대입연산자는 부모디폴트 대입연산자를 호출한다.
자식 디폴트 연산자를 임의로 만들경우에는 반드시 그 안에 부모 대입연산자 호출부가 들어가야한다.




반응형
반응형

http://blog.naver.com/thehighway/150011611500



반응형
반응형

http://gdiary-tmp.tistory.com/8


UB : OS (정확히는 MS Winows 계열) 의 버전 정보를 알아내는 함수.

XP / Vista / 7    각기 다른 세종류의 Windows가 난무(?)하는 요즘,
현재 시스템의 Windows 버전에 대한 정보를 알아오기 위한 방법.


OSVERSIONINFO osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFO));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osvi);


위 4라인만 돌려주면  osvi 라는 변수에 Windows의 정보가 들어가게 된다.

대충 사용할수 있는 변수를 보면

메이저버전 : osvi.dwMajorVersion
마이너버전 : osvi.dwMinorVersion
빌드넘버 : osvi.dwBuildNumber

Windows 7의 경우  메이저(6), 마이너(1) 이며 Vista 의 경우 메이저(6), 마이너(0) 이다.






좀더 자세한 것은


//ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.ko/sysinfo/base/osversioninfo_str.htm


에 나옴

반응형
반응형





class B{

public :

B(){

std::cout<<"B의 생성자 호출"<<std::endl;

}

~B(){

std::cout<<"B의 소멸자 호출"<<std::endl;

}

};


template<class T>

T* alloc(int _size)

{

void* buff = malloc(_size);

return new(buff) T;

}


template<class T>

void dealloc(T* _p)

{

_p->~T();

free(_p);

}




int main(){



B* addrs =  alloc<B>( sizeof(B) );


dealloc(addrs);


....

}






http://drunkenpsycho.tistory.com/13 에서 일부 발췌



malloc / new 차이점

  C++에서는 기본적으로 malloc과 new 모두 사용이 가능하다. 그렇다면 차이점은 어떤 것이 있고, 어떠한 경우에 malloc이나 new를 선택하느냐를 알아보자.

  1) malloc은 기본적으로 라이브러리 제공 함수로, 함수 콜을 요청하게 된다. 하지만, new는 C++ 언어에서 제공하는 기본 키워드로, 별도의 라이브러리 추가없이 바로 사용이 가능하다.

  2) malloc은 기본적으로 사이즈를 매개변수로 받고, 리턴타입이 void *형이므로 sizeof 와 캐스트 연산자의 도움을 받아야 쉬운 코딩이 가능하다. 하지만 new는 할당할 타입을 지정하면, 알아서 할당할 타입의 포인터로 넘어오기 때문에, 할당할 타입과 같은 타입의 포인터 변수로 받아오기만 하면 끝이다.

  3) malloc은 메모리를 동적으로 할당하는 것만이 목적이므로 초기값을 지정해 줄 수 없지만, new의 경우는 할당과 동시에 초기화가 가능하다. 

  4) new 키워드는 생성자를 자동으로 호출하게 된다. 생성자는 객체를 자동으로 초기화 해주는 함수로, malloc과 new의 가장 큰 차이점이다. 





http://kldp.org/node/89335




template<class T> 
T* alloc(int _size)
{
      void * buff = malloc(_size);
      return new(buff) T;             // 생성자 호출됨. 
}




template<class T>
void destroy(T* p)
{
  p->~T();
  free(p);
}


반응형
반응형

혹시 가상함수는 Inline 되지 않는다고 생각하는 분들도 아마 있지 않을까 생각하는데 경우에 따라서 되기도 하고 안되기도 합니다.

 

되는 경우는 컴파일 시점에서 어떤 클래스의 가상함수를 사용하는지 알수 있느냐 입니다.

struct Base

{

    virtual void Function(void) const = 0:

};

 

struct Delived

{

    virtual void Function(void) const

    {

         std::cout << "Hello" << std::endl;

    }

};

 

int main(int, char**)

{

    Delived  delived;

    delived.Function();

     return 0;

}

위 코드의 경우 컴파일 시점에서 Delived 클래스의 Function 멤버를 사용한다는 것을 알 수 있디 때문에 가상함수인 Function Inline화 할 수 있습니다.

 

 

struct Base

{

    virtual void Function(void) const = 0:

};

 

void Call( Base &base )

{

    base.Function();

}

 

struct Delived:  public Base

{

    virtual void Function(void) const

    {

         std::cout << "Hello" << std::endl;

    }

};

 

int main(int, char**)

{

    Delived   delived;

    Call( delived );

     return 0;

}

그럼 이 코드의 Function 멤버는 Inline화 할 수 있을까요물론 됩니다이유는 이것도 컴파일 시점에서 어떤 클래스의 Function을 사용하는지 알 수 있기 때문입니다.

 

 

그러나 아래의 코드는 Function 멤버를 Inline화 할 수 없습니다.

struct Base

{

    virtual void Function(void) const = 0:

};

 

struct Delived:  public Base

{

    virtual void Function(void) const

    {

         std::cout << "Hello" << std::endl;

    }

};

 

struct Null  : public Base

{

    virtual void Function(void) const{}

};

 

int main( int argc, char** )

{

    Delived   delived;

    Null        null;

    

    Base  *base = 1 < argc ? static_cast<Base*>( &delived ) : static_cast<Base*>( &null );

     base->Function();

 

    return 0;

}

이유는 설명 안 해도 되겠죠? ^^

 

 

 

출처 : http://d.hatena.ne.jp/anonymouse_user/20120309/1331303971 

 


반응형
반응형
#pragma comment( linker, "/entry:WinMainCRTStartup /subsystem:console")

전 블로그에 올린거 같은기억이;;;

 



반응형
반응형


static_cast와 dynamic_cast의 속도 비교를 해 보았다.



아래는 실험한 코드와 결과 화면이다. debug로 테스트 한 결과다.





  1. // cmd.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.  
  2. //  
  3.   
  4. #include "stdafx.h"  
  5.   
  6. class object  
  7. {  
  8. public:  
  9.     virtual void print() { cout << "object" << endl; }  
  10.     virtual void copy(TCHAR* a) {}  
  11. };  
  12.   
  13. class AAA : public object  
  14. {  
  15. public:  
  16.     virtual void print() { cout << "AAA" << endl; }  
  17.     void copy(TCHAR* a) { _tcscpy( str, a ); }  
  18.       
  19. private:  
  20.     TCHAR str[1024];  
  21. };  
  22.   
  23. int _tmain(int argc, _TCHAR* argv[])  
  24. {  
  25.     object* mTestObject = new AAA;  
  26.   
  27.     CCheckTime t; t.start();  
  28.       
  29.     for(DWORD i=0; i<10000000; i++)  
  30.         static_cast<AAA*>(mTestObject)->copy("KOREA");  
  31.   
  32.     cout << "static_cast : " << t.end() << "ms" << endl;  
  33.     t.start();  
  34.   
  35.     for(DWORD i=0; i<10000000; i++)  
  36.         dynamic_cast<AAA*>(mTestObject)->copy("KOREA");  
  37.   
  38.     cout << "dynamic_cast : " << t.end() << "ms" << endl;  
  39.   
  40.     delete mTestObject;  
  41.     return 0;  
  42. }  





2배 정도 차이나는걸로 보이는데 릴리즈로 컴파일하게 되면 더 차이가 날 듯 하다.

아마도 검사하는 부분이나 기타 설정부분에서 dynamic_cast가 하는 일이 많으니 이런 일이 생기는듯 하다..


아마도 상속관계를 확실히 알고 있다면 dynamic_cast보다는 static_cast로 작성하는것이 코드 구동 시간에서도 최적화에서도 더 나은 선택, 반드시 선택해야 될듯 하다.


반응형
반응형


http://blog.naver.com/ultimidou/130095833416

http://blog.naver.com/PostView.nhn?blogId=kimgudtjr&logNo=140116


메모리 - 가상 메모리 할당 함수, VirtualAlloc, 예약, 확정, 보호속성, 대용량 메모리 할당

 
[ C 런타임 함수 ]

 

응용 프로그램에서 메모리가 필요할 경우 운영체제에게 메모리 할당을 요청한다. 운영체제는 원칙적으로

응용 프로그램의 메모리 할당 요청을 거절하지 않으며 한 번 할당한 메모리는 해제하기 전에는

다른 할당 요청에 사용되지 않는다. Win32에서 메모를 할당하는 바업에는 여러 가지가 있는데 가장

간단한 방법이 C 런타임 함수를 사용하는 것이다. 4G 평면 메모리 모델의 간단한 구조 덕분에

malloc, free 두 함수만 사용해도 기본적인 메모리 할당은 할 수 있다 원형은 다음과 같다.

 void *malloc(size_t size);
 void free(void *membolck);

malloc의 인수로 할당하고자 하는 메모리의 크기를 바이트 단위로 밝히기만 하면 된다. 운영체제는

물리적인 메모리를 할당하여 가상 주소 공간에 맵한 후 그 번지를 리턴한다. 이 때 리턴되는 값은

void 포인터 형이므로 반드시 원하는 타입으로 캐스팅해야 한다. 만약 메모리 부족으로 할당이 되지

않을 경우는 에러에 해당하는 NULL을 리턴한다. win16에서는 malloc의 리턴 값을 반드시 점검해 보아야

했으니나 Win32에서는 메모리 할당이 실패하는 경우가 극히 드물기 때문에 종종 리턴값 점검을 생략한다.

(그렇다고 해서 생략하는 것이 반드시 좋다는 뜻은 아니다.) malloc 으로 할당한 메모리를 다시 사용 후

free로 해제한다.

 

------------------------------------------------------------------------
메모리 할당 해제 예제 생략... (너무 쉬우므로..)
------------------------------------------------------------------------

 

참고로 잘 사용되지는 않지만 malloc와 유사한 다음과 같은 할당 함수들이 있따.

void *calloc(size_t num, size_t size);
void *realloc(void *memblock, size_t size);

calloc은 size크기의 변수값 num개분에 해당하는 메모리를 할당한다. malloc(size*num) 과 동일하되

다만 필요한 메모리 양을 좀더 논리적으로 나타낸다는 점만 다르다.  realloc은 이미 할당된 메모리의

크기를 변경하여 재할당하는 함수이다. 이미 할당한 메모리르 더 크게 할당하거나 더 작게 축소하고자 할 때

realloc 함수를 사용한다. 확장시는 연속된 공간에 재할당하기 위해 메모리의 위치가 변경될 수도 있다.

malloc, free 함수는 Win16 에서는 물론이고 그 이전의 도스 프로그래밍에서도 사용하던 아주 오래된 함수이다.

malloc은 절대 번지를 할당하기 때문에 (Win16 환경에서는 절대번지 할당) 운영체제가 메모리르 이동하거나

스왑할 수 없는 몇 가지 잠재적인 문제가 있어 Win16 환경에서는 잘 사용되지 않았다. 그러나

가상 주소 공간을 지원하는 Win32 환경에서는 다시 이 함수들이 사용되는데 Win32의 메모리는 원칙적으로

이동 가능(Moveable)하며 언제든 스왑(Descardable) 할 수 있기 때문이다.

운영체제가 필요에 의해 가상 메모리상의 위치를 옮기더라도 페이지 테이블을 같이 수정하면 응용 프로그램은

자신이 알고 있는 포인터로 계속 그 메모리를 사용할 수 있다. 가상 쉽고 또 무난하므로 특별한 기능 제한

(메모리 보호, 엑세스 지정 등) 없이 메모리 할당 자체가 목적이라면 이 함수만 사용해도 무방하다.

C++의 객체를 동적으로 할당할 때는 new연산자를 사용한다. new 연산자를 피연산자로 주어진  클래스 형

객체만큼의 메모리를 할당할 뿐만 아니라 해당 클래스의 생성자를 호출하여 객체를 초기화하기까지 한다.

그리고 해당 객체의 포인터를 리턴한다. 이렇게 동적으로 생성된 객체는 delete 연산자로 파괴하는데

다음이 간단한 사용 예이다.


 MyClass *pObj;
 pObj = new MyClass(1,2);
 // pObj를 사용한다.
 delete pObj;

하나의 객체뿐만 아니라 객체 배열을 할당할 수도 있다. 객체 위주로 프로그램을 작성할 때는 주로

이 연산자를 사용하여 메모리르 할당한다. C++ 언어가 제공하는 연산자이므로 자세한 문법에 대해서는

C++ 문법서를 참고하기 바란다.


================================================================================
[ 가상 메모리 할당함수 ]

 

응용 프로그램이 필요로 하는 메모리르 할당할 때는 앞에서  살펴본 malloc 함수 , new 연산자만 써도

충분하다. Win32에서 추가된 가상 메모리 할당 함수들은 전통적인 이런 함수들에 비해 몇 가지 추가적인

이점들을 제공하며 메모리에 대한 좀 더 섬세한 통제를 할 수 있다. 단순히 할당해서 사용할 목적이라면

malloc 함수를 쓰고 가상 메모리 구조의 이점을 활용하고 싶다면 가상 메모리 함수를 사용하면 된다.

가상 메모리 함수가 malloc 함수에 비해 가지는 이점은 다음 두 가지이다.


------------------------------------------------------------------------------------
○ 메모리를 예약 상태로 할당할 수 있다. 예약이란 물리적인 메모리를 소비하지 않으면서 주소 공간만을

미리 할당해 놓는 방법이다. 이렇게 예약된 페이지는 필요할 때 언제든지 필요한 부분만 확정해서

사용할 수 있으므로 realloc 회수를 줄일 수 있다.

○ 할당한 메모리의 엑세스 권한을 지정할 수 있다. malloc으로 할당한 메모리는 언제나 읽기/쓰기가

가능하지만 가상 메모리 함수로 할당한 메모리는 읽기 전욕, 액세스 금지 속성을 가질 수 있어

실수로 인한 데이터 파괴를 막을 수 있다.
------------------------------------------------------------------------------------

 

이 외에도 여러 가지 차이가 있지만 대부분이 이 두 가지 차이점으로 인해 파생되는 차이점이다.

가상 메모리 할당 함수는 일단 다음 두 가지가 있다. 할당 할때는 VirutalAlloc 함수를 사용하고

해제할 때는 VirtualFree 함수를 사용한다.


LPVOID VirtualAlloc(LPVOID lpAddress, DWORD dwSize, DWORD flAllocationType, DWORD flProtect);

BOOL VirtualFree(LPVOID lpAddress, DWORD dwSize, DWORD dwFreeType);

첫 번째 인수 lpAddress는 할당하고자 하는 메모리의 절대 번지를 지정하되 NULL이면 시스템이 알아서

할당 번지를 지정한다. 동적으로 메모리를 할당할 때 할당 위치는 별 의미가 없으므로 보통 NULL을 주되

예약된 페이지를 확장할 때는 예약되어 있는 번지를 지정해야 한다. 두 번째 인수 dwSize는 할당하고자

하는 메모리의 양을 바이트 단위로 지정한다. 세 번째 인수 flAllocationType은

할당 방법을 지정한다.

 

------------------------------------------------------------------------------------
할당 방법  설명
------------------------------------------------------------------------------------
MEM_RESERVE 물리적인 메모리의 할당없이 주소 공간만을 예약한다.

MEM_COMMIT 물리적인 메모리를 확정한다.

MEM_TOPDOWN 가급적 높은 번지에 메모리를 할당한다. NT이상에서만 쓸 수 있다.
------------------------------------------------------------------------------------

 

네 번째 인수 flProtect는 할당한 페이지의 액세스 타입을 지정하며 보통 PAGE_READWRITE로 지정한다.

이에 대해서는 잠시 후에 자세하게 알아볼 것이다. 메모리 할당에 성공하면 할당된 메모리의 번지를

리턴하며 실패했을 경우 NULL을 리턴한다.


VirtualFree 함수는 할당된 페이지를 해제한다. 첫 번째 인수 lpAddress는 해제하고자 하는 메모리의

선두 번지를 지정하며 두 번째 인수 dwSize는 해제하고자 하는 메모리의 크기를 지정한다.

세 번째인수 dwFreeType은 다음 두 값중 하나이되 둘을 같이 쓸 수는 없으며 반드시 따로 사용해야 한다.

만약 확정된 메모리르 해제하려면 확장 해제 후 예약 해제해야 한다.


------------------------------------------------------------------------------------
값  설명
------------------------------------------------------------------------------------
MEM_DECOMMIT 확정된 페이지를 확정 해제한다.

MEM_RELEASE 예약된 페이지를 예약 해제한다.
------------------------------------------------------------------------------------

다음은 가상 메모리 할당 함수를 사용하는 간단한 예이다.

------------------------------------------------------------------------------------
ptr = (int*)VirtualAlloc(NULL, sizeof(int)*10, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

VirtualFree(ptr,sizeof(int)*10,MEM_DECOMMIT);
VirtualFree(ptr,0,MEM_RELEASE);

------------------------------------------------------------------------------------


================================================================================

[ 예약과 확정 ]

 

Win32 프로세스가 가지는 4G의 가상 주소공간은 "페이지"라는 단위로 구성된다.  한 페이지의 크기는

시스템 마다 다른데 가장 많이 사용되는 인텔 계열의 CPU에서는 4K 바이트의 크기를 가진다.

윈도우즈는 페이지 단위로 주소 공간을 관리한다.  즉 할당하거나 해제하는 단위가 페이지 단위라는

뜻이다. 주소 공간을 구성하는 각 페이지는 다음 세 가지 상태 중 하나의 상태로 존재한다.


------------------------------------------------------------------------------------
상태  설명
------------------------------------------------------------------------------------
자유 영역(Free)  - 사용되지 않는 자유 영역이다. 언제든지 예약하거나 확정할 수 있다.

예약(Reserved)  - 장래 사용을 위해 예약만 되어 있는 페이지이며 물리적인 메모리가 할당되어 있지 않다.
  주소 공간만 할당되어 있는 상태이다.

확정(Committed) - 가상 메모리와 연결되어 있는 상태이며 바로 사용할 수 있다. 물리적 메모리를 소모한다.
------------------------------------------------------------------------------------

 

프로세스가 처음 실행되었을 때 부부분의 주소 공간은 자유 영역일 것이며 실행 파일의 이미지와 공유 DLL등이

확정되어 사용될 것이다. 자유 영역으로 남아있는 주소 공간은 언제든지 할당해 사용할 수 있는데

할당의 수준이 예약과 확정 두 종유가 있다. 예약이란 말 그대로 주소 공간만 할당하여 이 번지가

다른 목적으로 사용되지 않도록 하는 것이며 확정은 물리적인 메모리가 실제로 필요할 때에

RAM 또는 페이징 파일을 주소 공간에 연결(MAP)하는  것이다.

 Win16에는 없던 이런 예약과 확정이라는 것이 왜 필요하게 되었는가 하면 물리적인 메모리와

논리적인 주소공간이 분리되었기 때문이다. 논리적인 주소 공간을 할당하는 것이 예약이고 에약된 주소

공간에 물리적인 메모리르 연결하는 것이 확정이다. 주소 공간만을 할당하는 예약은 물리적인 메모리를

전혀 소모하지 않는다. 그래서 충분한 주소 공간을 미리 예약해 두어도 전혀 손해를 볼 것이 없다.

일단 예약된 주소 공간은 다른 할당 요청에 의해 다시 할당되지 않으므로 필요할 때마다 물리적인

메모리를 확정해서 사용하면 된다.

예를 들어 어떤 프로그램에서 10M바이트의 연속적인 메모리가 필요하다고 하자 그런데 당장은 이 메모리가

한꺼번에 사용되지 않지만 반드시 연속적인 메모리여야 한다면 일단 10M의 주소 공간을 예약해 둔다.

예약만 했으므로주소 공간만 할당되었을 뿐 물리적인 메모리는 전혀 소모하지 않았다. 그리고 필요할 때마다

원하는 위치의 주소 공간을 확정하여 물리적 메모리와 연결하여 사용하면 된다. 주소 공간이 연속되어

있으므로 예약된 주소와 연결되는 물리의 번지가 반드시 연속되지 않아도 아무 문제가 없다.

메모리를 예약할 것인가 확정할 것인가는 VirtualAlloc 함수의 세 번째 인수 flAllocationType으로

지정하는데 예약만 할 때는 MEM_RESERVE를 주고 예약된 메모리를 확정할 때는 MEM_COMMIT를 준다.

예약과 동시에 확정하려면 두 플래그를 OR로 묶어서 같이 지정한다. 예약과 확정을 따로 하고 싶다면

다음과 같이 두 번 호출한다.

ptr = (int*)VirtualAlloc(NULL,sizeof(int)*10,MEM_RESERVE,PAGE_READWRITE);
ptr = (int*)VirutalAlloc(NULL,sizeof(int)*10,MEM_COMMIT,PAGE_READWRITE);

예약에 의해 주소 공간이 임의의 번지에 먼저 할당되고 확정에 의해 이 주소 공간이 가상 메모리에

맵된다. 예약만 하고 확정은 하지 않은 상태는 주소 공간만 할당되어 있고 물리적인 메모리와 맵되어

있지 않은 상태이기 때문에 실제 메모리 구실을 할수 없다. 따라서 다음과 같은 코드는 AccessViolation

예외를 발생시킨다.

ptr = (int*)VirtualAlloc(NULL,sizeof(int)*10,MEM_RESERVE,PAGE_READWRITE);

ptr[0] = 'S';

메모리를 예약만 했는데 이 때 예약된 번지수가 리턴되기는 하지만 이 번지는 가상 주소 공간의

한 지점일 뿐 실제 물리적인 메모리와 연결되지 않았다. 그러므로이 번지에 무엇인가 값을 대입하는 것은

불법이며 읽기만 해도 불법이다.


================================================================================

[ 할당 단위와 페이지 ]

 

VirtualAlloc 함수가 메모리를 할당할 때는 바이트 단위를 사용하지 않는다. 4G나 되는 주소공간을

바이트 단위로 사용하는 것은 너무 비효율적이기 때문에 일정한 단위로 주소 공간을 분할한다. 마치

하드딧크가 바이트 단위로 파일을 기록하지 않고 섹터, 클러스터 단위를 사용하는 것처럼 말이다.

클러스터 단위로 파일을 기록하면 낭비되는 디스크 공간이 생기지만 속도는 훨씬 더 빨라진다.

가상 주소 공간의 단위는 두 가지가 있다. 우선 할당의 시작점을 지정하는 할당 단위(Allocation Granualrity)가

있고 할당의 크기를 지정하는 페이지(Page)가 있다. 이런 단위를 사용하여 메모리를 관리하는 이유는

메모리가 지나치게 조각나는 것을 방지하고 좀 더 신속하게 메모리를 관리하기 위해서이다. 물론 바이트

단위로 메모리를 관리하는 것이 효율 면에서는 이상적이지만 이렇게 하면 페이지 테이블이 지나치게

커질 것이며 운영체제는 너무 너무 바빠질 것이다.

VirtualAlloc으로 메모리를 할당(예약하거나 확정)할 때 그 시작점은 반드시 할당 단위의 경계선에

정렬된다. 즉 할당 단위의 배수 위치에서 할당이 시작된다. 대부분의 플랫폼에서 할당 단위는 64K이므로

가상 메모리 공간은 64K 단위로 할당된다고 할 수 있다.  예를 들어 다음과 같이 예약 명령을

내렸다고 하자.

ptr = (int*)VirtualAlloc(0x71234, sizeof(int)*10, MEM_RESERVE, PAGE_READWRITE);

예약의 시작점을 0x71234번지로 강제로 지정하였다. 그러나 운영체제는 정확하게 이 번지에서 할당을

시작하지 않고 할당 단위의 배수가 되도록 번지를 내림하여 할당한다. 이 경우 실제 예약되는 번지는

0x70000번지가 된다. 하위 2바이트를 0으로 만든 번지에서 할당된다고 생각하면 된다.

또 할당된 영역의 크기는 반드시 페이지 단위의 배수가 된다. 페이지의 크기는 플랫폼에 따라서 다른데

인텔을 비록한 대부분의 시스템에서 페이지 크기는 4K바이트이다. 예를 들어 10K의 크기만큼 할당을

요청했다면 실제로 할당되는 영역의 크기는 12K가 될 것이다. 현재 플랫폼에서 할당 단위와 페이지 크기를

조사하고 싶으면 GetSystemInfo 함수를 사용하면 된다. 다음은 이 함수를 사용하여 할당 단위와 페이지

크기를 조사하는 간단한 예제이다.

<GetSystemInfo 함수사용 예> (독자는 콘솔어플리케이션으로 했음..)


---------------------------------------------------------

#include <stdio.h>
#include <windows.h>

int main()
{

 SYSTEM_INFO si = {0};
 
 GetSystemInfo(&si);

 printf("Page Size : %d\n",si.dwPageSize);
 printf("Min Addr : %d\n",(int)si.lpMinimumApplicationAddress);
 printf("Max Addr : %d\n",(int)si.lpMaximumApplicationAddress);
 printf("Alloc Gra : %d\n",si.dwAllocationGranularity);
 

 return 0;
}
---------------------------------------------------------


할당 단위는 Wion32가 실행되는 현재까지의 모든 플랫폼에서 64K이며 페이지 크기는 Alpha 시스템만

8K이고 그 외의 CPU(Intel, MIPs, PowerPC)에서는 모두 4K이다.  모든 플랫폼에서 이상없이 돌아가는

프로그램을 만들려면 GetSystemInfo 함수로 이 값들을 잘 조사해 보아야 하나 내부적인 메모리 관리에만

사용되므로 응용 프로그램 수준에서 실제로 이 정보가 유용한 경우는 별로 없다.

 

================================================================================

[ 보호 속성 ]

 

VirtualAlloc의 네 번째 인수 flProtect는 할당하고자 하는 메모리의 액세스 타입(Access Protection)을

지정한다. Win32 가상 메모리의 특징 중 하나가 메모리 페이지마다 액세스 타입을 설정하여 미연의

실수를 방지할 수 있는 점이다. 마치 파일에 읽기 전용 속성을 주어 실수로 지워지지 않도록 하는 것과

개념적으로 동일하다 지정 가능한 플래그는 다음과 같다.   이 중 PAGE_GUARD, PAGE_NOCACHE 플래그는

PAGE_NOACCESS 플래그를 제와한 다른 플래그와 함께 사용할 수도 있다.


< AirtualAlloc의 네 번째 인자에 줄 수 있는 옵션 값 >
---------------------------------------------------------------------
액세스 권한  설명
---------------------------------------------------------------------
PAGE_READONLY  읽기만 가능하다. 이 메모리는 쓰기를 할 수 없다.
PAGE_READWRITE  읽기 쓰기 가능하다.
PAGE_EXECUTE  실행만 가능하다. 읽기, 쓰기 모두 할 수 없다.
PAGE_EXECUTE_READ 실행, 읽기, 쓰기를 가능하다.
PAGE_GUARD  보호 페이지로 지정한다. 이 페이지에 읽기 , 쓰기를 시도하면
   STATUS_GUARD_PAGE 예외가 발생하며 보호 페이지 상태가 해제
   된다. 보호 페이지는 메모리의 끝을 표시하는 용도로 주로 사용된다.
   NT 이상만 지원한다.
PAGE_NOACCESS  어떤 액세스도 하지 못하게 한다. 읽기, 쓰기 , 실행 어떤 것도 하지 못하게 한다.
PAGE_NOCACHE  캐시를 금지시킨다. 일반적으로 응용 프로그램은 이 플래그를 사용하지 않는 
   것이 좋다. 디바이스 드라이버 등의 시스템 소프으웨어에서 이 플래그를
   사용한다.
PAGE_EXECUTE_WRITECOPY 공유된 영역에 쓰기를 할 때 사본을 작성한다. NT이상만 지원한다. 
---------------------------------------------------------------------


일반적인 용도로 읽기도 하고 쓰기도 할 메모리라면 PAGE_READWRITE 로 지정하여 마음대로 읽고 쓰도록

하면 된다. 그러나 특정 목적으로 읽기만 하고 쓰지는 못하게 하려면 PAGE_READONLY 플래그를 지정한다.

이렇게 할당된 메모리는 읽을 수는 있어도 쓸 수는 없다. 만약 읽기 전용의 메모리에 쓰기를 시도하면

Access Violation 예외가 발생한다


ex) 
ptr = (int*) VirtualAlloc(NULL,sizeof(int)*10, MEM_RESERVE | MEM_COMMIT, PAGE_READONLY);

int i = ptr[0];  // 읽기 가능
ptr[0] = 'x'; //쓰기 불가능

읽기 전용, 쓰기 전용의 말은 다 이해하겠는데 실행 전용 이라는 말은 선뜻 이해가 되지 않을 것이다.

읽기는 해당 메모리의 데이터를 읽을 수 있다는 말이며 실행은 해당 메모리의 코드를 CPU가

실행할 수 있다는 뜻이다. 현재까지의 모든 시스템(인텔,알파)은 읽을 수 있으면 실행도 가능하므로

사실 아직까지 읽기 전용은 같은 뜻이다. Win32가 플랫폼에 독립적인 API 이다 보니 미래에 읽기와

실행을 구분하는 CPU를 위해 이런 액세스 타입을 미리 준비해두고 있을 분이다. 메모리의

엑세스 타입은 할당할 때 지정하지만 실행중에도 다음 함수를 사용하여 변경할 수 있다.

하지만 응용 프로그램 수준에서 메모리의 액세스 타입을 그거솓 실행중에 변경하는 경우는 지극히 드물며

예를 들기도 무척 곤란하므로 이런 함수도 있다는 것만 알아두자. 꼭 예를 든다면 아주 중요하나 데이터를

특정 함수만 변경하고 싶을 때 읽기 전요응로 만들어 두고 이 함수에서만 액세스 타입을 잠시  바꾸는

정도가 있다. 시스템의 함수를 훅킹하는 고난이도의 기법을 구사할 때 이 함수를 슨다.

가상 메모리의 보호 속성은 프로그램 로더가 사용한다. 응용 프로그램을 메모리로 읽어올 때 실행파일(PE)에

기록되어 있는 섹션 속성에 따라 쓰기가 가능한 메모리 영역과 읽을 수만 있는 메로리 영역이 구분되어

가상 메모리에 배치된다. 일반적으로 코드영역은 읽기 전용이며 번역변수 영역은 읽고 쓸 수 있는

영역이다. 하지만 문자열 상수는 읽기 전용 영역에 배치되므로 이 값은 실행중에 변경할 수 없다.


다음 예제를 보자

------------------------------------------------------------------
#include <stdio.h>


char *str = "string";
int i = 0;

int main()
{
 
 str[0] = 'A'; // 메모리 액세스 에러

 i = 10;  // 메모리 액세스 성공

 

 return 0;
}
------------------------------------------------------------------

 

위에서 str[0] = 'A'는 에러가 날것이고 i = 10은 에러가 나지 않을 것이다.

이것이 문자열 상수 영역과 정 전역변수 영역의 메모리 접근 권한의차이 이다.


16비트의 메모리는 이런 구분이 없기 때문에 우발적인 사고로 코드 영역을 건드릴 수도 있었는데

32비트의 가상 메모리는 메모리에 속성을 부여함으로써 변경되지 말아야 할 영역을 보호 할 수 있다.


PAGE_GUARD 속성은 가드 페이지를 설정함으로써 효율적인 스택 확장에 사용된다.

스택은 기본적으로 1M 예약되며 그 중 한 페이지 분량이 4K 정도만 확정된 채로 생성된다.

예약된 1M는 스택의 최대 크기이며  확정된 페이지는 당장 사용할 수 있는 영역인데 스택을 많이

사용하면 확정을 점점 늘린다. 이때 스택을 언제 확장할지를 결정하기 위해 운영체제가 사용하는

속성이 PAGE_GUARD이다. 스택이 확장되는 과정은 아래와 같다.


 ---------------------------------
 예약| 예약 | 예약 | 가드 | 확정   ( 확장 전)
 ---------------------------------
 낮은번지   높은번지

 ---------------------------------
 예약| 예약 | 가드| 확정 | 확정   ( 확장 후)
 ---------------------------------
 낮은번지   높은번지

 격자 하나가 1페이지 이다.


최초 제일 높은 번지의 한 페이지만 확정되어 있으며 이 안에서 함수를 호출하고 지역 변수를 생성한다.

그러다가 스택 사용량이 늘어 가드 페이지에 닿으면 예외가 발생하는데 운영체제는 이 예외가 발생했을 때

가드 페이지를 추가로 확정하고 바로 다음의 예약 페이지를 다시 가드 페이지로 설정한다.

만약 가드 페이지가 없다면 스택을 액세스할 때마다 확정 여부를 점검해야 하므로 무척 비효율적일 것이다.


이런 식으로 스택은 점차적으로 확장되는데 단, 마지막 페이지를 확정하지 않으므로써 예약된 1M 영역을

절대로 넘지는 않는다. 만약 스택 크기다 !M를 넘어서 오버플로우가 발생했다면 이는 대개의 경우

프로그램의 논리가 잘못된 것이다. 스택은 함수를 호출함에 따라 오르락 내리락거리는 것인데

이 정도로 커졌다면 재귀 호출이 잘못된 경우이므로 예외를 일으키고 죽도록 내버려 두는 편이 더 낫다.

참고로 95/98 에서는 PAGE_GUARD 속성이 지원되지 않기 대문에 PAGE_NOACCESS 속성이 대신 사용된다.


================================================================================

[ 메모리 잠금 ]

 

가상 주소 공간은 논리적으로 존재할 뿐이며 데이터나 코드가 저장되는 곳은 가상 메모리 이다.

여기서 가상 메모리라고 칭하는 것은 RAM과 하드 디스크의 페이징 파일을 합쳐 부르는 말이다.

페이징 파일도 RAM보다 좀 느릴 뿐이지 분명히 메모리이다. 운영체제는 RAM과 페이징 파일 사이를

끊임없이 교체하면서 돌아간다. 당장 필요한 부분은 RAM으로 읽혀지며 당분간 사용되지 않을 부분은

페이징 파일로 이동된다.

운영체제의 이런 가상 메모리 관리는 응용 프록램 입장에서 볼 때 완전히 투명하다. 즉 원하는 데이터가

RAM에만 있도록 할 수 있다. 즉 페이징 파일로 복사하지 못하게 금지할 수 있는데 이렇게 하면 원하는 데이터를

물리 RAM에서 바로 찾을 수 있으므로 속도가 더 발라진다. 이 때는 다음 두 함수를 사용한다.


BOOL VirtualLock(LPVOID lpAddress, DWORD dwSize);
BOOL VirtualUnlock(LPVOID lpAddress, DWORD dwSize);

VirtualLock 함수는 lpAddress가 지정한 번지로부터 dwSize 길이 만큼의 메모리 페이지를 잠근다.

이렇게 되면 운영체제는 이 번지의 데이터는 페이징 파일로 보내지 않고 항상 RAM에 남아 있도록 하여

최대한 신속하게 사용할 수 있도록 보장한다. 이때 lpAddress 번지는 반드시 가상 메모리가 맵되어 있는

확정 상태여야만 한다. 그렇지 않으면 잠금 동작 자체가 무의미하다. 장금을 풀 때는 VirtualUnlock 함수를

사용한다.

잠겨진 메모리에 대해서는 페이징 파일로 스왑하지 않아 원하는 데이터가 RAM에 없는 상태(Page Fault) 가

발생하지 않는다. 단 예외적으로 해당 프로세스가 액티브 상태가 아니면 이 때는 잠간 페이지라도

페이징 파일로 이동시켜버릴 수 있다. 메모리를 잠그면 응용 프로그램은 자신이 필요로 하느 데이터를 언제나

RAM에서 읽으므로 더 빨리 실행될 수 있어서 좋겠지만 이 기능은 반드시 꼭 필요한 부분에만 신중하게

사용해야 한다. 그렇지 않고 남발하게 되면 운영체제의 시스템 관리 능력을 저해하여 자칫하면 전반적으로

속도가 느려질 수 있다.

Win32의 메모리 관리 알고리즘은 상당히 정교하며 효율적으로 작서오디어 있으므로 굳이 메모리를

잠그지 않아도 큰 불편 없이 쓸 수 있다. 메모리 잠금을 반드시 사용해야 하는 프로그램은 디바이스 드라이버

정도이다. 응용 프로그램 수준에서는 거의 쓸 일이 없으며 함부로 메모리 관리에 개입해서는 안된다.

멀티 태스킹은 운영체제의 지휘 아래응용 프로그램들의 자발적인 협조에 의해 부드럽게 돌아가는 것인데

특별히 이유없이 혼자서 자원과 시간을 독점하는 것은 금물이다.


================================================================================

[ 대용량 메모리 ]


가상 메모리를 제대로 사용하는 예제를 만들어 보자. 만약 어떤 프로그램이 실행중에 데이터를 읽어들이는데

다 읽기 전에는 정확한 용량을 알 수 없으며 최대 100M까지의 용량이 필요하다고 해 보자.

이 경우 필요한 최대값인 100M을 몽땅 할당해 놓고 작업을 시작한다면 시스템의 메모리 실장량에 따라

메모리 할당이 실패할 수도 있다. 실제 필요한 용량은 그보다 훨씬 작은데 그렇다고 해서

최대값을 무시할 수도없고, 이럴 때 바로 가상 메모리의 예약과 확정 기능을 사용한다. 다음 예제는

그 예를 보여 부는데 작업 관리자를 열어 놓고 메모리의 변화를 잘 관찰해 보아라.

 

<가상 메모리 할당함수 사용>
------------------------------------------------------------------------------------

#include <windows.h>
#include <windows.h>

#define MEGA 1048576
PBYTE stptr = NULL;
TCHAR Status[256] = TEXT("할당되지 않았습니다.");


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpszClass = TEXT("First");  //윈도우 이름 및 타이틀바에 등록할 문자열

void FreeRecords();
void ReadRecords();

void Error(const char *mes);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
 HWND hWnd;
 MSG Message;
 WNDCLASS WndClass;
 
 g_hInst = hInstance;

 //------------ 아래 부분은 윈도우 클래스를 설정해주는 것이다. --------------------

 WndClass.cbClsExtra = 0;
 WndClass.cbWndExtra = 0;
 WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
 WndClass.hCursor = LoadCursor(NULL,IDC_ARROW);
 WndClass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
 WndClass.hInstance = hInstance;
 WndClass.lpfnWndProc = WndProc;
 WndClass.lpszClassName = lpszClass;
 WndClass.lpszMenuName = NULL;
 WndClass.style = CS_HREDRAW | CS_VREDRAW;

 //------------ 위 부분은 윈도우 클래스를 설정해주는 것이다. --------------------

 RegisterClass(&WndClass);   //  <-- 여기서는 위에서 설정한 클래스를 등록한다.

 hWnd = CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
      CW_USEDEFAULT,NULL,(HMENU)NULL,hInstance,NULL);   // 설정하고 등록한 윈도우를 생성한다.

 ShowWindow(hWnd,nCmdShow);   //생성한 윈도우를 출력..(이 함수를 호출하지않으면 윈도우가 보이지 않는다.)

 while(GetMessage(&Message,NULL,0,0))   //사용자가 종료하기 전까지 반복해서 메세지 처리를 호출한다.
 {
  TranslateMessage(&Message);
  DispatchMessage(&Message);
 }

  return (int)Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) //여기서 실제로 메시지를 처리한다.
{
 HDC hdc;
 PAINTSTRUCT ps;
 TCHAR *Mes = TEXT("왼쪽 마우스 버튼 : 메모리 할당, 오른쪽 마우스 버튼:메모리 해제");

 switch(iMessage)
 { 
 case WM_CREATE:
  hWndMain = hWnd;
 srand(GetTickCount());
  return 0;

 case WM_LBUTTONDOWN:
  SetCursor(LoadCursor(NULL,IDC_WAIT));
 ReadRecords();
 SetCursor(LoadCursor(NULL,IDC_ARROW));
  return 0;

 case WM_RBUTTONDOWN:
  FreeRecords();
  return 0;

 case WM_PAINT:
  hdc = BeginPaint(hWnd,&ps);
  TextOut(hdc,50,50,Mes,lstrlen(Mes));
  TextOut(hdc,50,80,Status,lstrlen(Status));
  EndPaint(hWnd,&ps);
  return 0;

 case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
 }

 return DefWindowProc(hWnd,iMessage,wParam,lParam);  //프로그래머가 처리하지 않은 나머지 동작을 기본처리로 넘긴다.
}


void FreeRecords()
{
 if(stptr != NULL)
 {
  if(VirtualFree(stptr,(100 * MEGA),MEM_DECOMMIT) == 0)
    Error("메모리 확정 해제 실패");

  if(VirtualFree(stptr,0,MEM_RELEASE) == 0)
    Error("메모리 예약 해제 실패");

  stptr = NULL;
 }

 wsprintf(Status,TEXT("할당되지 않았습니다."));
 InvalidateRect(hWndMain,NULL,TRUE);
}

void ReadRecords()
{
 int c = 0;
 int RecSize = 0;

 PBYTE nowptr, endptr;

 FreeRecords();

 stptr = (PBYTE)VirtualAlloc(NULL,(100 * MEGA),MEM_RESERVE,PAGE_READWRITE);

 if(stptr == NULL) Error("메모리 예약 실패");

 nowptr = stptr;
 endptr = stptr;

 c = rand() % 90 + 10;

 for(int i=0; i<c; i++)
 {
  if( (endptr - nowptr) < MEGA )
  {
   VirtualAlloc(endptr,MEGA,MEM_COMMIT,PAGE_READWRITE);
   endptr += MEGA;
  }

  RecSize = ((rand() % 100)+1)*10240;
  memset(nowptr,i,RecSize);
  nowptr += RecSize;
  
 }

 wsprintf(Status,TEXT("예약:100 메가, 확정:%d 메가, 사용%d 메가"),(endptr-stptr)/MEGA,(nowptr-stptr)/MEGA);
 InvalidateRect(hWndMain,NULL,TRUE);
}

void Error(const char *mes)
{
 MessageBoxA(0,mes,"error",0);
 exit(0);
}
------------------------------------------------------------------------------------


ReadRecords 함수에서 네트워크 또는 DB에서 레코드를 메모리로 읽어들이는데 레코드의 개수는 정해져

있지 않지만 최대 100까지 읽을 수도 있다. 또한 레코드 크기도 실제로 읽어봐야 알 수 있는데

작게는 10K 정도 되고 큰 레코드는 1M 정도 된다. 이 경우 필요한 메모리는 레코드 개수와 크기에 따라

100K~100M에 달하는데 문제는 실시간으로 읽어들이는 레코드이기 때문에 필요한 메모리 용량을

정확하게 알 수 없다는 것이다. 미래의 일을 예측할 수 없는 이런 상황이 항상 문제가 되는데

생각보다 자주 발생한다.

그렇다고 해서 최대 용량인 100M를 다 할당하는 것은 무리고 대충 충분한 용량인 50M 정도를 할당하는 것도

꺼림직하다. 이럴 경우 100M의 주소 공간을 예약만 해 놓고 필요할 때 그때 그때 메모리를 확장해서

사용하면 된다. ReadRecords 함수에서 stptr에 100M의 주소 공간을 예약하는데 예약만 했기 때문에

실제 메모리를 소모하는 것은 아니다. 그리고 루프를 돌면서 필요한 레코드들이 이 메모리로 읽어들이되

남은 메모리가 1M 미만이면 추가로 1M을 확장해서 사용한다. 이때 추가 확정되는 메모리가 반드시

기존의 영역과 연속적일 필요는 없는데 어차피 주소 공간에서는 연속적인 메모리로 인식되기 때문이다.

예제의 enptr은 할당된 메모리의 끝이며 nowptr은 읽어들인 레코드의 끝이다.  endptr에서 nowptr을

뺀 용량, 즉 현재 남은 용량이 다음 읽을 레코드의 최대 크기인 1M보다 작을 때 1M을 더 확정하여

endptr을 1M 더 뒤쪽으로 이동시킨다. endptr과 nowptr의 간격이 항상 1M 이상이 되도록 유지하는 것이다.

이런 식으로 메모리를 예약, 확정하면 실시간으로 필요한 연속적 메모리르 시스템에 무리를 주지 않고

할당해 사용할 수 있다. FeeRecords에서는 예약 및 확정된 메모리를 해제한다.

실행주으이 모습은 다음과 같다. (생략)

예약된 메모리 용량과 확정된 메모리 용량을 보여준다. 필요한 메모리 용량으 정확하게 계산하기

힘들 때는 이 방법대로 메모리를 할당해서 사용하면 된다. 다음은 똑같은 문제를 malloc, realloc 함수로

다시 작성해 본것이다. 동일한 동작을 하며 필요할 때마다 메모리를 재할당한다.

<C 메모리할당 함수 사용>


------------------------------------------------------------------------------------
#define MEGA 1048576
PBYTE stptr = NULL;

void FreeRecords()
{
 if(stptr != NULL)
 {
  free(stptr);
  stptr = NULL;
 }

 wsprintf(Status,"할당되지 않았습니다.");
 InvalidateRect(hWndMain,NULL,TRUE);
}

void ReadRecords()
{
 int i, c;
 int RecSize;
 int AllocSize;
 int RemainSize;
 
 FreeRecords();
 
 AllocSize = RemainSize = 2*MEGA;
 stptr = (PBYTE)malloc(AllocSize);

 if(stptr == NULL) Error("메모리 할당 실패");


 c = rand() % 91 + 10;

 for(i=0; i<c; i++)
 {
  if(RemainSize < MEGA)
  {
   AllocSize += MEGA;
   RemainSize += MEGA;
   stptr = (PBYTE)remalloc(stptr,AllocSize);
  }

  RecSize = ((rand() % 1000)+1)*10240;
  memset(arpre+AllocSize-RemainSize,i,RecSize);
  RemainSize -= RecSize;
 }

 wsprintf(Status,"총 할당량:%d 멕, 사용: %d 메가",
  AllocSize/MEGA,(AllocSize-RemainSize)/MEGA);

 InvalidateRect(hWndMain,NULL,TRUE);
}

------------------------------------------------------------------------------------


똑같은 동작을 하기는 하지만 realloc 함수가 굉장히 느리기 때문에 전체적으로 속도가 느리다는 큰

차이점이 있고 재할당할 때마다 번지가 바뀔 수 있다는 단점이 있다. 속도를 좀 높이려면 재할당할때

여유분을 충분히 주는 방법을 사용할 수는 있지만 가상 메모리를 쓰는 것보다는 확실히 성능이 떨어진다.

대용량의 가변적인 메모리를 다룰 때는 가상 메모리의 이점을 활용하는 것이 더 좋다.

가상 메모리는 예약 후 필요한만큼만 점진적으로 확정해 가며 쓸 수 있으므로 물리적인 메모리를

낭비하지 않으면서도 한번 예약한 번지가 바뀌지도 않아 쓰기 편리하고 속도도 빠르다.

단. 가상 메모리는 할당단위가 크므로 필요 메모리량이 클 때만 사용하는 것이 좋다. 연결 리스트의 노드처럼

작은메모리를 할당할 때는 realloc을 쓰는 방법이 더 바람직하다.

 

475366

반응형
반응형

http://blog.naver.com/mcblagg?Redirect=Log&logNo=10027082656


operator new란 무엇인가? 쉽게 말해서 객체를 생성하기는 하는데, 다른 요소는 몽땅 무시하고 메모리 공간만 그냥 잡아준다는 말이다. 물론 생성자도 호출되지 않는다.

 

선언방식은 매우 간단한다. 연산자 정의만 새로 해주면 끝.

void* operator new(size_t size); // size크기만큼 메모리를 잡아주고 메모리 포인터를 넘겨준다.

 

사용법은 더 간단하다. 스트링 객체를 생성해보자.

 

void* stringClass = operator new(sizeof(String));  // operator new를 이용한 메모리 공간 할당

void* stringClass = malloc(sizeof(String)); // malloc을 이용한 메모리 공간 할당

 

결국 두가지 방법 모두 똑같은 형태란 얘기다. 클래스 크기만큼의 공간만 할당해줬을뿐. 엄청 쉽다.

 

 

 

그렇다면 이젠 메모리지정 new, placement new 에 대해서 알아보면 되겠다.

 

우리가 위에서와 같이 그냥 메모리 공간만 할당해 놓았다면,

 다시 말해서 초기화가 되지 못한 메모리 상태라면,

 생성자를 호출해 버리고 싶은 욕구를 떨쳐내기 힘들 것이다. 크.. 당연한 얘기다.

 

그래서 필요한 게 메모리지정 new다 쉽게 말하자면 이미 잡혀있는 공간에 생성자를 불러준다는

뜻으로 생각하면 쉽겠다.  준비과정으로 무엇이 필요할까?

 

1. 잡아놓은 메모리공간

2. 생성자에서 필요로하는 매개변수들

 

이 두놈들만 있으면 준비는 끝이다. 이제 사용방법에 대해서 알아보자.

 

우선 operator new를 새로 정의해주자.

void* operator new(size_t, void* location) {

   return location;

}

위 정의에서 보면 알수 있듯이 인자로 받은 포인터를 고스란히 넘겨주도록만 정의해주면 끝이다.

 

그럼 실제로 적용해보자.

 

Book이라는 클래스가 있다고 가정하고, Book(string title)이 매개변수 생성자라고 하자.

 

ConstructBook(void* book, string title) {

   return new (book) Book(title);

}

 

이렇게 정의한 ConstructBook 함수가 바로 메모리 지정 함수다.

지정된 메모리(void* book)가 넘어오고 이 메모리에 생성자가 다시 호출되고 있다.

 

결과를 확인해보면 이 함수를 통해 공간만 할당되었던 book의 메모리에 인수로 받은 title이

새롭게 지정된 것을 확인할 수 있을 것이다.

 

정리를 해보자면.

 

1. new는 힙에 메모리를 할당함과 동시에 초기화를 해준다.

2. operator new는 힙에 메모리를 할당은 하지만 초기화는 해주지 않는다.

3. 메모리지정 new는 초기화 되지 않은 힙에 있는 메모리를 초기화 할 수 있게 해준다.

반응형
반응형




http://blog.naver.com/kkss2013/60108816896




gpg study를 돌아다니다가 우연히 보게 된 기법..

클래스의 헤더에 변수는 내부에서만 사용하는 변수가 많은데

그것들을 외부로 보여지지 않게 하는 기법이 있어서 잠깐 구현방법을 확인해봤습니다.

 

우선 장점을 보면

: 클래스의 내부 변수를 외부에서 접근 및 확인 하기 어렵다.

: 내부 변수를 추가할때 헤더 파일에 추가하지 않아도 된다.(다른 헤더를 빌드할 필요가 없다!!)

 

단점은

 : 내부변수를 new로 생성해야 하기 때문에 속도상의 문제가 조금있지 않을까 싶기도 하네요 ..

 : 인라인 함수를 만들 수 없다는점도 .....

 

구현방법을 간단히 보면

 

 // 헤더 파일

 

#pragma once


class CPimpl

{

public:

 CPimpl(void);

 ~CPimpl(void);

protected:

 struct XImpl;  // 맴버변수의 구조체 전방선언

 XImpl* pimpl_;  // 맴버변수의 포인터

public:

 int GetType();

};

 

 

 


 

// cpp 파일

#include "Pimpl.h"

// 맴버 변수는 여기에 추가 합니다.

struct CPimpl::XImpl 

{

 int m_nType;

 // 초기화 코드 추가

 XImpl()

 {

  m_nType = 0;

 }

};


CPimpl::CPimpl(void)

{

 pimpl_ = new XImpl;

}

CPimpl::~CPimpl(void)

{

 delete pimpl_;

}

int CPimpl::GetType()

{

 pimpl_->m_nType = 10;

 return pimpl_->m_nType;

}


 

반응형
반응형
C++에서 동적 객체를 생성하기 위해 일반적으로 new 연산자를 사용하는데, new 연산자는 2가지의 동작단계를 지니고 있습니다.

  • 요청한 타입의 객체를 담을수 있는 크기의 메모리를 할당한다.
  • 객체의 생성자를 호출하여 할당된 메모리에 객체의 초기화를 수행한다.

new 연산자의 첫번째 동작단계, 즉 요청한 타입의 객체를 담을수 있는 크기의 메모리 할당은 내부적으로 operator new 라는 함수를 호출하여 이루어집니다.
 
 void * operator new(size_t size)

operator new함수의 반환타입은 void * 입니다. 즉, operator new는 malloc함수와 마찬가지로 메모리만 할당하는 것뿐입니다. 객체니 생성자니 하는것은 operator new와는 전혀 관계없는 것들일뿐이며, 할당된 메모리를 받아 온전한 객체를 생성하는 것은 new 연산자의 몫입니다.
 
우리는 new 연산자에 대해 재작성이나 오버로딩을 할 수 없지만, operator new 연산자는 재작성, 오버로딩을 할 수 있는데 이를 통하여 자신만의 커스터마이징된 메모리 관리 시스템을 개발할 수 있습니다.(operator new를 오버로딩 할 때에는 size_t이외의 매개변수를 추가할 수 있는데, 이럴때도 첫번째 매개변수는 언제나 size_t 타입이여야 합니다.)
 
 
1. placement new
operator new(또는 malloc)을 통해 할당은 받앗지만 초기화되지않은 메모리가 있을때, 우리는 바로 이 메모리 위해 객체를 초기화시킬 수 있습니다. 이를 placement new라 합니다. placement new를 실행하기 위해 준비해야할것은 #include<new>를 추가하는 것이 전부입니다.
  1. MyClass *mem = static_cast<MyClass*>(operator new(sizeof MyClass));  
  2. new (mem) MyClass();  
위 코드에서 new (mem) MyClass() 부분이 placement new 연산자 입니다. 문법이 조금 이상하지만 operator new 연산자에 매개변수가 1개 더 추가 되었다고 보면 됩니다.

 void * operator new(size_t, void *location)
new, operator new, placment new에 대해 정리하면 다음과 같습니다.
 
  • 어떤 객체를 heap에 생성하기 위해서는 new 연산자를 사용합니다.
  • 메모리만을 할당하고 싶을 경우에는 operator new 함수를 사용합니다. 즉, 만약에 heap 객체의 생성을 도맡는 메모리 할당 매커니즘을 손보고 싶다면, operator new를 직접 작성하면 됩니다. new 연산자는 operator new 함수를 호출하게될 것이며 호출된 operator new함수는 개발자가 새롭게 작성한 함수입니다.
  • heap에서 직접 메모리를 가져오지 않고, 개발자가 지정한(이미 가지고 있는) 메모리를 사용하게 하려면 placement new를 사용합니다.
 
 
2. delete, operator delete
메모리 누수를 막기 위해서는, 할당한 메모리는 반드시 그에 대응되는 메모리 해제를 통해 운영체제에 돌려줘야 합니다. C++에서는 기본적으로 제공되는 delete 연산자를 사용하게 됩니다. 그런데 delete 연산자도 new 연산자와 마찬가지로 내부적으로 어떤 함수를 호출하게 되있습니다.
 
 void operator delete(void *dealloc)
 
new 연산자를 통해 할당된 메모리는 delete 연산자를 통해 해제해야합니다. delete 연산자는 객체의 소멸자를 호출하고 객체가 차지하고 있던 메모리를 운영체제에 반환하게 됩니다.
 
여기서 주의해야할점은, 미초기화된(객체가 생성되지 않은) 메모리를 가지고 어떤 일을 할때에는 new와 delete 연산자를 사용하지 않아야 합니다. 그 대신 메모리를 얻을때에는 operator new를 메모리를 해제할때는 operator delete를 사용합니다.(마치 malloc함수를 통해 할당된 메모리를 free함수를 통해 해제하는것처럼)
 
placement new를 사용한 경우에는 절대로 delete 연산자를 호출하면 안됩니다. 이유는 delete 연산자는 operator delete를 호출하여 메모리를 해제하지만, placement new에서 사용된 메모리는 원천적으로 operator new를 통해 할당된 것이 아니기 때문입니다. 대신 객체의 소멸자를 직접 호출해주고 메모리도 역시 직접 할당한 방법에 따라 해제해줘야 합니다.
  1. MyClass->~MyClass();  
  2. operator delete MyClass;  
이와 같이 C++에서 기본 제공하는 new, delete 연산자의 동작방식을 직접 손댈수는 없지만 이들 연산자가 내부적으로 호출하는 operator new, operator delete 연산자는 개발자가 마음먹은대로 고칠 수 있으며, 이는 프로그램의 전첵 메모리 할당 매커니즘을 총괄하는 강력한 메모리 매니지먼트 시스템을 개발할 수 잇음을 의미합니다.

 

예시

 

 

 

http://lyb1495.tistory.com/48

 

반응형
반응형

http://www.gpgstudy.com/forum/viewtopic.php?p=49015






C++에서는 메모리 구조 측면에서 두 종류의 object를 갖습니다. 

하나는 C의 structure나 built-in type과 동일한 메모리 구조를 갖는 object이고 다른 하나는 다형성이나 생성/소멸자 등등의 특성을 사용할 수 있는 object입니다. 전자를 Plain Old Data(POD)라고 하고 후자를 non-POD라고 합니다. 


POD object는 C의 구조체처럼 똑같이 사용할 수 있습니다. memcpy나 memset 등을 사용할 수 있습니다. POD 의 조건은 아래와 같습니다. 

1. built-in type 

2. 가상 함수를 가지지 않으며 사용자 정의 할당자와 소멸자를 가지지 않은 class의 object 

3. non POD를 non-static 멤버로 가지지 않은 class의 object 


위 조건만 만족한다면 C의 struct처럼 object를 사용해도 괜찮습니다.






전송 구조에선 아무래도 메모리로 관리해 주는 게 편할 것 같아서 좀 고민했거든요... 
근데 POD의 조건으로 나와 있는 3가지 항목을 보니, 

1번은 기본 멤버 변수 타입을 요구하는 것 같고 
2번은 생성, 소멸자 및 가상함수 같이 유저 외에 컴파일러가 따로 처리해야 하는 부분이 없어야 하며 
3번은 사용자 정의 타입이나 함수의 사용에 있어서는 static으로 사용될 용량을 도출해 낼 수 있어야 한다... 

란 의미인 걸로 우선 해석되는데, 방향을 제대로 잡았나요? 
어차피 int하고 float, char의 멤버 변수로만 구성된 struct를 쓸 예정이긴 합니다만...;;







C의 구조체와 같은 배치와 사용법을 가지는 C++의 구조체, 클래스는 다음과 같은 조건을 가져야 합니다. 


A. built-in 타입, 또는 다른 POD타입만을 멤버로 가질 것. 

B. 가상함수가 없을 것. 


부연하자면, 

A. 말씀하신 조건중 1번과 3번은 사실상 같은 내용입니다. POD타입은 built-in 타입과 같이 쓸 수 있는 타입을 말하므로 어떠한 것이 오더라도 상관이 없습니다. 또한, 스태틱 멤버는, 당연히 아시겠지만, 객체에 포함되는 멤버가 아니므로 POD이건 non-POD이건 무엇이 오건 영향을 미치지 않습니다. 

B. 가상함수만 없다면, 생성자(constructor)와 소멸자(destructor)는 있건 없건 메모리 배치에 영향을 주지 않습니다. (물론, 가상소멸자는 가상함수에 포함됩니다) 사용자 정의 할당자(allocator) 역시 정의되어 있건 아니건, 할당 위치에 영향을 미칠 뿐, 객체 내부의 메모리 배치에는 영향을 주지 않습니다. RTTI역시 메모리 배치에 영향을 주긴 하지만, 가상함수가 없는 타입의 객체에는 RTTI가 포함되지 않으므로 가상함수를 안쓴다면 신경 안써도 됩니다.

반응형
반응형

아래 주소의 목록에서 더 많은 부분을 찾을 수 있다


http://msdn.microsoft.com/en-us/library/kk6xf663(v=vs.100).aspx


strcpy, wcscpy, _mbscpy

Visual Studio 2010
4 out of 4 rated this helpful Rate this topic

Copy a string. More secure versions of these functions are available; see strcpy_s, wcscpy_s, _mbscpy_s.

char *strcpy(
   char *strDestination,
   const char *strSource 
);
wchar_t *wcscpy(
   wchar_t *strDestination,
   const wchar_t *strSource 
);
unsigned char *_mbscpy(
   unsigned char *strDestination,
   const unsigned char *strSource 
);
template <size_t size>
char *strcpy(
   char (&strDestination)[size],
   const char *strSource 
); // C++ only
template <size_t size>
wchar_t *wcscpy(
   wchar_t (&strDestination)[size],
   const wchar_t *strSource 
); // C++ only
template <size_t size>
unsigned char *_mbscpy(
   unsigned char (&strDestination)[size],
   const unsigned char *strSource 
); // C++ only
strDestination

Destination string.

strSource

Null-terminated source string.

Each of these functions returns the destination string. No return value is reserved to indicate an error.

The strcpy function copies strSource, including the terminating null character, to the location specified by strDestination. The behavior of strcpy is undefined if the source and destination strings overlap.

Security noteSecurity Note

Because strcpy does not check for sufficient space in strDestination before copying strSource, it is a potential cause of buffer overruns. Consider using strcpy_s instead.

wcscpy and _mbscpy are wide-character and multibyte-character versions of strcpy. The arguments and return value ofwcscpy are wide-character strings; those of _mbscpy are multibyte-character strings. These three functions behave identically otherwise.

In C++, these functions have template overloads that invoke the newer, secure counterparts of these functions. For more information, see Secure Template Overloads.

Generic-Text Routine Mappings

TCHAR.H routine

_UNICODE & _MBCS not defined

_MBCS defined

_UNICODE defined

_tcscpy

strcpy

_mbscpy

wcscpy

Routine

Required header

strcpy

<string.h>

wcscpy

<string.h> or <wchar.h>

_mbscpy

<mbstring.h>

For additional compatibility information, see Compatibility in the Introduction.

// crt_strcpy.c
// compile with: /W3
// This program uses strcpy
// and strcat to build a phrase.

#include <string.h>
#include <stdio.h>

int main( void )
{
   char string[80];

   // Note that if you change the previous line to
   //   char string[20];
   // strcpy and strcat will happily overrun the string
   // buffer.  See the examples for strncpy and strncat
   // for safer string handling.

   strcpy( string, "Hello world from " ); // C4996
   // Note: strcpy is deprecated; consider using strcpy_s instead
   strcat( string, "strcpy " );           // C4996
   // Note: strcat is deprecated; consider using strcat_s instead
   strcat( string, "and " );              // C4996
   strcat( string, "strcat!" );           // C4996
   printf( "String = %s\n", string );
}
String = Hello world from strcpy and strcat!

반응형
반응형

http://www.cplusplus.com/reference/stl/






Not logged in
library

Containers

Standard Containers
A container is a holder object that stores a collection of other objects (its elements). They are implemented as class templates, which allows a great flexibility in the types supported as elements.

The container manages the storage space for its elements and provides member functions to access them, either directly or through iterators (reference objects with similar properties to pointers).

Containers replicate structures very commonly used in programming: dynamic arrays (vector), queues (queue), stacks (stack), heaps (priority_queue), linked lists (list), trees (set), associative arrays (map)...

Many containers have several member functions in common, and share functionalities. The decision of which type of container to use for a specific need does not generally depend only on the functionality offered by the container, but also on the efficiency of some of its members (complexity). This is especially true for sequence containers, which offer different trade-offs in complexity between inserting/removing elements and accessing them.

stackqueue and priority_queue are implemented as container adaptors. Container adaptors are not full container classes, but classes that provide a specific interface relying on an object of one of the container classes (such asdeque or list) to handle the elements. The underlying container is encapsulated in such a way that its elements are accessed by the members of the container class independently of the underlying container class used.

Container class templates

Sequence containers:

Container adaptors:

Associative containers:

Member map

This is a comparison chart with the different member functions present on each of the different containers:

Legend:
C++98Available since C++98
C++11New in C++11


Sequence containers

Headers<array><vector><deque><forward_list><list>
Membersarrayvectordequeforward_listlist
constructorimplicitvectordequeforward_listlist
destructorimplicit~vector~deque~forward_list~list
operator=implicitoperator=operator=operator=operator=
iteratorsbeginbeginbeginbeginbegin
before_begin
begin
endendendendendend
rbeginrbeginrbeginrbeginrbegin
rendrendrendrendrend
const iteratorsbegincbegincbegincbegincbegin
cbefore_begin
cbegin
cendcendcendcendcendcend
crbegincrbegincrbegincrbegincrbegin
crendcrendcrendcrendcrend
capacitysizesizesizesizesize
max_sizemax_sizemax_sizemax_sizemax_sizemax_size
emptyemptyemptyemptyemptyempty
resizeresizeresizeresizeresize
shrink_to_fitshrink_to_fitshrink_to_fit
capacitycapacity
reservereserve
element accessfrontfrontfrontfrontfrontfront
backbackbackbackback
operator[]operator[]operator[]operator[]
atatatat
modifiersassignassignassignassignassign
emplaceemplaceemplaceemplace_afteremplace
insertinsertinsertinsert_afterinsert
eraseeraseeraseerase_aftererase
emplace_backemplace_backemplace_backemplace_back
push_backpush_backpush_backpush_back
pop_backpop_backpop_backpop_back
emplace_frontemplace_frontemplace_frontemplace_front
push_frontpush_frontpush_frontpush_front
pop_frontpop_frontpop_frontpop_front
clearclearclearclearclear
swapswapswapswapswapswap
list operationssplicesplice_aftersplice
removeremoveremove
remove_ifremove_ifremove_if
uniqueuniqueunique
mergemergemerge
sortsortsort
reversereversereverse
observersget_allocatorget_allocatorget_allocatorget_allocatorget_allocator
datadatadata

Associative containers

Headers<set><map><unordered_set><unordered_map>
Memberssetmultisetmapmultimapunordered_setunordered_multisetunordered_mapunordered_multimap
constructorsetmultisetmapmultimapunordered_setunordered_multisetunordered_mapunordered_multimap
destructor~set~multiset~map~multimap~unordered_set~unordered_multiset~unordered_map~unordered_multimap
assignmentoperator=operator=operator=operator=operator=operator=operator=operator=
iteratorsbeginbeginbeginbeginbeginbeginbeginbeginbegin
endendendendendendendendend
rbeginrbeginrbeginrbeginrbegin
rendrendrendrendrend
const iteratorscbegincbegincbegincbegincbegincbegincbegincbegincbegin
cendcendcendcendcendcendcendcendcend
crbegincrbegincrbegincrbegincrbegin
crendcrendcrendcrendcrend
capacitysizesizesizesizesizesizesizesizesize
max_sizemax_sizemax_sizemax_sizemax_sizemax_sizemax_sizemax_sizemax_size
emptyemptyemptyemptyemptyemptyemptyemptyempty
reservereservereservereservereserve
element accessatatat
operator[]operator[]operator[]
modifiersemplaceemplaceemplaceemplaceemplaceemplaceemplaceemplaceemplace
emplace_hintemplace_hintemplace_hintemplace_hintemplace_hintemplace_hintemplace_hintemplace_hintemplace_hint
insertinsertinsertinsertinsertinsertinsertinsertinsert
eraseeraseeraseeraseeraseeraseeraseeraseerase
clearclearclearclearclearclearclearclearclear
swapswapswapswapswapswapswapswapswap
operationscountcountcountcountcountcountcountcountcount
findfindfindfindfindfindfindfindfind
equal_rangeequal_rangeequal_rangeequal_rangeequal_rangeequal_rangeequal_rangeequal_rangeequal_range
lower_boundlower_boundlower_boundlower_boundlower_bound
upper_boundupper_boundupper_boundupper_boundupper_bound
observersget_allocatorget_allocatorget_allocatorget_allocatorget_allocatorget_allocatorget_allocatorget_allocatorget_allocator
key_compkey_compkey_compkey_compkey_comp
value_compvalue_compvalue_compvalue_compvalue_comp
key_eqkey_eqkey_eqkey_eqkey_eq
hash_functionhash_functionhash_functionhash_functionhash_function
bucketsbucketbucketbucketbucketbucket
bucket_countbucket_countbucket_countbucket_countbucket_count
bucket_sizebucket_sizebucket_sizebucket_sizebucket_size
max_bucket_countmax_bucket_countmax_bucket_countmax_bucket_countmax_bucket_count
hash policyrehashrehashrehashrehashrehash
load_factorload_factorload_factorload_factorload_factor
max_load_factormax_load_factormax_load_factormax_load_factormax_load_factor

반응형
반응형

http://www.swedishcoding.com/2008/08/31/are-we-out-of-memory/






Are we out of memory?

August 31st, 2008 by Christian Gyrling — Game Coding — 8 Comments

If I had a dollar every time I heard the question “Do we not have any more memory?”
When I ask the question “How much memory are you using for subsystem X?”, very few developers know the answer. It is usually a smaller or bigger ballpark but no definite answer.
Memory for any application is a crucial resource. No matter what type of application you are creating you will benefit from having good memory management. With some basic memory management in place you can know your memory distribution between systems, finding memory leaks much easier, simplify memory alignment requirements… These are just a few of the areas where you will benefit greatly with good memory management.

Getting the chaos under control

The first thing you need to do, unless you have already done it, is to override the global new and delete functions. Doing this gives you a starting point for all your memory management. All calls to new and delete will be handled by your custom functions. The very first step is to just intercept the allocation request and carry it out like it normal.

void* operator new(size_t size)
{
    return malloc(size, sizeof(char));
}

void delete (void* mem)
{
    free(mem);
}

// Don’t forget the array version of new/delete
void* operator new[](size_t size)
{
    return malloc(size, sizeof(char));
}

void delete[](void* mem)
{
    free(mem);
}

I’m not going to go into any detail about intercepting calls to malloc or the like. All I will say is that you will do best in staying away from using alloc, malloc, calloc, realloc and free (and all other permutations that I have forgotten). In most cases it can be tricky to intercept those calls. I would say that you should only use malloc once and that is to allocate all the needed memory for your application… but more about that later.

Custom versions of new and delete

You will quickly find that when you get an allocation request in you ‘new’ handler it would be very handy to know who made that allocation. From the code doing the allocation it would sometimes be very nice to be able to also specify extra information such as the alignment needed for the memory block. Aligned memory in particular is a pain to work with unless it is supported by the memory allocator. If you have a class that has members that need to be 16-byte aligned (SSE for example) it will be messy.

// Allocate memory with the needed padding to ensure that you
// can properly align it to the required boundary. Then you need
// to placement construct the new object in that memory.
char* pInstanceMem = new char[sizeof(MyClass) + 0x0F];
char* pAlignedMem = pInstanceMem & (~0x0F);
MyClass* pMyClassInst = new (pAlignedMem) MyClass();

// The allocation of the object it not terribly ugly but it is
// a pain to work with… but not as much of a pain as it is to
// delete the instance. The code below will crash or just leak.
// The destructor will be properly called but the memory address
// pointed to by pMyClassInst was never returned from a call
// to ‘new’. The memory address actually returned by the call
// to 'new' used for this object is really unknown at this point.
// What to do!?!?!?
delete pMyClassInst

Wouldn’t it be much nicer if you could allocate your MyClass instance like this…

// Just allocate with an extra argument to ‘new’
MyClass* pMyClassInst = new (Align(16)) MyClass();
MyClass* pMyClassInst = new (Align(16)) MyClass();

// and deletion will be straight forward… The pointer passed to
// ‘delete’ is the same pointer returned from the call to ‘new’.
delete pMyClassInst.

This is where overloaded versions of new and delete comes to the rescue.

// We use a class to represent the alignment to avoid any code
// situations where 'new (0x12345678) MyClass()' and
// 'new ((void*)0x12345678) Myclass()' might cause a placement
// construction instead of construction on an aligned memory
// address.
class Align
{
public:
    explicit Align(int value) : m_value(value)
    {
    }
    int GetValue() const
    {
       return m_value;
    }
private:
    int m_value;
};

// Overridden 'normal' new/delete
void* operator new (size_t size);
void* operator new[] (size_t size);
void operator delete( void* mem );
void operator delete[]( void* mem );

// Aligned versions of new/delete
void* operator new[] (size_t size, Align alignment);
void* operator new (size_t size, Align alignment);
void operator delete (void* mem, Align alignment);
void operator delete[] (void* mem, Align alignment);

Allocate all memory up front

Now when you have ways to allocate memory and pass arbitrary parameters to the allocator you can start organizing your memory.
If you need to ship an application that can only use 256 MB of RAM I would suggest that you allocate 256 MB of system memory up front and call that ‘retail memory’. Most of the time you need more memory during development of various systems and for this I would allocate another clump of memory… and call this memory ‘development memory’. You now have two huge buffers to use for your application and you should not allocate any more system memory. When you receive a call to ‘new’ you could check a variable whether to allocate the memory from the retail or the development memory pool.
By clearly separating the two memory pools you make sure that debug-only allocations end up in the debug pool and allocations needed to ship the game are taken from the retail pool. This way it is very clear from which pool you allocate memory and it is easy to keep track of the allocated/available memory. You could even use another custom overloaded new/delete that pass in whether the allocation should be from retail or development memory.

Divide and specialize

Now when you have your chunks of memory it might be a good idea to split it up based on the needs of your application. After all it’s terribly impractical to have only one huge buffer for an application. I am very much against dynamic allocations at run time of an application in general but some allocations have to happen and what is the best way of handling it?

A good way to organize the memory is to split the memory chunks into smaller blocks managed by different allocators using various allocation algorithms. This will not just help to be more efficient about the memory usage but will also serve as a way to clearly assign the memory to the various systems.

Not all allocations are the same. Here are a just few common allocations that came to mind.

PERSISTENT ALLOCATIONS

Allocated once at startup/creation of a system (pools, ring buffers, arrays). It will never be deleted and therefore we don’t need any complicated algorithms to allocate persistent memory. Linear allocations work great it this scenario. All it takes for an allocator like this is a pointer to the buffer, the size of the buffer and the current offset into the buffer (allocated space).

// Simple class to handle linear allocations
class LinearAllocator
{
public:
    LinearAllocator(char* pBuffer, int bufferSize) :
        m_pBuffer(pBuffer),
        m_buffersize(bufferSize),
        m_currentOffset(0)
    {
    }
    void* Alloc(size_t size)
    {
       void* pMemToReturn = m_pBuffer + m_currentOffset;
       pMemToReturn += size;
       return pMemToReturn;
    }
    void Free(void* pMem)
    {
       // We can't easily free memory in this type of allocator.
       // Therefore we just ignore this... or you could assert.
    }
private:
    char* m_pBuffer;
    int m_bufferSize;
    int m_currentOffset;
};

DYNAMIC ALLOCATIONS

This memory is allocated and freed at random points throughout the lifetime of the application. Sometimes you just need memory to create an instance of some object and you can’t predict when that will happen. This is when you can use a dynamic memory allocator. This allocator will have some type of algorithm to remember what memory blocks are free and which ones are allocated. It will handle consolidation of neighboring free blocks. It will however suffer from fragmentation which can greatly reduce the amount of memory available to you. There are tons of dynamic allocation algorithms out there, all with different characteristics; speed, overhead and more. Pick a simple one to start with… you can always change later.

ONE-FRAME ALLOCATIONS

These allocations happen for example when one system creates data and another consumes it later in that same frame. It could be variable sized arrays used for rendering, animation or just debug text created by some system earlier in the frame. This allocator handles this by resetting itself at the beginning (or end) of every frame, hence freeing all memory automatically. By doing this we also avoid fragmentation of the memory. The above LinearAllocator can be used here as well with the addition of a simple ‘Reset()’ function.

Memory Map

Conclusion

Well… I hope this is useful for someone out there. This type of information has helped me when I have worked on my personal or professional projects.

Another thing I did not talk much about is memory tracking. I guess all I will say about that for now is… Do it! Spend the time to implement something quick and easy to help you track down memory leaks and where in your code are you using up all the memory. This is a large topic by itself and therefore I will not write about it in this post. Make use of the overloading of the new/delete operators to allow you to pass __FILE__ and __LINE__ for each allocation. Use macros or other things to make the code prettier.

Until next time…

8 RESPONSES

  • Noel Llopis // Sep 2, 2008 at 5:51 am

    Very nice article, Christian. It’s very much how I like to deal with memory these days as well. I’m getting ready to write about memory allocation patterns for my column and this will definitely come in handy.

  • realtimecollisiondetection.net - the blog » Posts and links you should have read // Sep 2, 2008 at 7:34 am

    [...] a PC game developer). Learn the how’s and why’s by reading Christian Gyrling’s Are we out of memory? and Jesus de Santos Garcia’s post about Practical Efficient Memory Management (though the [...]

  • systematicgaming // Sep 3, 2008 at 10:54 pm

    This is a lot like my recent series on memory management – very similar ideas and implementations. That’s to be expected I suppose for similar applications (games) operating in similar environments.

  • James Bird // Sep 16, 2008 at 12:27 am

    I think there might be a bug in your alignment code.

    Assume that pInstanceMem = 3
    Then this would cause: pAlignedMem = 0

    Which is before the first byte of your allocated chunk of memory.

    I think what you meant to do was round up to the nearest multiple of 16, instead of down:

    pAlignedMem = (pInstanceMem + 0×0F) & ~(0×0F);

  • Christian Gyrling // Sep 16, 2008 at 9:46 am

    James… you’re right. Good catch. I did miss out on that when I wrote that example.
    I guess that will teach me to publish a post after midnight. :-)

  • Arseny Kapoulkine // Jan 3, 2009 at 2:23 am

    There is one minor problem with the code above – unless the code for delete(void*) and delete(void*, Align) is the same, it won’t work. delete ptr; always calls delete(void*), in fact the only case when delete(void*, Align) will be called is if ctor of the constructed class throws an exception.

  • Charles Nicholson // Jan 25, 2009 at 7:35 pm

    Hey Christian, Your “aligned delete” operator will only ever get called if the object being constructed throws when allocated with your “aligned new” operator.

    When you simply invoke ‘delete’ on a pointer that was allocated with your overloaded “aligned new”, your overloaded unaligned global operator delete will be called, not your “aligned delete” operator. See 5.3.4/17 and 5.3.4/19 for details.

    Or, run this code and see the compiler in action:

    #include
    #include

    enum MyToken
    {
    Charles
    };

    void* operator new(size_t size, MyToken token)
    {
    std::printf(“MyToken new\n”);
    return ::operator new(size);
    }

    void operator delete(void* p, MyToken token)
    {
    std::printf(“MyToken delete\n”);
    return ::operator delete(p);
    }

    struct Type
    {
    explicit Type(bool shouldThrow)
    {
    std::printf(“Type ctor\n”);
    if (shouldThrow)
    throw std::exception();
    }

    ~Type()
    {
    std::printf(“Type dtor\n”);
    }

    char c[256];
    };

    int main()
    {
    std::printf(“—New/delete without ctor-throw:\n”);
    delete new (Charles) Type(false);

    std::printf(“—New with ctor-throw:\n”);
    try
    {
    new (Charles) Type(true);
    }
    catch(const std::exception&)
    {
    }

    return 0;
    }

  • Jon B // Apr 14, 2010 at 7:18 am

    I think there may be a bug with your linear allocator…

    void* Alloc(size_t size)
    {
    void* pMemToReturn = m_pBuffer + m_currentOffset;
    pMemToReturn += size; // Oops..
    return pMemToReturn;
    }

    Should perhaps be…

    void* Alloc(size_t size)
    {
    void* pMemToReturn = m_pBuffer + m_currentOffset;
    m_currentOffset += size; // Thats better..
    return pMemToReturn;
    }

반응형

+ Recent posts