728x90

이 페이지는 함수자를 공부해 보도록 하겠습니다.

이 페이지를 공부하기 위해서는 연산자 중복, 템플릿을 알고 있어야 합니다.

 

함수자(functor)는 함수처럼 동작하는 객체입니다.

함수자는 STL의 어뎁터나 술어(predicate), 바인더, 콜백 등에 사용됩니다.

 

1, 함수자 만들기

함수자를 사용하면 함수를 호출하는 것인지? 객체로 함수를 호출하는 것인지를 추상화할 수 있습니다.

함수자는 operator() 연산자 중복된 클래스의 객체입니다.

그럼 간단한 예제코드를 만들어 보도록 하겠습니다.

 

전역 함수 호출 예

#include <iostream>
using namespace std;
void Print( )
{
    cout << "전역 함수!" << endl;
}
void main( )
{
    Print( );
}
  1. 전역 함수!

 간단하죠? ㅡㅡ;;

 

함수자를 이용한 호출 예

#include <iostream>
using namespace std;
class Functor
{
public :
    void operator( ) ( )
    {
        cout << "함수자!" << endl;
    }
};
void main( )
{
    Functor functor;

    functor( ); //함수를 호출한 것인지, 객체의 멤버 함수 operator()를 호출한 것인지 알 수 없습니다.
    functor.operator( ) ( ); // 실제 이렇게 멤버 함수를 호출합니다..
}

  1. 함수자!
  2. 함수자!

 functor객체를 만들어 함수를 호출합니다.

 

인자를 갖는 함수를 보도록 하겠습니다.

전역 함수 호출 예

#include <iostream>
using namespace std;
void Print(int n)
{
    cout << "정수 : " << n << endl;
}
void main( )
{
    Print( 10 );
}
  1. 정수 : 10

간단!

 

함수자를 이용한 호출 예

#include <iostream>
using namespace std;
class Functor
{
public :
    void operator( ) (int n)
    {
        cout << "정수 : " << n << endl;
    }
};
void main( )
{
    Functor functor;

    functor( 10 ); // 암시적 호출
    functor.operator( ) ( 10 ); //명시적 호출
}
  1. 정수 : 10
    정수 : 10

간단하죠?!

 

2, 함수, 함수자, 함수 포인터

이 세 가지는 모두 동일한 인터페이스를 갖습니다.

 

함수와 함수자, 함수 포인터를 이용한 함수 호출 예제입니다.

#include <iostream>
using namespace std;
class Functor
{
public :
    void operator( ) (int n)
    {
        cout << "정수 : " << n << endl;
    }
};

void Print(int n)
{
    cout << "정수 : " << n << endl;
}

void main( )
{
    Functor functor;
    void (*pfunc)(int ) = &Print;

    Print( 10 );  //함수 호출 인터페이스가 모두 동일합니다.
    functor( 10 );//함수 호출 인터페이스가 모두 동일합니다.
    pfunc( 10 );  //함수 호출 인터페이스가 모두 동일합니다.
}
  1. 정수 : 10
    정수 : 10
    정수 : 10

이 예제처럼 호출 인터페이스만 보면 일반 함수, 함수자, 함수 포인터가 모두 동일한 방식으로 사용된다는 것을 알 수 있습니다.

 

이제 템플릿을 이용하면 함수의 리턴 자료형과 매개 변수 자료형에 상관없이 함수자를 이용할 수 있습니다.

#include <iostream>
using namespace std;
template <typename RType, typename AType>
class Functor
{
public :
    RType operator( ) (AType n)
    {
        cout << "인자 : " << n << endl;
    }
};

void main( )
{
    Functor< voidint > functor;

    functor( 10 );
}
  1. 인자 : 10

이처럼 좀 더 일반화할 수 있습니다.


출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

C++에서 함수 포인터는 함수자(functor)를 구현할 때 유용하게 사용되며 STL이나 콜백 메커니즘을 구현할 때도 유용하게 사용될 수 있습니다.

 

1, 함수 포인터의 종류

C++언어의 함수는 세 가지 방식으로 호출할 수 있습니다.

  • 첫째, 정적 함수 호출(전역함수, 클래스 내의 정적함수, 네임 스페이스 내의 전역함수등)
  • 둘째, 객체로 멤버 함수 호출(객체 자체로 호출)
  • 셋째, 객체의 주소로 멤버 함수 호출(객체의 주소로 호출)

위 세 가지에 대한 함수 포인터 선언및 호출 방식이 다릅니다.

 

우리는 이미

첫째, 정적 함수의 주소를 저장하는 함수 포인터는 알고 있습니다.

 

여기서 다시 복습해 볼까요?

#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    // ... 여러 멤버들 ..
    static void Print(int n) // 2.Point 클래스 static 함수
    {
        cout << "n = " << n << endl;
    }
};

void Print(int n) // 1.전역 함수
{
    cout << "n = " << n << endl;
}

namespace NetGong
{
    void Print(int n) // 3.NetGong 네임스페이스  내의 전역함수
    {
        cout << "n = " << n << endl;
    }
}

void main( )
{
    void (*pfunc)(int ); // 함수 포인터 선언 - C언어 QuickStart1 참고
   
    pfunc = &Print; // 1.전역함수 주소 저장
    pfunc( 10 ); // 1.전역함수 호출

    pfunc = &Point::Print; // 2.Point 클래스 static 함수 저장
    pfunc( 10 ); // 2.Point 클래스 static 함수 호출

    pfunc = &NetGong::Print; // 3.NetGong 네임스페이스  내의 전역함수 저장
    pfunc( 10 ); // 3.NetGong 네임스페이스  내의 전역함수 호출
}
  1. n = 10
    n = 10
    n = 10

뭐.. 별로 설명할 것이 없습니다. 

알아 둘 것은 위 세 가지 경우 모두 같은 포인터 변수를 사용한다는 것입니다. 호출 방식이 같거든요.

함수의 주소를 저장할 때 Print가 함수 주소이므로 &Print 와 같이 &를 붙여도 그만 붙이지 않아도 그만이지만 명시적으로 &붙여 사용합니다. 연산자의 위치와 용도에 따라 의미가 달라지는 걸 아시죠? 이곳에서 &가 있든 없든 같은 코드입니다. 또, 아래쪽에서 배울 맴버함수는 꼭 & 연산자가 있어야 합니다. 하여튼 &를 붙이는 방식을 선호하므로 꼭 붙여서 사용하는 습관을 들이세요.

 

이제 둘째, 셋째 멤버 함수호출인 멤버 함수 포인터를 사용하는 예제를 공부해 보도록 하겠습니다.

  • 멤버 함수 포인터 선언

 class Point
{

//멤버 ...
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

 void (Point::*pfunc)( ); //멤버 함수 주소를 저장하기 위한 함수 포인터 선언

 일반 함수 포인터라면 void (*pfunc)( ); 라고 선언했겠지만 Point 클래스의 멤버 함수 포인터이므로 Point::* 와 같은 연산자를 사용합니다.

  • 멤버 함수 포인터를 이용한 호출

    • 객체로 호출 : (객체.*pfunc)( );
    • 객체의 주소로 호출 : (객체주소->*pfunc)( );

.* 연산자와 ->*연산자를 이용하여 각각 객체와 객체의 주소로 함수 포인터가 가리키는 멤버함수를 호출할 수 있습니다. 또 연산자 우선순위 때문에 ( )로 묶어 주어야합니다. 주의하세요.

 

2, 멤버 함수 포인터 실습

그럼 실제 함수 포인터 예제를 보도록 하겠습니다.

#include <iostream>
using namespace std;

class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

void main( )
{
    Point d1(2,3);
    Point *p = &d1;

    void (Point::*pfunc)( ); //멤버 함수 주소를 저장하기 위한 함수 포인터 선언
    pfunc = &Point::Print; 

    d1.Print(); //둘째, 객체로 멤버 함수 호출
    p->Print();  //셋째, 객체의 주소로 멤버 함수 호출

    (d1.*pfunc)( ); //둘째, 객체로 함수 포인터 이용한 멤버 함수 호출
    (p->*pfunc)( ); //셋째, 객체의 주소로 함수 포인터 이용한 멤버 함수 호출

    Point d2, d3(5,5);
    (d2.*pfunc)();
    (d3.*pfunc)();
}
  1. (2,3)
    (2,3)
    (2,3)
    (2,3)
    (0,0)
    (5,5)

객체는 연산자 .*를 사용하고 객체의 주소는 ->* 연산자를 사용한다는 것만 주의하세요.

 

또 다른 예제를 보겠습니다.

#include <iostream>
using namespace std;
class Point
{
    int x, y;
public:
    Point(int _x = 0, int _y = 0) : x(_x), y(_y) { }
    bool Compare(const Point& arg)
    {
        return x==arg.x && y == arg.y ? true : false ;
    }
    void Print( )
    {
        cout << "(" << x << "," << y << ")" << endl;
    }
};

void main( )
{
    Point d1(2,3);
    Point d2(5,5);
    Point *p = &d1;

    bool (Point::*pfunc)(const Point& ); //멤버 함수 주소를 저장하기 위한 함수 포인터 선언
    pfunc = &Point::Compare;

    cout << d1.Compare(d2) << endl; //둘째, 객체로 멤버 함수 호출
    cout << p->Compare(d1) << endl; //셋째, 객체의 주소로 멤버 함수 호출

    //둘째, 객체로 함수 포인터 이용한 멤버 함수 호출
    cout << (d1.*pfunc)(d2) << endl;
    //셋째, 객체의 주소로 함수 포인터 이용한 멤버 함수 호출
    cout << (p->*pfunc)(d1) << endl;
}
  1. 0
    1
    0
    1

출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

이번 장은 앞쪽 포인터부분 공부가 필수적입니다. 앞쪽 포인터부분을 이해했다면 이 장은 그냥 덤입니다. ㅡㅡ;;

1, void 포인터

모든 변수는 변수의 크기와 메모리  저장형식인 자료형이라는 것이 존재합니다. C언어에서 void는 '자료형이 없다', '자료형이 정해지지 않았다'라는 의미로 사용됩니다. 그렇다면 int n; 처럼 void v; 이 가능할까요? 당근 불가합니다. 다시말하지만(변수장에서 했습니다.) 자료형이란 변수의 연산, 크기, 저장형태을 나타낸다고 했습니다. void는 자료형이 없다는 의미이므로 변수를 만들 수 없습니다.

 

여러분 포인터의 크기는 얼마일까요? 모든 분들이 아실거라 믿습니다. 4byte입니다. 포인터란 무엇일까요? 주소를 저장하는 변수입니다. 

 

void 포이터란? 주소를 저장하는 포인터입니다. 음? 이게 다야? 네~ void 포인터는 자료형이 결정되지 않은(어떤 자료형의 주소든) 주소를 저장하는 포인터입니다. (malloc() 함수의 리턴 자료형이 void* 입니다. 함 생각해 보세요.)

 

void 포인터는 어떤 자료형의 주소든 모두모두 담을 수 있습니다.

void main( )
{
    void *pv;
    char c = 'A';
    int n = 10;
    double d = 3.14;

    pv = &c;
    pv = &n;
    pv = &d;
}

 이처럼 어떤 자료형의 주소든 저장할 수 있습니다. 어떤 자료형의 주소든 4byte를 넘지 않을 것이고 void* 형 pv는 단지 주소를 저장하는 포인터입니다. 그렇다면, 주소 pv로 할 수 있는 일은? 자료형이 없어서 아직 할 수 있는 일은 없습니다. 주소는 단지 주소일 뿐 어떤 데이터가 메모리공간에 저장돼 있는지 알 수 없으므로 의미가 없습니다.

 

왜? void 포인터가 의미 없는 주소인지 포인터를 복습해 보도록 할까요?

그림을 보며 설명드리도록 하겠습니다. (이해가 쉽지 않다면 포인터 장을 참고하세요)

void main( )
{
    int n = 10;
    int * pn = &n;

}

 

 

pn이 int* 이므로 *pn은 int형이 됩니다.

 

또, .....

void main( )
{
    int n = 10;
    int * pn = &n;

    int ** ppn = &pn;

}

 

 

 ppn이 int ** 형이므로 *pn은 int* 형이며 **ppn은 int형 정수가 됩니다.

 

이제 다시 void*를 볼까요?

void main( )
{
    void *pv;
    int n = 10;
    pv = &n;

*pv // 에러~~

}

 

 

 

*pv는 연산 에러(컴파일 에러)입니다. void*형에 *연산자를 사용할 수 없습니다.

 

가지고 있는 주소 메모리에 접근하려면 아래와 같이 주소 형변환 후 접근할 수 있습니다.

void main( )
{
    void *vp;
    char c = 'A';
    int n = 10;
    double d = 3.14;

    vp = &c;
    printf("%c\n", *(char*)vp );
    vp = &n;
    printf("%d\n", *(int*)vp );
    vp = &d;
    printf("%lf\n", *(double*)vp );
}
  1. A
    10
    3.140000

 vp를 각 자료형으로 변환한 후 *연산자로 메모리 접근합니다.

아래 그림을  참고하세요

 

 

보통 아래 예제처럼 사용합니다.

void main( )
{
    void *vp;
    int *pn;
    int n = 10;
   
    vp = &n;
    pn = (int*)vp;
    printf("%d\n", *pn );
}
  1. 10

pn변수에 대입 후 *pn으로 메모리 접근합니다. 위 예제는 바보 같습니다. 바로 pn을 사용하면 되는 데 복잡하게 사용하고 있으니까요. 하지만, 이것은 예제일 뿐이고 실전에서는 정당한 이 예제와 같이 사용해야 하는 정당한 이유가 있습니다. 지금은 패~스!

 

2, 함수 포인터

포인터는 주소를 저장하는 변수이므로 함수 포인터는 함수의 주소를 저장하는 포인터입니다.

어?? 어어?? 함수의 주소를 저장해? 함수도 주소가 있다는 말인가?

그렇습니다. 실제 함수를 호출하면 함수의 주소를 이용하여 호출합니다.

 

함수의 주소를 출력해 보도록 하겠습니다.

void func( )
{
}
void main ( ) {
    printf("main : %x\n", main);
    printf("func : %x\n", func);
    printf("printf : %x\n", printf);
}
  1. main : 411140
    func : 4111d1
    printf : 1025abb0

함수의 이름은 그 함수의 시작주소입니다. 배열의 이름도 그 배열이 시작하는 시작주소죠? 같습니다.

함수 포인터의 크기는 4byte입니다. 모든 포인터는 4byte입니다. 함수 포인터도 단지 주소를 저장하는 4byte 메모리 공간일 뿐입니다.

아래 그림을 참고하세요.

 

 

 

어떤 함수의 주소가 K라면 우리는 함수의 주소를 이용하여

  •  K( );
  •  함수주소( );

이렇게 함수를 호출하는 것입니다.

 

함수 포인터란 위와 같은 함수의 주소를 저장하는 포인터입니다.

int형 주소는 int형 포인터에

double형 주소는 double형 포인터에

함수형 주소는 함수형 포인터에 저장합니다.

 

함수 포인터 선언

함수의 원형만 알면 쉽게 포인터를 선언할 수 있습니다.

예로

void func( )
{
}

원형은 ?

void func( ); 입니다.

위 함수의 주소를 저장하는 포인터 변수 선언은 void (*pf)( );

 

void  func(int n )
{
}

원형은 ?

void func(int  ); 입니다. 

위 함수의 주소를 저장하는 포인터 변수 선언은 void (*pf)(int );

원형과 같고 변수에 (* ) 연산자를 붙이면 됩니다. 연산자가 없으면 함수 원형 선어이 되겠죠?

 

간단한  함수의 주소를 저장하는 예제입니다.

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    func( );
    pf( );
}
  1. 함수 포인터 예제!
    함수 포인터 예제!

 함수포인터 pf를 선언하고 함수의 주소(func)를 저장한 후 함수를 호출하고(pf() ) 있습니다.

 

아래와 같이 호출할 수도 있습니다.

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    func( );
    (*pf)( );
}
  1. 함수 포인터 예제!
    함수 포인터 예제!

 함수 포인터는 pf()와 (*pf)() 로 포인터가 가리키는 함수를 호출할 수 있습니다. pf는 함수의 주소인데 어떻게 (*pf)와 같나? 이렇게 의문이 생길 수 있지만 함수 포인터는 완벽히 동일합니다. pf는 함수의 주소이며 *pf도 함수의 주소입니다. 연산자 공부할 때 연산자는 위치와 사용 용도에 따라 연산자 의미가 달라진다고 했습니다.

pf와 *pf는 함수의 주소로 같다. 아래 예제를 보시죠!

void func( )
{
    printf("함수 포인터 예제!\n");
}
void main ( )
{
    void (*pf)( );
    pf = func ;

    printf("%x  %x  %x\n", func, pf, *pf);
}
  1. 4111d1   4111d1   4111d1

모두 같은 함수의 주소입니다.

 

하나의 함수 포인터로 서로다른 함수를 호출하는 예제입니다.

void func1(int n1, int n2)
{
    printf("함수 func1(  ) 인자 : %d, %d\n", n1, n2);
}
void func2(int n1, int n2)
{
    printf("함수 func2(  ) 인자 : %d, %d\n", n1, n2);
}
void func3(int n1, int n2)
{
    printf("함수 func3(  ) 인자 : %d, %d\n", n1, n2);
}
void main ( )
{
    void (*pf)(intint );
    pf = func1 ;
    pf( 10, 20);

    pf = func2 ;
    pf( 10, 20);

    pf = func3 ;
    pf( 10, 20);
}
  1. 함수 func1(  ) 인자 : 10, 20
    함수 func2(  ) 인자 : 10, 20
    함수 func3(  ) 인자 : 10, 20

 간단하죠? ㅡㅡ; 패~스!

 

마지막으로 함수 포인터를 사용하는 이유는 무엇일까요?

첫째, 서로 다른 함수에 대해 동일한 인터페이스를 제공한다. 같은 이름으로 서로다른 함수를 호출할 수 있다는 것입니다.

둘째, 실행시간에 알고리즘이나 기능을 변경하는 유연성을 제공한다.

셋째, 콜백(callback) 메커니즘을 제공한다. 등..


출처 : http://blog.daum.net/coolprogramming/76

728x90
728x90

이번에 비트맵을 불러와서 Resizing 하고 다시 저장해서 확인하기 위해 


비트맵 파일을 불러오고 저장하는 예제를 만들었다.


#define LOG_OUT(_x_) OutputDebugString(_x_) 

#define LOG_OUT_W(_x_)  OutputDebugStringW(_x_) 

#define LOG_OUT_A(_x_)  OutputDebugStringA(_x_) 


bool LoadBmp(char* filename, byte** pImage);

bool SaveBmp(char* filename, byte* pImage, int width, int height);


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

{

    byte* pLoadImage = nullptr;


    LoadBmp("../Original_image.bmp", &pLoadImage);


    SaveBmp("../Save_image.bmp", pLoadImage, 1920, 1080);


    free(pLoadImage);


    return 0;

}


bool LoadBmp(char* filename, byte** pImage)

{

    FILE* fp;


    // 비트맵 정보 구조체

    BITMAPFILEHEADER BMFH; ///< BitMap File Header.

    BITMAPINFOHEADER BMIH; ///< BitMap Info Header.


    fopen_s(&fp, filename, "rb");

    if (nullptr == fp)

    {

        LOG_OUT_A("fopen() error");

        return false;

    }


    // 지정한 크기만큼 파일에서 읽어옴, 그리고 비트맵파일헤더에 넣어줌

    fread(&BMFH, sizeof(BITMAPFILEHEADER), 1, fp);

    if (BMFH.bfType != 0x4d42) // 비트맵 파일이 아니면 종료한다.

    {

        fclose(fp);

        LOG_OUT_A("not '.bmp' file !!!");

        return false;

    }


    fread(&BMIH, sizeof(BITMAPINFOHEADER), 1, fp); //인포헤더에 있는 크기의 정보만큼 읽어서

    if (BMIH.biBitCount != 24 || BMIH.biCompression != BI_RGB) //24비트인지 체크하고, 압축이 안되어 있는지 체크를 함

    {

        fclose(fp);

        return false;

    }


    INT Width = BMIH.biWidth;

    INT Height = BMIH.biHeight - 1;

    INT BytePerScanline = (Width * 3 + 3) & ~3;  // 패딩

    INT size = BMFH.bfSize;


    *pImage = (BYTE*)malloc(size);


    fread(*pImage, size, 1, fp);  // 파일의 정보를 전부 읽어온다.

    //*pImage += BytePerScanline * Height;


    // FILE*을 닫음.

    fclose(fp);


    return true;

}


bool SaveBmp(char* filename, byte* pImage, int width, int height)

{

    // DIB의 형식을 정의한다.

    BITMAPINFO dib_define;

    dib_define.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

    dib_define.bmiHeader.biWidth = width;

    dib_define.bmiHeader.biHeight = height;

    dib_define.bmiHeader.biPlanes = 1;

    dib_define.bmiHeader.biBitCount = 24;

    dib_define.bmiHeader.biCompression = BI_RGB;

    dib_define.bmiHeader.biSizeImage = (((width * 24 + 31) & ~31) >> 3) * height;

    dib_define.bmiHeader.biXPelsPerMeter = 0;

    dib_define.bmiHeader.biYPelsPerMeter = 0;

    dib_define.bmiHeader.biClrImportant = 0;

    dib_define.bmiHeader.biClrUsed = 0;


    // DIB 파일의 헤더 내용을 구성한다.

    BITMAPFILEHEADER dib_format_layout;

    ZeroMemory(&dib_format_layout, sizeof(BITMAPFILEHEADER));

    dib_format_layout.bfType = *(WORD*)"BM";

    dib_format_layout.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);// +dib_define.bmiHeader.biSizeImage;

    dib_format_layout.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);


    // 파일 생성.

    FILE* fp = nullptr;

    fopen_s(&fp, filename, "wb");

    if (nullptr == fp)

    {

        LOG_OUT_A("fopen() error");

        return false;

    }


    // 생성 후 헤더 및 데이터 쓰기.

    fwrite(&dib_format_layout, 1, sizeof(BITMAPFILEHEADER), fp);

    fwrite(&dib_define, 1, sizeof(BITMAPINFOHEADER), fp);

    fwrite(pImage, 1, dib_define.bmiHeader.biSizeImage, fp);

    fclose(fp);


    return true;

}


소스 코드 : 

test.cpp


728x90
728x90

윈도우 키 입력을 받아 테스트 할 때 쓰기 위해 Define을 해놓은 키보드 코드 이다.


간단하게 많이 쓰는 키보드는 적어놓고 나머지는 파일을 첨부한다.


#define VK_0 0x30

#define VK_1 0x31

#define VK_2 0x32

#define VK_3 0x33

#define VK_4 0x34

#define VK_5 0x35

#define VK_6 0x36

#define VK_7 0x37

#define VK_8 0x38

#define VK_9 0x39

#define VK_A 0x41

#define VK_B 0x42

#define VK_C 0x43

#define VK_D 0x44

#define VK_E 0x45

#define VK_F 0x46

#define VK_G 0x47

#define VK_H 0x48

#define VK_I 0x49

#define VK_J 0x4A

#define VK_K 0x4B

#define VK_L 0x4C

#define VK_M 0x4D

#define VK_N 0x4E

#define VK_O 0x4F

#define VK_P 0x50

#define VK_Q 0x51

#define VK_R 0x52

#define VK_S 0x53

#define VK_T 0x54

#define VK_U 0x55

#define VK_V 0x56

#define VK_W 0x57

#define VK_X 0x58

#define VK_Y 0x59

#define VK_Z 0x5A


WindowsKeyboardCodes.h


출처 : http://www.opensource.apple.com/source/WebCore/WebCore-855.7/platform/WindowsKeyboardCodes.h

728x90
728x90

#include "stdafx.h"
#include "stdlib.h"
#include "assert.h"
#include <windows.h>
#include <stdio.h>
#include <list>
#include <algorithm>

using namespace std;
void Delete(char* p)
{
     delete p;
}

int _tmain(int argc, _TCHAR* argv[])
{
    //포인터 보관
     list<char*> g_listL; // 16MByte
     list<char*> g_listM; // 1MByte
     list<char*> g_listS; // 1~1024byte
    
    //약 2GB미만의 메모리 할당

     for (int i=0;i<115;i++)
     {
          g_listS.push_back(new char[4096]); // 1~8192Byte
          g_listL.push_back(new char[1024*1024*16]); // 16MByte
          g_listM.push_back(new char[1024*1024]); // 1MByte
     } 
    // 16Mbyte 리스트는 해제
     for_each(g_listL.begin(),g_listL.end(),Delete);
     
     int nSizeL = (int)g_listL.size();
     g_listL.clear();

       
    // 절반 횟수로 좀던 큰 덩어리 32MByte로 할당 
    nSizeL = nSizeL /2; 
     for (int i=0;i<nSizeL;i++)
     {
          g_listL.push_back(new char[1024*1024*32]);
     }

     for ( ; ; )
     {
     }
 
     return 0;
}



1. 하나의 사용자 프로세스가 사용하는 약 2GB 미만의 주소공간을 할당한 후의 모습




2. 할당된것중 16MBye크기들만 해제한후  단편화된 모습 (연속적인 메모리 공간이 없어보인다)



3. 다시 더 큰 32MByte 로  할당시 실패 




*!! 단편화가되면!!
- 할당가능한 위치를 찾느라 시간이 상대적으로 더걸린다. 
- 여유 메모리가 있음에도  연속적인 공간이없어 할당에 실패한다.



출처 : http://egloos.zum.com/ldw9981/v/3504884

728x90
728x90

가상 메모리
는 프로세서가 운영체제와 조합되어 응용 소프트웨어와 물리 메모리 사이에 가상의 계층을 만들고 하드웨어의 일부분을 메모리 처럼 사용하는 것으로 가상 메모리 관리 기법에는 페이징과 세그멘테이션이 있다.



페이징 기법은 가상 메모리를 모두 같은 크기의 블록으로 나누어 프로세스를 메인 메모리에 올려 동작하게 하는 기법으로 이때 일정한 크기로 나누어진 블록을 페이지(프레임의 시작 주소)라고 하며 메인 메모리는 프레임이라는 단위로 나뉘고 페이지와 크기는 같다.

또한 페이징 기법은 일정한 크기로 페이지를 나누기 때문에 내부 단편화가 발생할 수 있다.


페이지 테이블은 프로세스의 페이지 정보(프레임의 시작 주소)를 저장하고 각 프로세스의 메모리가 실제 메모리와 어떻게 맵핑 되어 있는지를 나타내는 테이블로 하나의 프로세스는 하나의 페이지 테이블을 가진다. 페이지 테이블은 페이지 번호를 가지고 있으므로 페이지를 찾아간 후 페이지의 프레임 시작 주소를 통해 메인 메모리의 주소를 알 수 있다.


세그멘테이션은 가상 메모리를 서로 크기가 다른 논리적 단위인 세그먼트(Segment)로 메모리를 할당하는 기법이다.

미리 분할해 둘 수 없고 메모리에 적재 될 때 빈 공간을 찾아 할당하는 사용자 관점의 가상 메모리 관리로써 세그먼트의 가변적 크기로 인해 외부 단편화가 일어난다.


메모리 풀은 동일한 사이즈의 메모리 블록들을 미리 할당해 놓음으로써 다양한 블록 사이즈 때문에 생기는 단편화와 그로 인한 퍼포먼스 저하를 방지하기 위해 사용되는 기법이다.


메모리 압축은 여러 가지 크기의 사용되지 않는 메모리들을 가능하면 연속된 큰 메모리 블록이 되도록 하나로 통합하는 과정이다.



출처 : http://leehayun123.tistory.com/20






728x90
728x90

1. Frame rate란? 

 - 1초에 메인 루프가 몇 번 도는지를 가리켜 프레임 레이트(Frame rate)라고 부른다. 예를 들어 '프레임

    레이트는 60이다'라고 표현하고 더 줄여서 '60프레임이다'라고도 한다. 영어로는 60 Frame per second

    라고 하고 줄여서 fps라고도 한다. 앵간하면 게임에선 60프레임 고정을 목표로 하자.

 

2. 입력 페이스 조절.

 - 여기서 고민해 볼 것은 게임 입력키로 자주 사용하는 GetAsyncKeyState()로 입력을 받아서 물체를 이

   동 시킨다고 치면, 그 게임의 Frame ate에 따라 입력되는 횟수가 틀려질 것이다.

   이것을 해결하기 위한 방법은 기본 적으로 3가지가 있다.

   1) 누른 시간에 비례해서 움직인다.

   2) 이전 프레임에서 눌리지 않았을 때만 반응한다.

   3) 일정 시간 이상 눌려야 반응한다.

  

   1)의 방법은 액션 게임에서는 일반적으로 사용되는 방법으로 한 칸 단위가 아니라 정밀하게 움직이는

   게임에서 사용한다. 예를 들면 1프레임동안 눌렀다면 0.2만큼 이동해라.

 

   2)의 방법은 이전 프레임 상태를 저장해 두고 이번 프레임에서 처음 눌렀을 때만 반응하는 방법이다.

   이 방법 같은 경우에는 이동키에는 사용하기 힘들고, 격투 게임에서 펀치 버튼에서 사용할 수 있겠다.

 

   3)의 방법은 그다지 사용되는 빈도는 높지 않지만 일단은 알아두자. 키를 눌렀을 때 바로 반응하면 곤

   란하거나 잠시 누르고 있을 대 계속 반응한 채로 두고 싶을 때 사용된다.

 

3. Frame rate 계산.

  - 이 계산에도 다양한 방법이 있을 것이다. 여기선 단순하게 하자. 결국 Frame rate는 MainLoop를 1초에

    몇번하는가 이다. 그러면 MainLoop가 1회당 몇초가 걸리는지 생각해서 그 역수를 Frame rate로 하는

    것이다. 하지만 고정된 수로 하는 것은 위험하다. 언제 변수가 생길지 모르니까...

    그래서 MainLoop의 걸리는 시간을 10개 정도 저장을 해서 평균을 내는 것이 좋다.

 

    unsigned PreviousTime[10];

 

    MainLoop()

    {

         unsigned currentTime = time();

         unsigned frameTimeAve = currentTime - PreviousTime[0];

 

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

         {

              PreviousTime[i] = PreviousTime[i + 1];

         }

         PreviousTime[10 - 1] = currentTime;

 

         unsigned frameRate = 1000 * 10 / frameTimeAve;     // 프레임 레이트 계산.

 

         // 여기서 업데이트 및 렌더 처리

    }

 

    10프레임일 때 기록을 저장해 두고 지금 측정한 시간에서 제일 오래된 기록을 빼면 '최근 10 프레임에

    걸린 시간'을 알 수 있다. 다시 10으로 나무면 1프레임당 걸린 시간으로 고칠 수 있고 여기부터는

    Frame rate도 계산 할 수 있다.


4. timeGetTime()을 이용한 fps 구하기.

  - 이 방법은 timeGetTime()을 이용하여 간단하게 fps를 측정하는 코드이다.

    #include <timeapi.h>

    #pragma comment(lib, "Winmm.lib")


    UpdateFPS()

    {

        static DWORD frameCount = 0;                            //프레임 카운트수

        static float timeElapsed = 0.0f;                        //흐른 시간

        static DWORD lastTime = timeGetTime();                  //마지막 시간(temp변수)


        DWORD curTime = timeGetTime();                          //현재 시간

        float timeDelta = (curTime - lastTime)*0.001f;          //timeDelta(1번생성후 흐른 시간) 1초단위로 바꿔준다.


        timeElapsed += timeDelta;


         /////////////////////////////////////////////// 함수 실행!!!!


        frameCount++;

        if (timeElapsed >= 1.0f)                                //흐른시간이 1초이상이면 내가 하고싶은것 처리

        {

            wchar_t pMessage[300] = { 0, };

            float fps = (float)frameCount / timeElapsed;

            swprintf_s(pMessage, sizeof(pMessage), TEXT("[D3D PusthToChannel] FPS: %f \r\n"), fps);     

            OutputDebugStringW(pMessage);

            frameCount = 0;

            timeElapsed = 0.0f;

        }

        lastTime = curTime;

    }

 

5. Frame rate를 고정하자.

  - 위에서 계산한 프레임은 성능이 같은 컴퓨터 끼리는 같은 결과가 나오겠지만 성능이 다른 컴퓨터 끼

    리는 다른 결과가 나오게 된다. 때문에 Frame rate를 고정할 필요가 있다.

    이 고정하는 방법은 대충 2가지의 방법이 있다. 첫 번째는 Frame rate를 일정하게 고정하는 방법이고,

    두 번째는 게임 처리를 Frame rate에 맞춰 변경하는 것이다. 예를 들어 Frame rate가 60일 때에 1 픽셀

    씩 이동하고 Frame rate가 3일 때에 2 픽셀씩 이동하는 방식이다.

 

    1) 프레임 레이트를 고정하는 방법.

      - 프레임 레이트를 고정해서 게임을 제작하는 방법은 고정 프레임 레이트라고 부른다.

        unsigend EndTime = PreviousTime + 16   // 예정 시간. 16은 프레임으로 치면 62.5 정도....

        while( 1)

        {

             unsigned CurrentTime = time() - PreviouseTime;

             if( CurrentTime < 0) CurrentTime += 0x100000000;     // 현재 시간이 음수가 되면 안되기 때문에

             if( CurrentTime >= EndTime)

             {

                   break;

             }

        }

      

        이렇게 하면 이전 프레임 시각에서 16밀리 초 이상 경과하지 않으면 다음으로 넘어가지 않는다. 아

        주 간단히 고정 프레임 레이트를 구현할 수 있지만, 남는 시간을 while문으로 계속 돌리는 것은 좋

        지 않다. 그냥 sleep()으로 재워두자.

 

        while( time() < EndTime())

        {

             sleep( 1);

        }

 

    2) 고정 프레임의 문제점.

      - 현재 빠른 것을 느리게 한다는 가정으로 고정 프레임을 만든 것인데, 느린 것을 빠르게 만드는 방

        법은 없다. 이것이 가장 큰 결점이다. 그래서 프레임 레이트는 가장 느릴 때에 맞춰 정해야만 한다.

        만약 정해진 프레임 레이트에 맞지 않게 된다면 이것은 처리 지연이라고 한다.

 

    3) 프레임 레이트에 맞게 게임 속도를 바꾸는 방법.

      - 프레임 레이트에 맞춰 게임 처리를 변경하는 방법으로 가변 프레임 레이트라고도 한다.

        이 방식의 이점은 크게 두가지로 볼 수 있는데, 첫 번째는 항상 컴퓨터 성능을 극한까지 끌어낼 수

        있다는 것이고 두 번째는 처리 지연이 발생하지 않는다는 것이다. 프로그램이 쉬는 시간이 전혀 없

        으므로 항상 컴퓨터를 최대한으로 사용한다.

       

        하지만 단점도 있다. 프레임 간격이 계속 변한다는 것을 전제로 게임 처리를 작성해야 하므로 코드

        가 복잡해지고, 재현성 문제도 있어서 가변 프레임 레이트 게임에서 같은 상태를 재현하기는 어렵

        다. 때문에 디버그가 상당히 어려워진다.

728x90

+ Recent posts