[카테고리:] 수정중

  • 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)

     

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

     

    한동안 위와 같은 경우를 겪어보지 않아. 적잖히 당황을 했지만, 이내 몇 년전 구글의 C 포럼의 쓰레드에서 사이즈가 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)님의 글입니다