카테고리 보관물: 수정중

D3D 디바이스 소실 처리

http://www.gpgstudy.com/forum/viewtopic.php?topic=1451

디바이스 소실의 두가지 경우

1. 디바이스 소실되었고 복구 불가능의 경우

: 이 경우에는 대책이 없다. 전부(IDirect3DDevice를 통해 생성된 모든 리소스들과 IDirect3DDeivce객체) 해제한 후 처음부터 다시 생성한다.

2. 디바이스 소실되었고 복구 가능의 경우

: 이 경우에는 디바이스에 올라간 리소스(리소스 생성시 D3DPOOL_DEFAULT 플래그를 이용하여 생성된 객체들)들만 새로 로딩하면 된다.

특이사항

디바이스가 소실되어도 IDirect3DDevice관련 함수들이 성공을 반환 할 경우가 있다.

도움말에 보면 “디바이스가 소실되어도 동작은 보증된다”고 되어있다. 물론 이때의 동작은 헛동작이다. 아무 효과가 없는것이다. 왜 이런식인가 생각해보니, 밑의 글을 읽어보면 알겠지만,, 언제 어떻게 디바이스 손실이 발생할지 모르니 프로그래머로써는 이런 상황을 대처 할 수 없는것이다. 디바이스와 관련된 모든 함수들에 대해서, 리턴값을 체크하지 않는한 불가능하다는 얘기이다. 그리고 손실된 디바이스 자원이 언제 넘어오게 될지도 모르는 상황에서 이런 처리는 너무 고난이도일껏 같다. 차라리 처리되는것 처럼 보이고, 실제로는 처리가 안되게 하는것이 훨씬 이득일껏 같다. 어디까지 제 추측입니다. 틀렸어도 양해를..

왜 이딴게 있는걸까?

예를들면, 2개의 응용프로그램 A와 B가 돌아가는 상황에서, A란 놈과 B란 놈 둘다 비디오 램을 많이 사용하는 프로그램이었다 치자. 이 상황에서 A란 프로그램이 돌아가고 있는 상황이어서 비디오 램의 여유분이 없었다 치자, 자.. 이제 문제의 B가 실행되어 졌을 경우,, 어떻게 해야할까? Win32는 멀티 태스킹 환경이다. 고로 여러 응용프로그램들이 같은 하드웨어 자원을 공유한다. 즉, DOS처럼 하나의 응용프로그램이 하드웨어 자원을 독점하면 안된다는 것이다. 고로 윈도우즈가 택한방식이 활성화 애플리케이션에게 자원을 넘겨주자는 방식이다. A가 활성화되면, A에게 자원을 주고, B가 활성화되면 B에게 자원을 준다. 그러므로 사용자는 두 응용프로그램을 동시에 사용할 수 있는것이다. 덕분에 프로그래머는 귀찮지만은… 어디까지 제 추측입니다. 틀렸어도 양해를.. 아니지 지적을..

디바이스 소실 처리

IDirect3DDevice::TestCooperativeLevel()을 호출하여, 디바이스를 복구 할 수 있는지 검사해본다. 반환값(HRESULT)이

D3DERR_DEVICELOST인 경우 디바이스가 소실되었고 아직 복구할 수 없는 상태이다. 복구할 수 있을때까지 대기한다.
D3DERR_DEVICENOTRESET인 경우 디바이스가 소실되지만 지금 복구할 수 있는 상태이다. 디바이스를 복구한다.
디바이스 복구

. D3DPOOL_DEFAULT로 잡은 리소스들을 전부 릴리즈한다. %%%(안그러면 다음에 호출될 IDirect3DDevice::Reset()이 실패할것이다.)
. IDirect3DDevice::Reset()을 호출한다. %%%(Reset()을 그냥 IDirect3DDevice의 복구명령어라 생각하면 이해가 편함)
. 디바이스를 다시 셋팅한다.(랜더스테이트, 뷰 행렬등..)
. D3DPOOL_DEFAULT로 잡은 리소스들을 다시 로드한다.
Adapter나 D3DDEVTYPE이 변경되어야 하는 경우에는 디바이스를 다시 생성해야 한다.

. 모든 리소스들을 릴리즈한다.
. 디바이스를 릴리즈한다.
. 디바이스를 새로 생성한다.
. 디바이스를 다시 셋팅한다.(랜더스테이트, 뷰 행렬등..)
. 모든 리소스들을 다시 로드한다.
디바이스가 소실되는경우

풀스크린 모드일때..

다른 애플리케이션이 디바이스를 점유하는 경우.
애플리케이션이 작업표시줄로 최소화 되는 경우.
다른 애플리케이션이 풀스크린 모드로 생성될경우.
전력관리 이벤트(WM_POWERBROADCAST)가 발생했을 경우.
윈도우 모드일때..

전력관리 이벤트가 발생했을 경우.
다른 애플리케이션이 풀스크린 모드로 생성될경우.. (이경우 제 카드에서는 문제가 없었음)
디스플레이 등록정보에서 색상모드를 변경하는 경우.
테스트 사항

카드 : Radeon 9000 128M, OS : Win 2000

A,B : D3D를 사용하는 애플리케이션

. A:win, B:win ==> A:full, B:win // (A를 풀스크린으로 전환) %%%-A:ok, B:ok
. A:full, B:win ===> A:win B:win // (A를 윈도우모드로 전환) %%%- A:ok, B:lost(고칠수 있음)
. A:full, B:win ===> A:full(minimize), B:win // (마우스로 바탕화면을 찍거나 Alt+Tab) %%%- A:lost(고칠수 없음), B:lost(고칠수 없음)
. A:full(lost), B:win ===> A:full(lost), B:full // (B를 풀스크린으로 전환) %%%- A:lost상태 유지, B:ok (A는 시작하기 전에 이미 lost상태였음)
. A:full(lost), B:full ===> A:full(lost), B:full(lost) // (바탕화면 찍음) %%%- A:(lost상태유지), B:lost(고칠수 없음) (A는 시작전에 이미 lost상태였음)
. A:full, B:win ===> A:종료, B:win(lost) // (A를 종료) %%%- A:종료됨, B:lost(고칠수 있음)
X. 기타 등등, 여러 상황에 따라 디바이스 로스트가 발생 된다. 이런 상황이 더 있으나 해결책을 어느정도 찾은것 같으므로 더 이상은 생략.

X. 그리고 다른 그래픽 카드의경우는 어떻게 처리될지 잘모르겠음.

결론

1. Present()함수의 리턴값은 항상 체크해야 한다. 위와 같이 언제 디바이스 손실이 발생 할지 모른다.

2. 임의로 체크해 보고 싶은 경우. TestCooperativeLevel()함수를 이용하자.

방법론

필수사항 : Present()리턴값은 항상 반드시 꼭 체크하자. 언제 어떻게 어떤 이유로 Present()가 실패할지 모른다. 이건 필수이다.

그리고 Present()가 실패하면 위와같은 방법으로 복구하면 될꺼 같다.

0. Present()함수 실패시만 복원한다.

단점으로는 에러를 알아내는 시점이 Present()가 호출될때(뷰포트를 하나 생성한 경우라면 한프레임에 한번씩)뿐 이라는 것이다. 이 단점은 랜더링 결과물에 접근하여, 그것이 애플리케이션 로직에 적용되는 경우라면 치명적일 수 있다고 예상된다. 그점을 제외하면 가장 무난할 껏 같다.

1. CD3DApplication(Direct3D Wizzard에서 제공)의 방식

이 클래스를 보면,, 꽤나 복잡한 방식(주로 윈도우 메세지를 핸들링한다.)으로 처리 하는데(즉, 디바이스 손실이 일어나기전에 미치 처리함)으로 해결하고 있다. 꼭 이렇게 까지 해야 하는지는 잘모르겠지만… 요는 여러가지 상황에 미리 대처한다는 방식이다. 위의 <테스트 사항>같은 짓을 많이 시도해보고, 어느때 디바이스가 손실되는지 정확하게 알아낸 후, 미리알 수 있는경우(풀스크린에서 Alt+tab에 의해 갑자기 비활성화 시킨다든지 등등) 할수 있는데까지 까지 하는것이다. 그런 눈물겨운 노력이 CD3DApplication에 보면 잘 나온다. 이런건 보고 베끼자…^^ 이경우 100% 문제를 해결했다고 볼 수는 없다고 생각되지만 0번의 경우보단 낫다. 이런 문제를 봐선 Direct3D의 설계의 결함같기도 하다. 결국 이런 문제들을 100% 해결 할 수는 없다고 생각된다.

결과 : 실제로 1번과 같이 대부분 예측되는 치명 적인 몇몇 가지들에 대해 미리 알아내서 처리해주면,, 거의 문제는 안생긴다. 대신 코딩이 지저분해지는건 어쩔수 없다.

stub code

void RenderAll()
{
// …
// 모든 그래픽들에 대한 랜더링을 한다.
// …

// 백버퍼 플리핑.
hr = m_pDevice->Present( NULL, NULL, NULL, NULL );
if ( FAILED(hr) )
{
if ( D3DERR_DEVICELOST == hr )
{
// 디바이스를 소실했고, 복구가 불가능 상태
}
else ( D3DERR_DEVICENOTRESET == hr )
{
// 디바이스를 소실했지만, 복구가 가능한경우.
}
}
}
설계

한가지 방법은 랜더러 클래스(또는 이런 이벤트를 처리할 객체)에 두가지상황(디바이스 손실시 복구가능/복구불가능)이 발생했을때, 로딩할 목록을 등록해 놓은 후, 이벤트가 발생하면 각각 해당 이벤트에 맞게끔 다시 로드하는 식으로, 구성하면 될 껏 같다.

//**** 디바이스 리소스 인터페이스..
// 디바이스 의존적인 리소스들의 추상클래스.
struct IDeviceRes
{
virtual void Invalidate() = 0;
virtual bool SetValidate() = 0;
};

//**** 텍스쳐 클래스
class CTextureRes : public IDeviceRes
{
public:
virtual void Invalidate() { 텍스쳐 해제; }
virtual bool SetValidate() { 텍스쳐 다시 로드; }

bool Load();
void Unload();

protected:
char m_szFileName[256];
LPDIRECT3DTEXTURE9 m_pTexture;
};

//**** 텍스쳐를 관리할 클래스(클라이언트에서 접근하여 사용)
class CDataContainer
{
public:
LoadTexture( const char* szFileName )
{
// 1. 텍스쳐 로딩
// 2. 만약 텍스쳐가 VRam에 잡혔다면..(D3DPOOL_DEFAULT)
// 2.1 랜더러의 의 VRam리스트에 등록(m_pRenderer->m_VRamRes)
// 3. 텍스쳐가 다른모드로 생성되었다면..(D3DPOOL_MANAGED or etc..)
// 3.1 랜더러의 의 Managed리스트에 등록(m_pRenderer->m_ManagedRes)
}

UnloadTexture( const char* szFileName )
{
// 랜더러의 디바이스 목록에서 지운다.
m_pRenderer->Unregist( szFileName );
}

protected:
vector m_TextureContainer;
CRenderer* m_pRenderer;
}

//**** 문제의 랜더러 클래스
class CRenderer
{
public:
// 디바이스가 해제 됬거나 해제 하라.
// – 이 함수는 외부에서 임의로 호출이 가능하다.
void InvalidateDevice( HRESULT hrDeviceState )
{
if ( FAILED( hrDeviceState )
{
if ( D3DERR_DEVICENOTRESET == hrDeviceState )
{
// 1. m_VRamRes 리소스만 해제한다.
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->Invalidate(); }
}
else if ( D3DERR_DEVICELOST == hrDeviceState )
{
// 1. m_VRamRes과 m_ManagedRes들에 대해서 해제한다.(전 리소스들에 대해 수행)
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->Invalidate(); }
// for_each(m_ManagedRes의 시작에서 끝가지) { m_ManagedRes[i]->Invalidate(); }

// 2. 디바이스를 릴리즈한다.
}
}
m_hrDeviceState = hrDeviceState; // 디바이스 상태값 업데이트
}

// 디바이스가 활성화하라.
// – 이 함수도 외부에서 임의로 호출이 가능하다.
bool SetValidateDevice()
{
if ( FAILED(m_hrDeviceState) )
{
if ( D3DERR_DEVICENOTRESET == m_hrDeviceState )
{
//**** 디바이스를 복구할수 있다.
// 1. IDrect3DDevice::Reset()한다.
// 2. 랜더 스테이트 / 뷰행렬 셋팅
// 3. m_VRamRes 리소스들을 다시 로딩한다.
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->SetValidate(); }
}
else if ( D3DERR_DEVICELOST == m_hrDeviceState )
{
//**** m_VRamRes 복구할수 없다.
// 1. 디바이스를 재생성한다.
// 2. 랜더 스테이트 / 뷰행렬 셋팅
// 3. m_VRamRes / m_ManagedRes 리소스를 로딩한다.
// for_each(m_VRamRes의 시작에서 끝까지) { m_VRamRes[i]->SetValidate(); }
// for_each(m_ManagedRes의 시작에서 끝가지) { m_ManagedRes[i]->SetValidate(); }
}

// 검증해본다.
m_hrDeviceState = m_pDevice->TestCooperativeLevel();
return SUCCEEDED(m_hrDeviceState);
}
return true;
}

// 랜더링한다.
void RenderAll()
{
//**** 디바이스가 유효한 상태가 아니라면 그냥 리턴.
if ( FAILED(m_hrDeviceState) ) return;

// …
//**** 모든 그래픽들에 대한 랜더링을 한다.
// …

//**** 백버퍼 플리핑.
m_hrDeviceState = m_pDevice->Present( NULL, NULL, NULL, NULL );
if ( FAILED(m_hrDeviceState) )
{
// 디바이스에 뭔가 이상이 있다면,,
InvalidateDevice( m_hrDeviceState );

// 외부에 알려준다. 아마 누군가에게 알려줄 필요가 있을껏이다.
// CApp나 혹은 다른 누군가에게..
}
}

protected:
//**** properties
// 디바이스 의존적인 메모리 관리
vector m_VRamRes; // POOL_DEFAULT
vector m_ManagedRes; // MANAGED or etc
HRESULT m_hrDeviceState; // 현재의 디바이스 상태값

LPDIRECT3DDEVICE9 m_pDevice;
};
추신

괜찮은 방법이나, 사용되어지는 방법이 있으면 올려주세요. 저도 구현은 안해본 사람입니다. 아마 틀린 부분도 있으리라 생각됩니다. 많은 지적을.. 써놓고 보니 장문이네요.

< 덧글 >

그리고 업데이트 내용입니다. gpgstudy.com의 손님께서 쓰신글의 요약입니다.

TestCooperativeLevel()로 D3DERR_DEVICENOTRESET임을 확인한후 Reset()을 했을때 실패할 수도 있다는군요.

나중에 TestCooperativeLevel()로 확인해 보니 D3DERR_DEVICELOST였다고 합니다. 결국 Reset()이 실패했을경우에도 다시 체크를 한후 소실상태라면 디바이스를 다시 생성해야 할껏같습니다.

CMiniFrameWnd

http://www.functionx.com/visualc/controls/miniframe.htm
http://blog.naver.com/lifeisforu/80030201322

The Mini Frame Window

Introduction

미니 프레임 윈도우는 대부분 응용프로그램의 주요 오브젝트인 다른 윈도우들과 함께 존재하는 유동(floating) 오브젝트로서 사용됩니다. 그것은 우리가 예전부터 보아 왔던 것처럼 경계와 클라이언트 영역을 가지는 보통의 프레임 윈도우처럼 보입니다. 그렇지만 미니 프레임 윈도우는 메인 윈도우가 아니라 툴로서 사용됩니다. 이러한 기능의 예를 들자면, 그것은 시스템 최소/최대화 버튼을 가지고 있지 않습니다. 툴 윈도우처럼 그것은 짧은 타이틀 바와 닫기 버튼을 가지고 있습니다.

Creation of a Mini Frame Window

미니 프레임 윈도우는 CMiniFrameWnd 클래스에 기반합니다. 미니 프레임 윈도우를 생성하기 위해서는 먼저 CMiniFrameWnd 에 대한 포인터나 변수를 선언합니다. AfxRegisterWndClass() 함수를 사용해서 그것의 클래스를 정의할 수 있습니다. 그리고 나서 그 함수의 반환값을 Create() 메서드에 넘깁니다. 이 메서드의 구문이 다음에 나와 있습니다 :

    virtual BOOL Create
        LPCTSTR lpClassName,
        LPCTSTR lpWndowName,
        DWORD dwStyle,
        const RECT& rect,
        CWnd* pParentWnd = NULL,
        UINT uID = 0
    );

lpClassName 매개변수는 AfxRegisterWndClass() 함수로부터 반환된 값이어야 합니다.

lpWindowName 은 윈도우의 타이틀바에 보여질 캡션입니다.

dwStyle 매개변수는 윈도우에 적용될 스타일입니다. 일반적으로 그것은 보통 윈도우의 정규 스타일을 사용합니다만, 많은 스타일이 다릅니다. 예를 들어서 Minimize 와 Maximize 버튼을 적용하든 적용하지 않든 그것들은 보이지 않습니다. 단지 다음 조합만을 사용할 수 있습니다 : WS_POPUP | WS_CAPTION | WS_SYSMENU. 정규 윈도우 스타일 이외에도 다음과 같은 특별한 스타일 중 하나 이상을 사용해 조합을 만들 수 있습니다 :

    MFS_MOVEFRAME : 이 스타일을 가지면 사용자가 프레임의 어떤 테두리 부분이라도

    클릭 & 드래그해서 마치 타이틀 바를 드래그한 것처럼 윈도우를 움직일 수 있습니다. 만약

    당신이 이 스타일을 적용한다면, 다른 스타일을 지정해서 윈도우를 리사이즈할 수 있도록

    설정했다고 해도 그것은 무시됩니다.

    MFS_4THICKFRAME : 이 스타일은 사용자가 미니 프레임을 리사이즈하지 못하게 합니다.

    MFS_SYNCACTIVE : 이 스타일은 미니 프레임의 부모가 활성화되어 있을 때

    자신도 활성화되어 있도록 보장합니다.

    MFS_THICKFRAME : 이것은 두꺼운 프레임을 생성하고 사용자가 필요시 윈도우를

    리사이즈할 수 있도록 합니다. MFS_MOVEFRAME 이 무시됩니다.

    MFS_BLOCKSYSMENU : 이것은 시스템 메뉴에 대한 접근을 막습니다.

rect 매개 변수는 프레임 윈도우의 위치 및 차원을 지정합니다.

pParentWnd 인자는 윈도우의 CWnd 부모입니다. 이 인자는 필수가 아닙니다.

nID 인자는 미니 프레임 윈도우의 식별자입니다. 이것은 필수가 아닙니다.

Create() 메서드 이외에도 CMiniFrameWnd 클래스는 CreateEX() 멤버 함수를 제공해 미니 프레임 윈도우를 생성할 수 있게 합니다.

void CMiniFrame2Dlg::OnBnClickedMiniframeBtn()
{
// TODO: Add your control notification handler code here
CMiniFrameWnd *MFW = new CMiniFrameWnd;
CString StrClassName = AfxRegisterWndClass(

CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,
LoadCursor(NULL, IDC_ARROW),
(HBRUSH)GetStockObject(COLOR_BTNFACE+1),

LoadIcon(NULL, IDI_APPLICATION));

MFW->CreateEx(0, StrClassName,

"Small Application",

WS_POPUP | WS_CAPTION | WS_SYSMENU |

MFS_BLOCKSYSMENU,

CRect(100, 100, 350, 420));

MFW->ShowWindow(SW_SHOW);

Size가 0인 배열은 도대체 무엇에 쓰이는 물건인가?

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

Size가 0인 배열은 도대체 무엇에 쓰이는 물건인가?

 

 

                                        작성자 고임

                                        최초 작성일 : 2007년 11월 30일 ~

                                        마지막 작성일 2007년 12월 5

 

– 들어가기

 

  우리는 컴퓨터 언어의 표준을 따르며 험난한 코딩의 나날을 보내고 있다매일 매일 새로운 기술 홍수 속을 키보드와 마우스로 헤쳐나가며코딩의 앞길마다 떨어지는 버그를 씹어 삼키며코딩의 기술 연마하고 있다.

 

고임 역시 중후한 표준의 사명을 따르며코딩을 하던 중컴파일러는 내게 다음과 같은 경고를 보내왔다.

warning C4200: nonstandard extension used : zero-sized array in struct/union (.cpp)

 

물론 실행하는데는 문제가 없으나 컴파일러가 도전장을 내밀듯이 불쑥 튀어나온 경고는 기분이 나쁘지 않는가?

 

한동안 위와 같은 경우를 겪어보지 않아적잖히 당황을 했지만이내 몇 년전 구글의 포럼의 쓰레드에서 사이즈가 0인 배열의 글타래를 기억해냈다물론 비표준이었는지 아니었는지 기억이 가물가물하여 내가 알고 있는 프로그래머 중 가장 파워풀한 지식과 다이나믹한 코딩 스킬을 가지고 계신 분에게 여쭈어보았다.

 

고임 사이즈가 0인 배열은 표준이 아니죠?

그분 표준은 아니지만 대형 벤더들은 전부 지원해요.

 

  그때의 다시금 기억을 되살리려 노력을 한다그러나금붕어도 울고갈 기억력의 소유자인 고임은 “후후 내가 기억할리가 없잖아!”라며.. 너무 쉽게 인정해버리고 자료를 찾아본다그러나 별다른 자료가 없다예전에 보았던 구글에서의 쓰레드는 어디로 갔는지 잘 찾질 못하겠고.. 다만 기억이 있는 것은 그 글타래에서 나왔던 이야기 중에 이런게 된다정도.. 였던 걸로 기억이 된다그 당시에는 사실 별 관심은 두지 않았다.

 

 그리하여 내가 하고 있는 프로젝트에서 해당 경고가 나온 줄을 찾아본다그것을 기반으로 하여 사이즈가 0인 배열의 정체를 파혜쳐 보기로 하였다.

 

 이런 식의 강좌 글은 느낌 상으로 수백년만에 다시 써보는 것 같아조금은 설레이나.. 할 것이 태산인 상황에서 이런 짓을 하니 조금은 눈치가 보이기는 한다.

 

 허나벤더사가 자신있게 내놓은 라이브러리에 비표준이 존재한다는 것에 무슨 의미가 있지나 않을까하는 강항 의구심을 해결하기 위하여 이 거대하고도 멋지고도 다 알고 나면 허무 개그를 본 듯한 주제를 가지고 달려보도록 하자.

 

 

– 사이즈가 0인 배열의 비표준의 경고와 에러 사이.

 

사이즈가 0인 배열이라고 하는 것은 단순히 다음과 같다.

int nZeroArray[0];

 

 즉 배열을 선언하는 순간 그 배열의 크기가 0이라는 것을 이야기 한다.

그러나 이것은 비표준이다따라서 당연히 에러가 발생이 된다상식적으로 크기가 0인 배열을 선언한다는 것이 말이 안되는 것 아닌가사이즈가 0인데 뭐하러 선언하는가?

 

그래서 우리의 컴파일러께서는 다음과 같은 에러 문장을 내주신다.

 

error C2466: 상수크기 0의 배열을 할당할 수 없습니다.

 

의역하자면 “장난하냐크기가 0인 배열을 어디에 위치시키라고?” 크기가 없기에 실존할 수 없다는 철학적인 메세지를 주시는 것이다.

 

여기서 우리가 눈치를 채야 할 것은 바로 사이즈가 0인 배열은 독립적으로 사용할 수 없다 라는 사실이다.

자 반복 학습이 중요하다.

사이즈가 0인 배열은 절대 독립적으로 사용할 수 없다.

 

자 그렇다면 이건 어디서 사용해야하는 것일까?

이것은 눈치가 빠른 분들은 아시겠지만구조체 공용체 클래스에서 사용할 수 있다뭐 사실 그것 밖에 더 있겠는가? -_-;

 

그 이유는 나중에 이야기 하기로 하고다음을 보도록 하자.

 

union UZeroArray{

     int nZeroArray[0];

};

struct SZeroArray{

     int nZeroArray[0];

};

class CZeroArray{

     int nZeroArray[0];

};

 

이 세 개의 구조체공용체클래스(이거 한글 표현이 뭐지?)안에는 멤버로써 크기가 0인 배열을 갖는다.

이 삼총사는 컴파일링의 시험에 간신히 합격하시며다음과 같은 코멘트를 달고 빌드가 되신다.

 

warning C4200: 비표준 확장이 사용됨구조체/공용구조체의 배열 크기가 0입니다.

UDT에 크기가 0인 배열이 있는 경우 복사 생성자 또는 복사 할당 연산자를 생성할 수 없습니다.

 

이 경고는 봐주긴 봐주는데일반적으로 사용하면 죽어! 라는 의미를 갖는다따라서 사이즈가 0인 배열은 용도가 매우 좁으니까너가 신경써서 사용하라는 이야기이다.

 

자 여기서 강좌 예제의 범위를 축소하기로 한다공용체나 클래스의 예제는 과감히 건너뛰기로 하고 구조체에 대해서만 살피도록 하겠다. (알고 싶은 분은 직접 코딩해서 알아보시길.)

 

 

struct SEndZeroArray{

     int ndummy;

     int nZeroArray[0];

};

struct SStartZeroArray{

     int nZeroArray[0];

     int ndummyEnd;

};

struct SMiddleZeroArray{

     int ndummyStart;

     int nZeroArray[0];

     int ndummyEnd;

};

struct STwoZeroArray{

     int ndummy;

     int nZeroArray1[0];

     int nZeroArray[0];

};

 

위의 크기가 0인 배열의 예제들이 아주 훌륭히 짜여져 있다여기서 컴파일 에러를 내지 않는 것은 무엇일까?

컴파일을 해보면 알겠지만첫 번째 예제만이 무난히 컴파일링을 통과하시게 된다나머지 세 개의 예제는 다음과 같은 에러를 당하시게 된다.

error C2229: struct ‘SstartZeroArray’에 크기가 0인 잘못된 배열이 있습니다.

 

사이즈가 0인 배열의 멤버는 공용체구조체클래스의 선언에서 가장 마지막에 선언을 해줘야하며해당 구조체공용체클래스에서 유일한 멤버변수여야 한다는 이야기이다여기서 매우 중요한 이야기가 두 개나 나오게 된다자 기억하자!!

 

사이즈가 0인 배열은 공용체구조체클래스 안에서 가장 마지막 멤버 변수로 선언되어야 한다.

사이즈가 0인 배열이 선언이 되는 공용체구조체클래스 안에서 오직 하나여야 한다.

 

여기까지 사이즈가 0인 배열을 에러를 피하기 위해 어떤 식으로 선언을 해야할지를 알아보았다세줄로 요약하면 다음과 같다이것을 명심하고 다음 단계로 넘어가기로 하자.

 

사이즈가 0인 배열은 절대 독립적으로 사용할 수 없다.

사이즈가 0인 배열은 공용체구조체클래스 안에서 가장 마지막 멤버 변수로 선언되어야 한다.

사이즈가 0인 배열이 선언이 되는 공용체구조체클래스 안에서 오직 하나여야 한다.

 

 

– 버그의 경계를 넘어..

 

자 여기서 부터는 왜 이런 비표준을 여전히 아직도 쓰고 있는지에 대해서 알아보겠다라고는 했지만역으로 추적하면서 이 강좌를 쓰는 것이므로 역사적인 배경은 전혀 알 수 없다그리고 역시 C99이나 새로 계정된 C++ 표준에 반영이 되려 했는지도 알 수 없다최근에 보았던 C99에 자료에서는 보지 못했던 것 같다사실 이런 기법에 대해서 관심이 없었다고 하는 것이 더 맞는 표현이겠지만말이다.

 

기존에 MS가 제공하는 라이브러리의 헤더에서 보여지는 크기가 0인 배열의 흔적을 거꾸로 찾아보며 느낀 점을 바탕으로 만들어진 예제를 가지고 왜 이런 비표준을 아직도 사용하는지에 대해서 이야기 해보도록 한다. (사실 크기가 0인 배열을 활용할 수 있는 기법은 하나 밖엔 없을 것으로 확신한다. )

 

 

자 시작해 보도록 합시다.

아래 예제는 일반적으로 어떤 정보 목록의 크기가 정적으로 정해져 있지 않고, 동적으로 변경이 될 가능성이 높을 때 사용되는 일반적인 정보 목록 관리 방법과 크기가 0인 배열을 이용한 정보 목록 방법을 비교하여크기가 0인 배열을 사용하면 어떤 이득이 있는지에 대해 알아보는 시간을 갖도록 하겠다.

 

다시 쉬운 문장으로 이야기 하자면, 아.. 예전엔 쉽게 잘(-_-;) 썼던 것 같은데. 음…..

예를 들어,

공연 티켓을 사는 사람들의 성향을 알아보기 위한 프로그램을 설계한다고 가정하자.

(여기서 Db를 사용하는 건 제외합시다.)

 

기본적으로 공연 티켓을 사는 사람들의 정보는 이름나이구매티켓수지역구입처 등등이 있을텐데.

구매하는 사람에 대한 정보를 담을 구조체가 필요할테고,

이 정보를 통합 관리할 구조체가 또 필요하다.

 

바로 이런 경우의 상황에 대해서 앞으로 논의할 것이다.

 

아래 예제를 보시면서,

 

기술적으로 저건 올바른 방법이 아니네.

이렇게 해야 더 효율적이네.

저게 무슨 일반적인 방법이야?

크기가 0인 배열은 됐구요도는 믿고 계십니까?

저 예제는 어디서 본 것 같아당신 표절 쟁이지?

 

라고 평하실 분들.. 여기서 읽기를 그만두시길 바랍니다.

더 읽어봤자 서로 정신건강에 해롭습니다.

 

다시 한번 강조하지만, 이 강좌는 “크기가 0인 배열이 무엇인가?”를 알아보는 것이다.

설명하기 편하게 UNICODE는 사용하지 않았다는 걸 미리 이야기한다.

 

예제의 빌드 환경

OS : Windows XP Professional

Complier : VS2005

CPU : Intel Core 2 6600

 

 

typedef struct tagVirtualDeviceInfo

{

     int DummyN;

     char DummyString[30];

     float DummyFloat;

}VirtualDeviceInfo, * pVirtualDeviceInfo;

Device의 정보를 담을 구조체.

 

typedef struct tagVirtualDevice

{

     int DeviceSize; // 가상장치의 갯수.

     pVirtualDeviceInfo Info; // 가상장치의 정보에 대한 주소

}VirtualDevice, *pVirtualDevice;

Device 정보를 통합 관리할 구조체일반적인 방법)

 

이 데이타 구조는 여러개의 가상 장치의 정보를 가상 장치 정보의 주소를 담을 수 있도록 했으며데이타 구조에서 관리되는 가상 장치의 정보의 갯수도 기록할 수 있겠금 구성되어있다.

 

이 구조가 우리가 흔히 접할 수 있는 구조이며이런 형태로 구성하는 것이 거의 정석이라 할 수 있겠다.

 

typedef struct tagVirtualDeviceZeroSize

{

    int DeviceSize; // 가상 장치의 갯수.

     VirtualDeviceInfo Info[0]; // 바로 이 분이 이 강좌의 주인공이심.

}VirtualDeviceZeroSize, * pVirtualDeviceZeroSize;

Device 정보를 통합 관리할 구조체크기가 0인 배열을 이용한 방법)

 

이 데이타 구조는 2번째의 데이타와 구조와 마찬가지이지만가상 장치 정보를 기록하는 멤버 변수가 크기가 0인 배열로 선언이 되어 있다.

 

처음 보는 사람들은 아마 “이 뭐병” 이란 단말마를 내실지 모르겠으나이 코드는 굳건히 컴파일링을 통과하십니다.

 

 

01 : void ZeroLengthArrayTest(void)

02 : {

03 : int nDeviceNumber = 2;

여기서 가상 장치가 2개만 있다고 생각하기로 하자.

 

04 : int nSizeVirtualDeviceInfo = sizeof(VirtualDeviceInfo);

05 : int nSizeVirtualDevice = sizeof(VirtualDevice);

06 : int nSizeVirtualDeviceZero = sizeof(VirtualDeviceZeroSize);

 

07 : int nSizeVirtualDeviceMem = nSizeVirtualDevice;

08 : int nSizeVirtualMemZero = sizeof(VirtualDeviceZeroSize) + sizeof(VirtualDeviceInfo)*nDeviceNumber;

 

 

 

여기서 우리는 VirtualDevice 크기가 8바이트이고, VirtualDeviceZeroSize의 크기가 4바이트 라는 것을 눈여겨봐야한다. VirtualDeivce의구조는Device의 목록의 갯수와 DeviceInfo의 정보를 가리키는 포인터로 구성되어 있으므로당연히 8바이트가 되게 된다. (물론 32비트 OS 상에서 32비트 어플리케이션을 제작할때의 이야기이다설마 이런 것도 트집 잡으려나? )

 

VirtualDeviceZeroSize는 Device의 목록의 갯수를 저장하는 변수가 4바이트 DeviceInfo의 정보를 저장하는 배열의 크기가 0이다.

 

이런 걸 처음 보는 사람은 리엑션을 크게 취해주도록 하자그래야 기억에 남는다고 하더라.

 

허허 그렇담 VirtualDeviceInfo Info[0]; 는 어디에 저장이 될 것인가?

그 이야기는 잠깐 뒤에서 이야기하도록 한다.

 

일반적인 방법구조체 초기화

 

09 : pVirtualDevice pVD1 = new VirtualDevice;

Virtual Device의 목록을 관리할 데이타를 생성합니다.

 

10 : VirtualDeviceInfo* pVirtualDeviceMemInfo = new VirtualDeviceInfo[nDeviceNumber];

11 : memset(pVirtualDeviceMemInfo, 0, nDeviceNumber* sizeof(VirtualDeviceInfo));

Virtual Device의 정보를 기록할 메모리를 확보하고 초기화한다.

 

12 : pVD1->DeviceSize = nDeviceNumber;

디바이스의 갯수를 기록한다.

 

13 : pVD1->Info = pVirtualDeviceMemInfo;

VirtualDeviceInfo를 가리키는 멤버 변수를 초기화시킵니다이 부분은 크기가 0인 배열을 이용한 방법에선 필요 없는 코드가 된다.

 

크기가 0인 배열을 이용한 방법구조체 초기화

 

14 : void* pVirtualDeviceMemZero = new BYTE[nSizeVirtualDeviceMemZero];

15 :memset(pVirtualDeviceMemZero, 0, nSizeVirtualDeviceMemZero);

위와는 다르게 Virtual Device의 목록을 관리할 데이타와 Virtual Device의 정보를 나타내는 데이타의 크기를 선형적으로 한꺼번에 생성하고 초기화하는 것에 유의하며 봅시다.

이 부분이 바로 크기가 0인 배열을 사용하는 이유가 되며일반적인 방법과는 달리 VirtualDeviceInfo

를 가리키는 멤버 변수를 초기화 할 필요가 없다.

 

16 : pVirtualDeviceZeroSize pVD2 = (pVirtualDeviceZeroSize) (pVirtualDeviceMemZero) ;

 

자 위에서 생성한 메모리를 형변환하여 pVirtualDeviceZeroSize 형의 변수에 대입해보자이때 VirtualDeviceInfo를 가리키는 Info의 주소를 살펴보면정확히 pVD2라는 변수가 할당된 메모리 위치의 바로 다음을 가리키게 된다.

호오 놀랍지 않은가? 일반적인 방법의 13번째 줄 처럼 Info 변수에 초기화도 하지 않았는데 말이다.

이것이 가능한 이유는 바로 배열이 선형적인 메모리를 갖는다는 특성 때문이다.

 

 

위의 그림을 보면.. pVD2의 메모리 위치는 0x00ee9f30이고, pVD2->Info가 가리키는 메모리 위치는 0x00ee9f34이다정확히 4만큼의 offset을 가지고 있는 것을 확인 하도록 하자.

 

17 : pVD2->DeviceSize = nDeviceNumber;

디바이스 갯수를 기록한다.

 

 

일반적인 방법 데이타 입력

 

18 : pVD1->Info[0].DummyN = 0x11;

메모리를 알아보기 쉽게 하기 위해 0x11을 삽입하도록 하자.

 

19 :memcpy(pVD1->Info[0].DummyString, “Virtual Device 1”sizeof(“Virtual Device 1”) + 1);

20 : pVD1->Info[1].DummyN = 0x22;

21 :memcpy(pVD1->Info[1].DummyString, “Virtual Device 2”sizeof(“Virtual Device 2”) + 1);

 

크기가 0인 배열을 이용한 방법데이타 입력

 

22 : pVD2->Info[0].DummyN = 0x11;

23 :memcpy(pVD2->Info[0].DummyString, “Virtual Device Zero 1”sizeof(“Virtual Device Zero 1”) + 1);

24 : pVD2->Info[1].DummyN = 0x22;

25 :memcpy(pVD2->Info[1].DummyString, “Virtual Device Zero 2”sizeof(“Virtual Device Zero 2”) + 1);

 

비록 크기가 0인 배열이라고 선언을 하였더라도배열의 이름인 Info가 배열이 시작되는 지점을

가리키는 주소가 되므로, pVD2 메모리 위치 뒤에 VirtualDeviceInfo 크기를 기준으로 배열의 크기가

확보된 상태에선 배열처럼 사용이 가능하다.

 

또 다른 장점으로는 어셈 코드가 간단해진다는 장점이 존재한다.

구조체에서 맴버 변수로 접근을 할때는 구조체 변수의 주소를 알아내고 나서해당 구조체의 멤버 변수의 주소를 알아낸 뒤 접근을 하게 되는데.

 

크기가 0인 배열을 이용하게 되면크기가 0인 멤버 변수가 메모리에 생성 되지 않기 때문에 Info란 맴버 변수는 코드상으로만 존재할 뿐 컴파일을 하게 될 때는 그저 offset으로만 인식하게 된다.

따라서 위의 일반적인 방법보다 조금은 명령어가 적게 들게 된다.

 

26 : delete pVD1;

27 : delete [] pVirtualDeviceMemInfo;

28 : delete [] pVirtualDeviceMemZero;

37 : }

 

정리하자면 크기가 0인 배열은 선형적으로 크기가 변하는 경우에 사용할 수 있는 일종의 치트 문법이다.

배열이란 문법적인 특징을 아주 치사하게 이용하고 있다고나 해야 할까?

이걸 처음 이용한 사람이 누구인진 모르겠으나, 이 점에 대해선 기립박수를 쳐주고 싶다. .

뭐 어찌 되었던, 크기도 작고, 명령어도 적게 쓰면서 동일한 효과를 내게 되었으니 말이다.

 

C언어에서 기존에 많이 사용이 되고 있어서, C++에서는 C와의 호환 유지 차원에서 봐 준 것 같다.

사실 마이크로 콘트롤러 래벨로 내려가면 막강한 기법이라고 생각이 든다.

하지만, 트릭처럼 사용되는 문법이기 때문에 언제까지 이것을 유지할지는 확신은 들지 않는다.

문법상으로 명백히 틀린 부분은 없으나, 버그의 경계선에서 왔다 갔다 하는 코드를 유지해야 하는 것도

점점 강화되고 있는 문법 성향에 안 맞는 것 같기도 하고,

C언어는 모르겠지만 아마 앞으로는 C++ 언어에선 사라질지도 모를 일이라고 조심스레 예견해본다.

 

부록 – 예제 코드 전문

 

typedef struct tagVirtualDeviceInfo

{

int DummyN;

char DummyString[30];

float DummyFloat;

}VirtualDeviceInfo, * pVirtualDeviceInfo;

 

typedef struct tagVirtualDevice

{

int DeviceSize; // 가상장치의 갯수.

pVirtualDeviceInfo Info; // 가상장치의 정보에 대한 주소

}VirtualDevice, *pVirtualDevice;

 

typedef struct tagVirtualDeviceZeroSize

{

int DeviceSize; // 가상 장치의 갯수.

VirtualDeviceInfo Info[0];

}VirtualDeviceZeroSize, * pVirtualDeviceZeroSize;

 

 

void ZeroLengthArrayTest(void)

{

int nDeviceNumber = 2;

int nSizeVirtualDeviceInfo = sizeof(VirtualDeviceInfo);

int nSizeVirtualDevice = sizeof(VirtualDevice);

int nSizeVirtualDeviceZero = sizeof(VirtualDeviceZeroSize);

 

int nSizeVirtualDeviceMem = nSizeVirtualDevice;

int nSizeVirtualMemZero = sizeof(VirtualDeviceZeroSize) + sizeof(VirtualDeviceInfo)*nDeviceNumber;

 

pVirtualDevice pVD1 = new VirtualDevice;

VirtualDeviceInfo* pVirtualDeviceMemInfo = new VirtualDeviceInfo[nDeviceNumber];

memset(pVirtualDeviceMemInfo, 0, nDeviceNumber* sizeof(VirtualDeviceInfo));

pVD1->DeviceSize = nDeviceNumber;

pVD1->Info = pVirtualDeviceMemInfo;

 

void* pVirtualDeviceMemZero = new BYTE[nSizeVirtualDeviceMemZero];

memset(pVirtualDeviceMemZero, 0, nSizeVirtualDeviceMemZero);

pVirtualDeviceZeroSize pVD2 = (pVirtualDeviceZeroSize) (pVirtualDeviceMemZero) ;

pVD2->DeviceSize = nDeviceNumber;

 

pVD1->Info[0].DummyN = 0x11;

memcpy(pVD1->Info[0].DummyString, “Virtual Device 1”sizeof(“Virtual Device 1”) + 1);

pVD1->Info[1].DummyN = 0x22;

memcpy(pVD1->Info[1].DummyString, “Virtual Device 2”sizeof(“Virtual Device 2”) + 1);

 

pVD2->Info[0].DummyN = 0x11;

memcpy(pVD2->Info[0].DummyString, “Virtual Device Zero 1”sizeof(“Virtual Device Zero 1”) + 1);

pVD2->Info[1].DummyN = 0x22;

memcpy(pVD2->Info[1].DummyString, “Virtual Device Zero 2”sizeof(“Virtual Device Zero 2”) + 1);

 

delete pVD1;

delete [] pVirtualDeviceMemInfo;

delete [] pVirtualDeviceMemZero;

}

sqlite의 stmt를 재활용(sqlite3_reset)하여 성능 향상 시키기

http://greenfishblog.tistory.com/m/post/view/id/151

sqlite3를 사용하여 Insert나 Query할 때 아래와 같은 방법으로 사용할 수 있습니다.
(리턴값 체크는 필수이지만, 간략하게 표시하기 위해 제외하였습니다)

(1) sqlite3_exec을 통해 SQL문을 그대로 만들어 전달하고, query는 callback 함수를 통해 전달 받음

int fnCallback(void* arg, int columns, wchar_t** value, wchar_t** name)
{

}

for (…)
{

// DB QUERY
        sqlite3_exec(db, “SELECT …“, fnCallback, pContext, NULL);

// DB INSERT
        sqlite3_exec(db, “INSERT …“, NULL, NULL, NULL);

(2) statement를 prepare하여 callback 함수 없이 각 값들의 bind를 통해 전달하고 전달 받음

    …
sqlite3_stmt* pStmt = NULL;

for (…)
{
// DB INSERT
        sqlite3_finialize(pStmt);
sqlite3_prepare(db, “INSERT … (?,?)“, -1, &pStmt, NULL);
sqlite3_bind_int(pStmt, 1, 123);
sqlite3_bind_int(pStmt, 2, 456);
sqlite3_step(pStmt);
sqlite3_finalize(pStmt);
pStmt = NULL;

// DB QUERY
        sqlite3_prepare(db, “SELECT a,b,c … WHERE a=? AND b=?“, -1, &pStmt, NULL);
sqlite3_bind_int(pStmt, 1, 111);
sqlite3_bind_int(pStmt, 2, 333);
for (;;)
{
if (SQLITE_ROW == sqlite3_step(pStmt))
{
nA = sqlite3_column_int(pStmt, 0);
nB = sqlite3_column_int(pStmt, 1);
nC = sqlite3_column_int(pStmt, 2);
}
else
{
break;
}
}
sqlite3_finalize(pStmt);
pStmt = NULL;

(3) statement를 단 한번만 prepare하고 필요시 reset하여 재사용

    …
sqlite3_stmt* pStmtInsert = NULL;
sqlite3_stmt* pStmtQuery  = NULL;

sqlite3_prepare(db, “INSERT … (?,?)“, -1, &pStmtInsert, NULL);
sqlite3_prepare(db, “SELECT a,b,c … WHERE a=? AND b=?“, -1, &pStmtQuery, NULL);

for (…)
{
// DB INSERT
        sqlite3_reset(pStmtInsert);
sqlite3_bind_int(pStmtInsert, 1, 123);
sqlite3_bind_int(pStmtInsert, 2, 456);
sqlite3_step(pStmtInsert);

// DB QUERY
        sqlite3_reset(pStmtQuery);
sqlite3_bind_int(pStmtQuery, 1, 111);
sqlite3_bind_int(pStmtQuery, 2, 333);
for (;;)
{
if (SQLITE_ROW == sqlite3_step(pStmtQuery))
{
nA = sqlite3_column_int(pStmtQuery, 0);
nB = sqlite3_column_int(pStmtQuery, 1);
nC = sqlite3_column_int(pStmtQuery, 2);
}
else
{
break;
}
}

}

sqlite3_finalize(pStmtInsert);
pStmtInsert = NULL;
sqlite3_finalize(pStmtQuery);
pStmtQuery = NULL;

겉으로 확인해 보면 방법 (1)이 가장 짧고 명확해 보입니다. 그리고, (3)이 (2)보다 좀더 복잡하고 까다로와 보입니다. 하지만 성능으로 따지자면 (1) < (2) < (3) 입니다. 물론 최근의 computing power가 좋아져서, 큰 차이는 없을 수 있습니다. 하지만, query의 양이 많거나 길이가 길 경우, 혹은 record 수가 많다던지, 여하튼 db의 크기가 커질 수 록 그 성능의 차이는 눈으로 드러날 것입니다.

(2)과 (3)의 차이는 sqlite3_prepare(…)의 호출 빈도라 할 수 있습니다. (3)에서는 그 자리에 대신 sqlite3_reset(…)이 들어가 있습니다. 해당 코드를 보면 sqlite3_reset이 훨씬 부하가 적어 보입니다(단순히 memory free 위주로 되어 있음). 물론 sqlite3_prepare는 btree 호출도 있고 구문 parsing도 있습니다. 위 예에서는 누락되어 있지만, sqlite3_reset의 리턴값 체크는 해주는 것이 중요합니다.

vc++ 코드 실행 시간 측정 방법 정리

http://tiger5net.egloos.com/5126729

이번에 알아보는 것은 Visual C++에서 시간을 측정하는 방법입니다. Visual C++에서 시간을 측정하는 방법은 여러 가지가 있는데 . ANSI C의 함수를 이용하는 방법부터 윈도우 API 함수를 이용하는 방법까지 다양합니다. 그리고, 각각의 방법을 사용하기 위해서 함수에 따라 적절히 헤더를 Include 해줘야 하기도 합니다.

다음은 DEVPIA와 MSDN 을 통해서 찾은 여러 가지 시간 측정하는 방법입니다. 여러 님들이 올리신 글들을 제가 한번 정리해 본 것이구요, 소스를 직접 테스트 해보기도 하고 수정을 하기도 했습니다. 그리고 각각의 방법들은 형태는 다르지만 모두 공통점이 있습니다. 바로 실행 시간을 측정하기 위해서 원하는 루틴의 시작점과 종료점에서 시간 측정과 관련하는 함수를 부른다는 겁니다. 그래서 그 루틴이 시작할 당시에 비해 CPU 클럭이 얼마나 지났는지를 따져서, 즉 그 시작점과 종료점의 클럭의 차이를 이용해서 실행 시간을 구합니다.

간단한 설명은 여기서 마치고, 직접 한번 살펴보세요. 그리고, 직접 한 번 돌려보시길 바랍니다.
눈으로만 읽어선 절대로 자기의 것이 될 수 없으니까요..^^;

순서..
1. clock() 함수 이용 실행시간 측정.
2. timeGetTime()함수 이용 실행시간 측정.
3. QueryPerformanceCounter()함수 이용 실행시간 측정.
4. 매크로를 이용한 실행시간 측정. [밀리세컨트(ms)단위]
5. Debugging 중에 @CLK를 이용한 실행시간 측정.
6. 프로파일 기능을 이용한 각 함수들의 실행시간 알아보기.

——————————————————————–
1. clock() 함수 이용 실행시간 측정. [초(s)단위]
——————————————————————–

어떤 작업을 수행하는데 걸리는 시간을 알고 싶은 경우는 흔하지만, 방법을 잘 몰라서 그냥 넘어가는 경우가 많습니다. 아래 소개된 방법이 가장 일반적인 방법이며 이보다 나은 방법은 아마 없다고 생각합니다. 그다지 어렵지 않으니 천천히 보세요.

컴퓨터에는 Clock Ticks라는 것이 있습니다. 그리고 운영체제에는 어떤 프로세스가 시작한지 얼마나 지났는지 알려주는 clock() 함수를 가지고 있구요. 이것을 이용하는 방법입니다.

clock_t current_tick = clock();

이 코드로 현재 프로세스가 실행한지 얼마나 지났는지를 Clock Tick단위로 알 수 있습니다. 시간을 알고 싶으시면 이 Clock Tick단위를 초단위로 변환해주면 되죠.

double current_sec = current_tick / CLOCKS_PER_SEC;

이 코드가 Tick 단위를 초단위로 환산해주는 코드입니다.
CLOCKS_PER_SEC은 time.h화일에 정의되어 있는 상수입니다.

간단히 예제를 만들어 보면,

#include “stdio.h”
#include “time.h”

void main()
{
clock_t before;
double  result;
before  = clock();

for ( int i = 0; i <32765; i++ ) printf(“%d\n”, i );
result = (double)(clock() – before) / CLOCKS_PER_SEC;

printf(“걸린시간은 %5.2f 입니다.\n”, result);
}

간단하게 C 스타일로 예를 들었지만, MFC환경에서도 잘 돌아갑니다.

— 김현승(Puzzle)님이 올려주신 글입니다.

——————————————————————–
2. timeGetTime() 함수 이용. [밀리세컨드(ms)단위]
——————————————————————–

제가 즐겨 쓰는 방법은 multimedia timer를 사용하는 겁니다.
함수는 timeGetTime() 이구요, 밀리세컨드(ms)단위로 측정가능합니다.
방법은 아주 간단합니다.

DWORD dwStartTime = timeGetTime();

// 처리부분

// 처리부분 종료

DWORD dwEndTime = timeGetTime();
printf(“%d ms”, dwEndTime-dwStartTime);

필요한 부분에 추가하시고 쓰면 됩니다.

단, 사용하기 위해서는 사용하는 파일에 Mmsystem.h 를 Include 해주고,winmm.lib 를 Project=>Setting(ALT+F7) 메뉴의 LINK 텝에서 Object/library modules: 에 추가를 해주어야 합니다..

— DEVPIA에 올리신 김영훈(zenk)님의 글입니다.

—————————————————————————–
3. QueryPerformanceCounter() 함수 이용 [ ms단위]
—————————————————————————–

다음은 QueryPerformanceFrequency() 와 QueryPerformanceCounter()를 이용한 방법입니다. 밀리 세컨드 단위로 측정 가능하구요, #include <Windows.h> 를 해주어야 합니다. 한가지 단점은 시스템에 따라 얼마나 작은 시간을 잴 수 있는지가 제한된다는 것입니다. 하지만 CPU의 속도에 의해 결정되는 것은 아니므로 조금 느린 시스템이라고 해서 걱정하실 필요는 없습니다. API 의 도움말을 보면 시스템에 따라 CPU 클럭의 레벨까지도 체크가 가능하다는군요. 사용하는 API 함수는 High Performance Timer Functions라고 불리는 QueryPerformanceFrequency() 와 QueryPerformanceCounter() 라는 두 개의 함수입니다.

앞의 놈은 자기 시스템이 최대 어느 정도까지의 timer resolution을 지원하는지를 판별하는데 쓰이구요, 뒤엣 놈은 현재의 카운터를 알아내는 데 사용됩니다. 예를 들어 앞의 함수를 콜한 후 넘겨준 파라미터 값이 10000 으로 되어 있다면 그 시스템은 10000분의 1초 (=> 1/10000) 즉 0.1밀리초까지 판별할 수 있습니다.

그리고 어떤 작업을 수행하기전에 QueryPerformanceCounter를 한번실행하고 수행후 다시 콜함으로써 각각 얻어진 카운터값의 차이를 구함으로써 수행시간을 판단할 수 있습니다

//멤버변수나 로컬변수로 변수지정

LARGE_INTEGER Frequency;
LARGE_INTEGER BeginTime;
LARGE_INTEGER Endtime;

//프로그램이나 클래스 시작부분에
QueryPerformanceFrequency( &Frequency );
//사용하고자 하는 부분에 다음 코딩
QueryPerformanceCounter( &BeginTime );

처리함수();

QueryPerformanceCounter( &endtime );
int64 elapsed = Endtime.QuadPart- BeginTime.QuadPart;
double duringtime = (double)elapsed / (double)Frequency.QuadPart;

//다음은 변수를 달리 했을 때의 사용법입니다.

__int64 Frequency;
__int64 BeginTime;
__int64 Endtime;

//프로그램이나 클래스 시작부분에
QueryPerformanceFrequency((LARGE_INTEGER *) &Frequency );

//사용하고자 하는 부분에 다음 코딩
QueryPerformanceCounter((LARGE_INTEGER *) &BeginTime );

처리함수();

QueryPerformanceCounter((LARGE_INTEGER *) &endtime );
int64 elapsed = Endtime – BeginTime;
double duringtime = (double)elapsed / (double)Frequency.QuadPart;

— DEVPIA에 올리신 오희백(ohpwin)님의 글을 수정한 것입니다.

————————————————————————-
4. 매크로를 이용한 실행시간 측정. [밀리세컨드(ms)단위]
————————————————————————-

//다음 두줄을 복사해서 쓰시면 됩니다.
// 이 매크로는 Win32 함수를 이용하여 nano second까지 측정할 수 있습니다.

#define CHECK_TIME_START  __int64 freq, start, end; if (QueryPerformanceFrequency((_LARGE_INTEGER*)&freq))  {QueryPerformanceCounter((_LARGE_INTEGER*)&start);

// a는 float type  milli second이고 b가 FALSE일때는 에러입니다

#define CHECK_TIME_END(a,b) QueryPerformanceCounter((_LARGE_INTEGER*)&end);  a=(float)((double)(end – start)/freq*1000); b=TRUE;                        } else b=FALSE;

위의 내용을 조금 바꾸면 나노까지 측정 가능하죠.. 우선은 mili가지 측정됩니다.
그리고 windows.h포함해야 합니다. 매크로로 만들어 놔서 조금은 편하답니다.
이용하는 구체적인 방법은 다음 코드를 참조하세요.

#include <windows.h>
#include <stdio.h>

#define CHECK_TIME_START  __int64 freq, start, end; if (QueryPerformanceFrequency((_LARGE_INTEGER*)&freq))  {QueryPerformanceCounter((_LARGE_INTEGER*)&start);

// a는 float type  milli second이고 b가 FALSE일때는 에러입니다
#define CHECK_TIME_END(a,b) QueryPerformanceCounter((_LARGE_INTEGER*)&end);  a=(float)((double)(end – start)/freq*1000); b=TRUE;                        } else b=FALSE;

void main()
{
float Time;
BOOL err;

CHECK_TIME_START;

//….측정하고자 하는 구간..예를들어..printf()같은거
printf(“나는 바보 입니다.”);

CHECK_TIME_END(Time, err);

if(err) printf(“printf() 걸린 시간은.. %8.6f미리세컨입니다.”,Time);
}

— DEVPIA에 올리신 김태연(MonoEye)님의 글을 수정한 것입니다.

——————————————————————–
5. Debugging 중에 @CLK를 이용한 실행시간 측정.
——————————————————————–

다음은 실행시간을 측정하는 간단한 Debugging Technic 입니다.

프로그램을 하다 보면, 특정한 루틴이 과연 얼마나 많은 시간을 차지하는지 알고 싶은 경우가 있습니다. 물론, 많은 경우는 아니지만, 특정한 알고리즘을 만든 경우.. 다른 알고리즘과 처리 시간을 비교해 보고 싶은 경우가 있죠. 이럴 때 시스템 시간을 구해서 비교하는 방법들을 많이 사용하는데요,

실제로는 그렇게 할 필요가 없죠.. Watch Window 에서 앞에 골뱅이(@) 붙여서 사용하는 레지스트리 값들있죠? 골뱅이 시리즈 중에 하나를 사용하시면 된답니다.

@CLK

(CLK == Clock)

위에 있는 @CLK는 말이죠. 현재 프로세스의 Clocking을 나타내줍니다. 그런데 위의 값을 사용자가 임의로 초기화 할 수 있기 때문에, 유용하게 사용할 수가 있는 것이랍니다.

자.. 예를 들어서..
A 라는 루틴이 있는데, A 가 과연 얼마나 시간이 걸리는지 알고 싶습니다.
그러면 어떻게 @CLK를 사용해야 할까요?

우선 A 에 들어가기 전에 Break Pointer를 걸어두고, @CLK를 초기화 하시면 됩니다.
그리고 A 가 끝나는 부분에 다시 Break Pointer를 걸어두시면 바뀐 @CLK 값을 확인해 보실 수 있겠죠. @CLK 값은 밀리세컨드 단위니까. @CLK/1000,d 라고 하시면 초 단위로 보실 수 있습니다.

그럼 간단한 예제를 통해서 실제 사용방법을 살펴 보고 , 마무리 하겠습니다.

#include “stdafx.h”

int main(int argc, char* argv[])
{
// Break Pointer 설치하는 방법
// Alt+F9 를 눌러서 Break at 란에.. 아래와 같이..
// {,”ClockTest.cpp”,} .23
// {,”ClockTest.cpp”,} .39
// 입력합니다.
// 첫번째 Break pointer
// 이 부분에서 ‘User Breakpointer’ 가 call 되면
// Watch Window 에 아래의 값을 넣습니다.
// —-
// @CLK
// @CLK=0
// —-
// 위에서 @CLK 는 시스템 Clocking 을 사용하겠다는 말이고,
// @CLK=0 는 스스템 Clocking 을 0으로 초기화 하겠다는 말입니다.
// 이제 이 상태에서 다시 Go 버튼을 눌러서 프로그램을 실행하면..
// 두번째 User Breakpointer 가 call 됩니다.

int i, j, k;
for (i = 0 ; i <1000 ; i++)
for (j = 0 ; j <1000 ; j++)
for (k = 0 ; k <1000 ; k++)
;

// 두번째 Break Pointer
// 이 부분에서 ‘User Breakpointer’ 가 call 되면 Watch Window 에 있는
// 첫번째 @CLK 값이 초과된 시간을 보여줍니다..
// 물론 이 값에는 Debugging 때문에 여러가지 추가 작업들이
// 행해져서 정확한 프로그램 실행시간이 아니지만, 여러가지
//작업을 상대적으로 비교할 수는 있겠죠..

return 0;
}

— DEVPIA에 올리신 서우석(seaousak)님의 글입니다.

——————————————————————————
6. 프로파일 기능을 이용한 각 함수들의 실행시간 알아보기
——————————————————————————

프로그램을 짜면서 각 함수가 어느 정도의 시간이 걸리는 지 알아보려는 노력들을 많이 합니다. 실제 실행시간일 수도 있고 전체 프로그램에서 어느 정도의 비율로 작용하는 지 알고싶을 때도 있구요…

이 때 프로그램의 각 함수별로 실행시간과 전체에서의 비율, 호출 횟수등을 알 수 있는 방법이 있습니다. 이것은 Debug모드에서만 가능합니다…

1.Project – Settings 메뉴를 선택한다

2. Link 탭으로 간다. ‘Enable profiling’을 선택한다.

3.그러고나면 Build 메뉴의 ‘Profile…’ 메뉴가 활성화가 된다.

4.  반드시 Rebuild 시킨후에 ‘Profile…’ 메뉴를 선택한다. 만일 그렇지 않으면 다음과 같은 메시지박스가 뜬다

5. Rebuild 시킨후 ‘Profile…’ 메뉴를 선택하면 다음과 같은 다이알로그가 뜬다

6. 여기서 ‘Function timing’을 선택하고 ‘OK’를 누르면 프로그램이 실행이 될 것이다.

7. 실행이 끝나고 나면 Output 창에 결과를 보여줄 것이다. 함수별 실행시간과 불려진 횟수 등을 확인할 수 있다.

이것을 이용하면 프로그램을 짠 후 오랜 시간이 걸리거나 짧으면서도 많이 호출되는 함수를 찾아서 프로그램을 개선시킬 수가 있다. 단, 실행되는 것이 디버그모드이고 이 때는 일반 디버그모드보다 시간이 더 걸린다는 것을 명심해야 합니다. 이 외에도 프로파일에는 여러 가지 기능이 있으니 사용해 보시기 바랍니다.

– DEVPIA에 올리신 이광진(mirjini)님의 글입니다

Win-base source

WinMain+Singleton(Main)

#pragma once

class CWndEx : public CSingleton< CWndEx >
{
private:
	// ENUM

	// PRE
	friend class CSingleton;

	// MEMBER
	HWND			m_hWnd;
	HINSTANCE		m_hInstance;

	LPWSTR			m_lpszTitle;

	CWndEx();
	~CWndEx();

	//VOID Clear();
	VOID Create();
	VOID Destory();
	VOID Render();
	VOID Update();

	// Method
	ATOM RegisterClass( WNDPROC, LPTSTR, UINT );
	BOOL InitInstance( INT );

	static LRESULT CALLBACK FramWndProc( HWND, UINT, WPARAM, LPARAM );

	VOID CreateStatusWindowEx( HWND );
	VOID SetStatusRGB();

	// MESSAGE
		// Frame MESSAGE
		VOID OnCommand( HWND, INT, HWND, UINT );
		BOOL OnCreate( HWND, LPCREATESTRUCT );
		VOID OnDestroy( HWND );

public:
	static INT MessageLoop( HINSTANCE = NULL );
};