yjh_common.source.utility.test_debugging 참고


CRT(C Runtime) Debugger사용방법


일반적으로 가장 잡기 힘든 버그의 하나로서 메모리 누수, 메모리 Overwrite등을 꼽을 수 있다. 이런 문제점을 해결하기 위해 CRT(C Runtime library)에서는 여러가지 다양한 메모리 관련 디버그 함수들을 제공한다. 그러나 이것들이 디폴트로 사용하기 힘들게 꺼져 있기 때문에 대부분의 프로그래머들은 이 사실을 알지 못하는 경우가 많다. 그래서 이 글에서는 CRT의 디버그 관련 함수들에 대해 알아보고 어떻게 사용하는 것이 좋은지에 대해 논해 보려고 한다.


John Robbins(필자가 가장 좋아하는 프로그래머 중의 한명)가 지은 Debugging Applications 이라는 책에도 좋은 내용들이 있으니 참고하기 바란다. 그러나 여기 나온 팁은 그 책에는 나와 있지 않은 것이다. Numega Bounds Checker나 Rational Purify등의 툴이 비싸서 엄두도 못내는 분들께는 좋은 내용이 되리라 믿는다


메모리 누수


개요


CRT가 출력해 주는 메모리 누수 리포트 결과를 간혹 본적이 있는 사람도 있을 것이다. 만약 MFC를 주로 사용한다면 이와 같은 메시지를 자주 볼 수 있다. 왜냐하면 MFC에서는 기본적으로 CRT 디버그 기능을 켜기 때문이다. 메시지 내용은 다음과 같다


Dumping objects ->

{65} normal block at 0x003748B0, 48 bytes long.

Data: <@H7 @H7 @H7 > 40 48 37 00 40 48 37 00 40 48 37 00 CD CD CD CD

{64} normal block at 0×00374840, 48 bytes long.

Data: < H7 H7 H7 > B0 48 37 00 B0 48 37 00 B0 48 37 00 CD CD CD CD

Object dump complete.


이 메시지의 내용은 두개의 블록이 할당된다음 해제되지 않았다는 것을 의미한다. 그렇다면 우선 이 메시지들에 대해 대략 알아보도록 하자


우선 {64}, {65}와 같은 것은 메모리에 할당된 순서로서, 각각 64번째, 65번째 할당된 메모리가 해제되지 않았음을 나타낸다. Normal block이라 함은 사용자에 의해 일반적인 new, delete, malloc등으로 할당된 메모리라는 뜻이다. 그리고 그 메모리 포인터 주소를 알려주고 있으며, 몇 바이트짜리 메모리 블록인지도 나와 있다


Data:… 라인은 그 메모리의 선두번지로부터 실제 메모리의 내용이 어떤 값을 포함하고 있는지를 나타내는 것이다


그러나 위의 메시지를 보았듯이 실제로는 아무 도움이 되지 않는다는 것을 금방 알수 있을것이다. 왜냐하면 메시지의 내용이 너무 암호와같이 복잡하다는데 문제가 있다. 몇줄짜리 프로그램이라면 또 모르겠으나, 거대한 프로그램인 경우 실제로 64,65번째 할당된 메모리를 순서대로 추적하는 것은 너무나도 어려우며, 어떤 포인터에 어느 번지의 메모리가 할당되었는지를 확인하는 것도 불가능에 가깝다고 할 수 있다. 또 자신이 만든 클래스나 구조체가 몇 바이트짜리인지를 일일이 확인한다는 것도 불가능하다.


그렇다면, 이 문제를 어떻게 해결해야 할 것인가? 실제로 CRT에는 여러가지 도우미 함수들을 포함하고 있어서 이 문제를 좀 더 쉽게 해결할 수 있도록 해 준다. 그러나 제대로 알고 사용하지 않는다면 결국에는 또다른 암호 코드만을 추가로 더 얻게 될 뿐이라는 것도 알아야 한다


샘플코드


#include “stdafx.h”

int main()

{

int *p = new int;

return 0;

}


위의 코드는 int *를 할당하고 해제하지 않았으므로 명백한 메모리 누수이다. 그러나 이 프로그램을 빌드하고 디버그해봐도 프로그램 종료시 아무런 메시지를 남기지 않는다. 즉 메모리가 샌것인지, 혹은 Overwrite가 일어났는지 등을 확인할 길이 전혀 없다. 그러면 프로그램 일부를 수정해 보도록 한다


#include “stdafx.h”

#include <crtdbg.h>

int main()

{

int *p = new int;

_CrtMemDumpAllObjectsSince(0);

return 0;

}


_CrtMemBumpAllObjectsSince를 사용하기 위해 crtdbg.h를 인클루드 하고, 프로그램 종료 직전에 함수를 호출했다 디버그를 시작하고 프로그램을 종료하면 출력결과는 다음과 같다


Dumping objects ->

{64} normal block at 0×00374838, 4 bytes long.

Data: < > CD CD CD CD

Object dump complete.

‘[2288] crtdbg.exe: 기본’ 프로그램이 0 (0×0) 코드에서 끝났습니다.


위에서 설명한것과 비슷한 종류의 메시지가 포함되어 있는 것을 알 수 있을 것이다. 이 메모리는 64번째 할당된 일반 메모리이며 0×00374838번지에 4바이트 할당되었음을 알 수 있다. 또 데이터 내용은 16진수로 CD CD CD CD이다 이 정보만으로도 많은 것을 알 수 있다. 예를들어 데이터가 CD CD CD CD라는 것은 할당만 해놓고 전혀 초기화를 하지 않았다는 의미이다. 단순한 위의 프로그램 만으로도 사용자가 처음 할당한 메모리가 64번째만에 할당되었다. 이유가 무엇일까? 이유는 간단하다. main함수가 호출되기 이전에 이미 많은 메모리 할당 요청이 있었고, 그것은 프로그램을 실행시키기 위해 운영체제나, CRT가 이미 사용했기 때문이다. 위의 프로그램은 단순하기 때문에 어디서 메모리가 샜는지 한눈에 척 알 수 있다. 그러나 메모리를 수십~수백번씩 할당했다 해제하는 일반 애플리케이션에서는 어떻게 정확히 64번째 할당된 메모리를 찾아낼 수 있을까? CRT에서 내준 정보는 CRT를 이용해 분석 가능하다 main함수가 처음 시작되기 전에 다음의 함수를 사용하도록 하자


_CrtSetBreakAlloc(64);

int *p = new int;


이 함수는 64번째 메모리 할당이 발생할 경우 프로그램 실행을 중지하고 디버거로 제어를 넘기라는 의미이다. 실제로 프로그램을 실행시켜 보면, “crtdbg.exe의 0x00411cb7에 처리되지 않은 예외가 있습니다. 사용자 중단점”과 같은 메시지를 출력하면서 프로그램 실행을 중지하게 된다. 브레이크 포인트가 가리키는 위치는 CRT의 메모리 할당 함수 내부이며, Call Stack을 따라가 보면 어느곳에서 할당이 일어났는지 바로 알수 있게된다.


전역 변수와 CRT 디버깅


다음과 같은 프로그램을 보도록 하자


#include “stdafx.h”

#include “crtdbg.h”

#include <crtdbg.h>

class A

{

public:

A() { p = new int; }

private:

int *p;

};

A a;

int APIENTRY _tWinMain(HINSTANCE hInstance,

HINSTANCE hPrevInstance,

LPTSTR lpCmdLine,

int nCmdShow)

{

_CrtSetBreakAlloc(64);

_CrtMemDumpAllObjectsSince(0);

return 0;

}


이 프로그램을 디버그 해 보면 똑 같은 메모리 누수를 보고하긴 하지만, 그러나 64번째 메모리 할당에서 정지하지 않는다. 이유가 무엇일까?


이유는 간단하다. A라는 클래스의 인스턴스인 a가 전역변수로 선언되었기 때문에 main함수가 호출되기 이전에 생성되기 때문이라는 것이다. a가 이미 생성되고 난 다음에 브레이크 포인트를 설정하니, 브레이크 포인트가 먹힐리가 없다. 그럼 방법이 없는 것일까?


답은 간단하다. 모든 할당이 일어나기 직전에 프로그램을 정지시켜놓고, 64번째 메모리 할당이 일어날 때 브레이크 하라는 명령을 주면 된다. 그럼 어떻게 하면 될까? 다음과 같이 하도록 한다.


1. CRT소스에서 WinMainCRTStartup()함수를 찾아낸다. 이 함수는 실질적인 main함수이며, 프로그램이 로드되기 전에 가장먼저 실행된다. 이 함수 내부에서 여러분이 정의한 main 또는 WinMain함수를 호출하게 된다. 이 함수는 파일의 찾기 기능을 이용하거나, 또는 crt0.c파일을 바로 열어서 찾아도 된다. 그러나 더 간단한 방법은 main함수에 BP를 찍어놓고, 한번 실행시킨다음 call stack을 거슬러 올라가는 방법이다.

2. WinMinCRTStartup함수의 시작부분에 BP를 찍어놓고 다시 디버거를 시작시킨다

3. Watch창을 열어 _crtBreakAlloc 변수를 확인해 본다. 아마 -1일 것이다.

4. 이 변수값을 원하는 메모리 할당 번지(위의 경우64)로 바꾼다.

5. 다시 실행시키면 64번째 메모리 할당을 하기 전에 정지한다.


이 기술은 코드를 재 컴파일 하지 않아도 디버거 상에서 바로 브레이크 포인트를 수정할 수 있다는 장점이 있다. 현재 이 방법 보다 간단하게 할 수 있는 방법은 현재 연구중에 있다. 좋은 결과가 나오는대로 다시 여러분들에게 알려드리도록 하겠다.


이상 몇가지 메모리 누수를 찾아내는 방법을 살펴보았다. 그러나 주의할 것은 반드시 crtdbg.h를 같이 인클루드 해야 한다는 것이며 _DEBUG매크로가 정의되어 있을때에만 제대로 동작한다는 것이다. 오늘은 이 정도로 글을 마무리할 생각이며 남은 몇 개의 팁들은 필자 시간나는대로(-_-) 정리해서 올릴 계획이다. 그러므로 너무 기다리지 않는 것이 정신 건강상 좋을 것이다. 참고로 필자는 매우 게으르다 -.-


요즘 CRT의 디버그 기능을 연구하기 시작하면서, 그동안 정말 좋은 기능들을 여럿 묵혀놓았다는 느낌을 지울수가 없습니다. 어렵게 메모리 관련 디버깅 루틴을 만들지 않아도, 너무나도 정확히 메모리 관련 에러를 잡아주니 STL을 처음 쓸 때 만큼이나 편리하게 느껴지더군요. 그럼 그동안 제가 연구한 것에 대해 보고드리도록 하겠습니다


지난 내용의 메모리 누수가 아닌 것 까지 모두 보고하는 문제


지난번 예제에서 약간만 더 수정해 보자


#include “stdafx.h”

#include <crtdbg.h>

#include <list>

using std::list;

typedef list<int> IntList;

typedef IntList::iterator IntListIter;

IntList myList;

int main()

{

_CrtMemDumpAllObjectsSince(0);

return 0;

}


위의 프로그램은 메모리 누수를 한 개 보고한다. 위치는 myList의 생성자이다. 그러나 정말 그것이 샌것일까? 그렇다면 STL은 항상 메모리 누수가 있다는 말인가.. 이것저것 고민하던 중에, 진짜 Main함수는 WinMainCRTStartup()라는 사실이 생각났고, 디버그 상에서 WinMainCRTStartup() 메소드의 끝까지 따라가 보았다. 그랬더니 다음과 같은 루틴을 찾을 수 있었다. (처음에는 숨겨진 함수를 찾았다고 생각했으나, 알고봤더니 MSDN에 이미 문서화 되어있는 함수였다. 역시 소스보다는 문서를 찾아보는 것이 우선이다 -.-)


/* Dump all memory leaks */

if (!fExit && _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & _CRTDBG_LEAK_CHECK_DF)

{

fExit = 1;

_CrtDumpMemoryLeaks();

}


_CrtSetDbgFlag이라는 함수는 CRT디버거의 전역 플랙을 셋팅하는 함수이다. 위에서 보다 시피 이미 CRT의 메인함수가 종료할 때 메모리 누수를 검사하고 있었던 것이다. 다만 디폴트로 꺼져 있었으며 열쇠는 _CrtSetDbgFlag이라는 함수가 쥐고 있다. MSDN에서 찾아본 결과 다음과 같다.


_CrtSetDbgFlag함수는 다섯개의 Flag이 있다.

_CRTDBG_ALLOC_MEM_DF : 디폴트로 켜져 있으며, 디버그 버전에서 모든 메모리 할당이 일어날 때마다 추적 가능하도록 특별한 기능을 추가해 둔다. 이 플랙이 켜져 있어야 메모리 누수를 안전하게 검사 할 수 있다.

_CRTDBG_DELAY_FREE_MEM_DF : delete, free등으로 삭제되어도 바로 삭제되지 않고, CRT의 메모리 관리소에 남아 있다가 프로그램 종료시에 완전히 삭제된다.

_CRTDBG_CHECK_ALWAYS_DF : 모든 메모리관련 연산에서 _CrtCheckMemory를 호출한다. 이 메소드는 이후에 다시 살펴볼 것임

_CRTDBG_CHECK_CRT_DF : CRT가 내부적으로 할당한 블록도 메모리를 체크할 때 포함한다. 일반적으로는 CRT가 할당한 블록은 메모리 체크에서 제외된다. 일반적으로 사용하지 않는다

_CRTDBG_LEAK_CHECK_DF : 프로그램이 완전히 종료되기 직전에 아직 해제되지 않은 메모리가 있는지 검사한다. 프로그램의 종료 포인트가 여러군데 있는 경우에 사용하면 일일이 _CrtDumpMemoryLeaks 메소드를 호출하지 않아도 자동적으로 메모리 누수를 검사할 수 있게된다.


이 중에서 첫번째 플랙만을 제외하고는 모두 디폴트로 꺼져있다. 그러므로 다음과 같이 메모리 검사 기능을 켜도록 한다

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

위의 프로그램에서는 다음을 삭제한다 _CrtMemDumpAllObjectsSince(0);

_CrtMemDumpAllObjectsSince 함수는 실제로는 특정 지점에서 지점간에 할당되어 있는 메모리들을 보고해 주는 함수이다. 인자로 0을 넘겨주면 처음부터 지금까지 할당되어 있는 메모리들을 보고해 준다. CRT가 아직 전역으로 할당된 메모리를 완전히 삭제하기도 전에 호출했기 때문에 STL의 메모리가 샌 것 처럼 보인것이다.


20141120 연준희 추가

이렇게 해도 계속 샌 것 처럼 보이는 경우가 있다.

Solution:
The solution is to ignore what is (likely) a false report, or change the static std::string objects to static std::string* pointers. Then make a static initializer/destructor that news/deletes those string objects. This means the memory is allocated on the stack, and comes/goes in the scope of the program. The other thing you do is file a bug with Microsoft and put a few comments on the documentation website that indicates the statics are still leaking and they haven’t really fixed it yet. :)

Links:
Bug report I submitted with response here 
<https://connect.microsoft.com/VisualStudio/feedback/details/697181/crt-memory-leak-detection-routines-still-report-statically-allocated-stl-objects-as-leaks#tabs>

MS article about CRT memory debug operations that says this bug had been fixed.
http://msdn.microsoft.com/en-us/library/x98tx3cf.aspx

출처: http://mattfife.com/?p=481


CRT 메모리 블럭


다음으로 넘어가기 전에 다음을 짚어보고 넘어가기로 하자. 디버그 버전에서는 메모리가 할당되거나 사용되기 직전에 특정한 값들로 할당된 메모리가 채워진다는 것을 알고 계실것이다. 의미는 다음과 같다


0xFD : 메모리 블록 양 끝의 버퍼에 생성된다

0xCD : 새로 생성된 버퍼에 저장되는 기본값이다

0xCC : 스택에 변수가 생성되면 우선 이값으로 채워진다

0xDD : 삭제된 메모리 블록은 이 값으로 채워진다


다음 예제를 보자


int main()

{ // 함수 시작지점

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

int *p = new int [10]; // 메모리가 할당되는 지점

printf(“%d”, *p);

delete [] p; // 메모리가 삭제되는 지점

return 0;

}


함수 시작지점에서 포인터 p는 초기값으로 0xCCCCCCCC를 갖게 된다. 디스어셈블 해보면 모든 지역변수들을 0xCC로 채워넣는 것을 볼 수 있다. 이후 메모리가 할당되는 지점에서 아마도 sizeof(int) * 10 바이트의 메모리가 할당될것이다. Sizeof(int)가 4바이트라면(32비트 운영체제에서) 40바이트가 생성되는 것이다.(디버그 버전이라는 가정하에) 이렇게 생성된 40바이트에 0xCD값이 채워진다. 만약 어떤 포인터의 값을 읽었을 때 값이 0xCDCDCDCD와 같은 형식이거든 초기화안된 메모리를 읽었다고 생각하면 된다.


그런데, 위의 할당지점에서 실은 48바이트가 생성된다. 40바이트 할당 명령을 주었는데 왜 48바이트의 메모리가 할당되었는가 이유는 위의 0xFD값에 있다. 40바이트의 블록을 생성시키고 그 블록의 앞과 뒤에 각각 0xFDFDFDFD를 삽입시켰기 때문이다. 이 값이 채워지는 이유는 다음과 같이 접근하는 경우


p[-1] = 10; // underwrite

p[11] = 10; // overwrite


0xFD로 채워진 메모리의 일부분이 깨질 것이고, 나중에 사용할 메모리 체크 명령에 의해 overwrite/underwrite가 발생했다는 사실을 알 수 있게된다.


마지막으로 메모리가 삭제되는 지점에서는 이론 대로라면 p값이 0xDD로 채워질 것이다. 그러나 실제로 필자의 컴퓨터에서는 0xFEEE가 채워졌다. 왜 그런지는 좀더 연구해 보고 알려드리도록 하겠다. -.- 오늘은 일단 여기까지 접고 연구결과가 더 나오는대로 여러분께 보고하는 시간을 갖도록 하려한다.. 그럼 다들 즐푸하시길..


지난번에 이어 이번에는 메모리에 어떤 문제가 있었는지를 체크해주는 _CrtCheckMemory함수에 대해 연구해 보도록 하겠습니다.

지난 강좌에 대해

강좌를 진행하기 전에 지난번 강좌 마지막 부분에서 메모리가 삭제될 때 0xDD값 대신에 0xFEEE값이 채워지는 이유를 찾아본 결과 CRT에서는 0xDD값을 정확히 채워 넣는다는 것을 확인하였다. _CRTDBG_DELAY_FREE_MEM_DF 플랙이 디폴트로 꺼져 있기 때문에 삭제했다는 표시를 하고 난 다음 바로 운영체제에서 삭제(진짜 삭제) 해버렸기 때문이다. 이부분은 뒤에서 다시 알아보도록 하겠다.

_CrtCheckMemory()

지난번에 알아보기를 CRT에서 메모리를 할당하려 할 때 몇가지 정보블럭들을 설정한다는 것을 알았을것이다. 자 이제는 설정된 정보블럭들을 검사할 차례이다. 문제는 일일이 디버거의 Watch창이나 Memory창을 이용해 블록이나 스택이 깨졌는지를 확인해야 한다는 것이다. 이런 기법은 아마도 잘 알고 있을 것이다. 특정 메모리 주소를 가르키게 해놓고 의심되는 코드를 실행시켰을 때 Memory창의 내용이 빨간색으로 변하는 모양을 살펴서, 엉뚱한 부분까지 쓰거나, 원치않은 결과를 얻는지를 확인하는 것이다. 문제는 이것이 너무나도 수동적이기 때문에 CRT에서는 _CrtCheckMemory라는 도우미 함수를 곁들였다. 이 함수는 몇가지 경우에 있어서는 아주 쓸만하다. 사용법을 보자

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

{

if(_CrtCheckMemory() == 0)

return -1;

return 0;

}

그냥 메모리 체크를 하기 원하는 위치에 _CrtCheckMemory함수를 삽입하기만 하면된다. 만약 0을 리턴한다면 할당된 메모리에 어떤 문제가 발생했다는 것을 의미한다. 그러나 위의 코드는 문제가 하나 있는데 바로 모든 CRT의 디버그 함수들은 DEBUG버전에서만 의미를 가지기 때문에 RELEASE버전에서는 아무 의미없는 코드가 된다는 것이다. 일단 보기에도 두 줄에 걸쳐 표기되어 있으므로 흉하다. 그러므로 다음과 같이 코딩하도록 한다.

_ASSERTE( _CrtCheckMemory( ) );

_ASSERTE는 CRT에서 제공해주는 매크로이다. 또는 assert.h의 assert함수를 이용해도 좋다. MFC등을 사용한다면 ASSERT등을 사용해도 좋고 Robbins의 SUPER_ASSERT를 사용해도 좋다. 각각 약간씩의 차이점이 있기는 결과는 거의 같다. 그러니 여기서는 CRT를 사용한다는 일관성을 유지하기 위해 _ASSERTE를 사용하도록 하겠다.

단순히 위와같이 의심갈때마다 호출해 주기만 한다면, CRT는 지금까지 등록된 모든 할당된 메모리들을 검사해 문제가 발생했는지를 확인한다. 그럼 어떤 종류의 에러들을 잡아주는지 다음의 예제들을 통해 알아보도록 하자

예제1. 경계를 넘어서서 쓰는 경우

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

{

int *p = new int[10];

p[-1] = 100; // 둘다 모두 오류이다

p[10] = 100;

_ASSERTE( _CrtCheckMemory( ) );

return 0;

}


위의 예제에서는 정수 타입 10개짜리 배열을 할당하고 11번째 멤버에 쓰기를 시도하였다. 이부분에는 지난 강좌에서 알려 드렸듯이 0xfd값이 채워져 있는 영역이다. 주어진 메모리 체크 함수는 0xfd값이 있어야 할 자리에 다른 값이 있는 경우 0을 리턴한다.


예제2. 삭제된 포인터에 접근을 시도하는 경우


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

{

// _CRTDBG_DELAY_FREE_MEM_DF 플랙을 반드시 켜야된다

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_DELAY_FREE_MEM_DF);

int *p = new int[10];

delete [] p;

p[0] = 10;

_ASSERTE( _CrtCheckMemory( ) );

return 0;

}


위 예제에서는 이미 삭제된 포인터를 다시 접근하고 있다. 이것은 디폴트로 비활성화된 플랙을 사용하므로 위의 예제에서처럼 프로그램 시작전에 _CRTDBG_DELAY_FREE_MEM_DF 플랙을 켜줘야한다. 이 플랙이 켜지게 되면 CRT는 삭제 명령(free함수)이 호출되는 경우 바로 삭제처리하지 않는다. 대신 삭제 처리했다는 표시(0xdd값)만을 남겨둔다음 필요할 때 이값이 깨졌는지 검사한다. 디버그 버전이라면 이 플랙은 반드시 켜두도록 한다. 물론 메모리 부하가 약간 더 있겠지만 심각한 오류를 검출하는데는 꼭 필요한 플랙이다. 어차피 디버그 버전은 디버깅이 최고의 목표이니까..


이 두가지 기능만해도 일반적인 프로그래머들이 겪는 대부분의 메모리 문제는 해결된다. 아! 필자가 지금까지 봐온 대부분의 메모리 관련 문제들은 거의 60%이상이 초기화가 되지 않았거나 쓰레기값이 들어있는 포인터 접근 문제였다. 이문제는 위의 함수가 잡아주지 않는다. 그럼 쓰레기값이 들어있는 포인터 접근 문제는 어떻게 해결하겠는가? 아시는 분들은 다 아실것이다 0xC0000005 오류가 바로 정답이다. 또 대부분의 컴파일러의 경우 컴파일하는 도중에 이미 초기화 안된 변수를 사용했다고 여러분들에게 알려줄 것이다.


다음에는 구간과 구간 사이에서 메모리 문제를 발견하는 방법에 대해 다룰 것이다. 전체 프로그램에서 문제를 발견했다면 그 범위를 점차 좁혀나가는 것이 중요하다. 아쉽지만 필자 개인적인 사정으로 인해 조금 늦어질지도 모르겠다.


Memory Leak Check



Visual C++에서는 MFC나 C 런타임 라이브러리 내에서 메모리 누수를 검사할 수 있는 간단한 방법을 제공한다. MFC는 사용자가 특별히 작업해주지 않아도 메모리 누수를 보고해주는 기능이 포함되어 있는 반면, MFC를 사용하지 않는 프로그램들은 메모리 누수를 검사하도록 사용자가 직접 코드를 추가해주어야 한다.


간단히 절차를 말하면, 다음과 같다.


전처리 부분의 가장 위쪽, 보통 PCH 다음에 #define _CRTDBG_MAP_ALLOC를 선언하고, #include “crtdbg.h”를 포함한다.


#include “stdafx.h”
#define _CRTDBG_MAP_ALLOC
#include “crtdbg.h”


그리고, 필요한 함수는 _CrtSetDbgFlag(), _CrtDumpMemoryLeaks(), _CrtSetReportMode() 세가지이다. 먼저, _CrtSetDbgFlag()를 사용하여 기본적인 디버그 설정을 한 다음, 메모리 누수를 검사할 덤프 시점에 _CrtDumpMemoryLeaks()를 호출하는 방식이다. 콘솔 프로그램을 작성한다면, 보통 main() 함수에서 return 이전 시점이 될 것이다.


여기서, 프로그램이 종료되는 위치를 고정적으로 선택할 수 없다면, _CrtDumpMemoryLeaks()를 종료되는 부분에 모두 작성하는 대신, _CrtSetDbgFlag() 함수를 사용할 수 있다. 이 함수의 파라미터 설정에 따라 어느 시점에서 메모리 누수를 검사할 수 있는지 설정하는 것이 가능하다. 예를 들어서, C++의 소멸자를 사용한 메모리 해제는 일반적인 경우라면 메모리 누수라고 보고될 것이다. 즉, 스마트 포인터와 같은 것을 작성하는 도중이라면, 소멸자의 행동까지 추적하지 않기 때문에 메모리 누수라고 보고될 것이다. 이 경우, _CrtSetDbgFlag()의 파라미터를 적절히 설정하는 것으로 메모리 누수 검사 시점을 선택할 수 있다.


_CrtSetReportMode()는 사용해도 그만이고, 사용하지 않아도 그만이다. 중요한 것은 _CrtSetDbgFlag()와 _CrtDumpMemoryLeaks()이다.


예를 든다면, 다음과 같다.


#define _CRTDBG_MAP_ALLOC
#include “crtdbg.h”

int main()
{
    int DbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
    DbgFlag |= _CRTDBG_LEAK_CHECK_DF;
    _CrtSetDbgFlag(DbgFlag);

    char* pMem = new char(64);

    _CrtDumpMemoryLeaks();
    return 0;
}


여기서, _CrtDumpMemoryLeaks()를 프로그램의 종료 시점에서 호출하고 싶다면, DbgFlag에 _CRTDBG_ALLOC_MEM_DF 플래그를 추가로 설정해주어야 한다. 즉, 위의 코드와 같은 경우, _CrtDumpMemoryLeaks() 다음에 힙을 정상적으로 해제하는 코드가 있더라도 메모리 누수를 보고하게 된다. _CRTDBG_ALLOC_MEM_DF 플래그를 설정하면, _CrtDumpMemoryLeaks()를 호출하지 않더라도 Output 윈도우에 메모리 누수 여부를 알려줄 것이다.


MSDN Document : http://support.microsoft.com/kb/601929/ko,
http://msdn.microsoft.com/en-us/library/5at7yxcs(VS.71).aspx

http://enddl22.net/wordpress/?p=2106

'library > CRT' 카테고리의 다른 글

컴파일 옵션  (0) 2013.02.22
메모리 누수 체크  (0) 2011.10.27
errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
블로그 이미지

란마12

,

컴파일 옵션

library/CRT 2013. 2. 22. 18:28

1. 단일 스레드는 더 이상 지원하지 않음.


2. /MT, /MTd

 1) CRT 정적 연결


3. /MD, /MDd

 1) CRT 동적 연결

 2) 버전별 dll필요(예: msvcr100.dll, msvcp100.dll)


4. 정적 연결은 배포가 편리하지만 메모리/파일용량 사용이 비효율적.

    현재같이 시스템 성능이 좋은 환경에서는 배포가 편리한 정적 연결이 좋은 것같은데 이견있으신 분 댓글 부탁.


Standard C++ Headers

ALGORITHMBITSETCOMPLEXDEQUE
FSTREAMFUNCTIONALIOMANIPIOS
IOSFWDIOSTREAMISTREAMITERATOR
LIMITSLISTLOCALEMAP
MEMORYNUMERICOSTREAMQUEUE
SETSSTREAMSTACKSTDEXCEPT
STREAMBUFSTRINGSTRSTREAMTYPEINFO
UTILITYVALARRAYVECTORXIOSBASE
XLOCALEXLOCINFOXLOCMONXLOCNUM
XLOCTIMEXMEMORYXSTRINGXTREE
XUTILITYYMATH.H

You cannot mix calls to the old iostream library and the new Standard C++ Library in Visual C++ 4.2.

Compile optionLibraries linked with
/ML (VC++ .NET 2003 and earlier)LIBC.LIB, LIBCP.LIB
/MLd (VC++ .NET 2003 and earlier)LIBCD.LIB, LIBCPD.LIB
/MTLIBCMT.LIB, LIBCPMT.LIB
/MTdLIBCMTD.LIB, LIBCPMTD.LIB
/MDMSVCRT.LIB, MSVCPRT.LIB
/MDdMSVCRTD.LIB, MSVCPRTD.LIB

Summary Table for CRT DLLs Used

Import library linked withDLLs used (Visual C++ 5.0|6.0)DLLs used (Visual C++ .NET 2002|Visual C++ .NET 2003)DLLs used (Visual C++ 2005|Visual C++ 2008
MSVCRT.LIBMSVCRT.DLLMSVCR7(0|1).DLLMSVCR(80|90).DLL
MSVCRTD.LIBMSVCRTD.DLLMSVCR7(0|1)D.DLLMSVCR(80|90)D.DLL
MSVCPRT.LIBMSVCP(5|6)0.DLLMSVCP7(0|1).DLLMSVCP(80|90).DLL
MSVCPRTD.LIBMSVCP(5|6)0D.DLLMSVCP7(0|1)D.DLLMSVCP(80|90)D.DLL
MSVCIRT.LIBMSVCIRT.DLL
MSVCIRTD.LIBMSVCIRTD.DLL


http://support.microsoft.com/kb/154753/en-us

'library > CRT' 카테고리의 다른 글

CRT(C Runtime) Debugger사용방법  (0) 2014.05.14
메모리 누수 체크  (0) 2011.10.27
errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
블로그 이미지

란마12

,

메모리 누수 체크

library/CRT 2011. 10. 27. 19:49

#define _CRTDBG_MAP_ALLOC_NEW
#include <stdlib.h>
#include <crtdbg.h>

#ifdef _DEBUG
#define new new(_CLIENT_BLOCK, __FILE__, __LINE__)
#endif

void ExitHandler(){
  _CrtDumpMemoryLeaks();
}

void main(){
#ifdef _DEBUG
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
 //_crtBreakAlloc = 129;
 //_CrtSetBreakAlloc(132);
 atexit(&ExitHandler);
#endif
}

다음과 같이 누수가 보고됐다면

Detected memory leaks!
Dumping objects ->
{994} normal block at 0x00DA55A0, 8 bytes long.
 
Data: <        > CD CD CD CD CD CD CD CD
{993} normal block at 0x00DA5558, 8 bytes long.
 
Data: <        > CD CD CD CD CD CD CD CD
{992} normal block at 0x00DA54C8, 8 bytes long.
 
Data: <        > CD CD CD CD CD CD CD CD


중괄호 안의 숫자, 예를 들어 {994} 지점에서 중단점을 설정

_crtBreakAlloc = 994; 또는 _CrtSetBreakAlloc(994);


- stl변수가 전역으로 초기화 되면 릭이 발생된 것으로 표시됨.
  가능하면 전역으로 쓰지말고 함수의 정적변수로 사용.

- vld쓰다가 최신 버전으로 교체했는데 멀티스레드에서 com사용할 때 문제가 있는 듯.

- 메인 프로세스에서 참조하고 있는 dll library내의 누수는 잡지 못함.(dll내에서 초기화시 위와 같은 체크준비를 똑같이 해야 함)




'library > CRT' 카테고리의 다른 글

CRT(C Runtime) Debugger사용방법  (0) 2014.05.14
컴파일 옵션  (0) 2013.02.22
errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
블로그 이미지

란마12

,

errno

library/CRT 2011. 1. 8. 22:49

표준 C에서는 errno를 통해서 실패의 이유를 알 수 있다.

다음은 예제는 C에서의 에러 처리 기법이다.
int main()
{
        FILE* f = fopen("a.txt", "rt");

        if ( f == 0 )
        {
            printf("error : %d\n", errno);
            printf("%s\n", strerror(errno ));        // errno를 문자열로 바꿔준다.
            perror("FAIL");                                // fprintf( stderr, "FAIL : %s\n", strerror(errno)) 의미
        }
}
여기서 문제는 error가 전역변수 라는 것이다. 지금은 문제가 안되겠지만, 멀티스레드 환경에서는 이 변수를 공유하기 때문에
문제가 될 수 있다.
그래서 윈도우즈 환경에서는 에러정보를 스레드당 한개씩 갖고 있게된다.

윈도우즈 환경에서는 스레드를 만들때마다 구조체가 한개씩 생성된다. 이 구조체를 TEB(Thread Environment Block)라 한다.
즉, 스레드가 10개 생성되면 TEB도 10개가 생성되는 것이다.

이 TEB안에는 모든 스레드별로 다양한 정보가 있는데, 그중에 Last Error Value 정보가 있다. 이Last Error Value가 C에서의 errno역할을 한다. 스레드당 한개씩 생기기 때문에 멀티스레드 환경에서 안전하다고 할 수 있다.

이 때, Last Error Value값을 꺼내오는 함수가 바로 GetLastError()이다.

또, TEB의 전체 내용은 User Memory에 있기 때문에 우리가 접근할 수 있고, WinDBG를 사용하면 TEB의 내용도 확일할 수 있다.

ex) dt nt!_TEB

http://sungjinl2e.egloos.com/3325928


'library > CRT' 카테고리의 다른 글

컴파일 옵션  (0) 2013.02.22
메모리 누수 체크  (0) 2011.10.27
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
블로그 이미지

란마12

,

1. 유니코드와 멀티바이트 간 변환 관련

 1.1. MultiByteToWideChar()
 1.2. WideCharToMultiByte()

 1.3. W2A, A2W


#include <atlconv.h>

char szMult[] = "멀티녀석들!!";
wchar_t szWide[] = L"유니코드닷";

USES_CONVERSION; //무조건 정의
strcpy(szMult, W2A(szWide)); // 유니코드 -> 멀티바이트 복사
_tcscpy(szWide, W2A(szMult)); // 멀티바이트 -> 유니코드 복사

 

 1.4. _bstr_t

  #include <comutil.h>

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


  _bstr_t str(L"asdfasfd가나다라");

 

   wchar_t* pstrw = str;

   char* pstra = str;

   TCHAR* pstrt = str;


   주의: sprintf 등 함수의 가변인자로 사용할 경우 반드시 타입을 명시해 줘야한다. 

           sprintf(pstra, "%s", (LPCSTR)pstrw);


2. 문자열 변환 관련


TCHAR szBuf[5] = "a";
StringCchCat(szBuf, _countof(szBuf), "bcdefgh"); //abcd
_tcsncat(szBuf, "bcdefgh", _countof(szBuf)); //abcdefgh

StringCch*계열 함수
 - 널종료에 대해 신경쓰지 않아도 된다.
 - strlwr()함수등 지원하지 않는 함수가 많다.

결론 : StringCch*계열 함수를 쓰되 지원되지 않는 함수에 대해 _tcs*계열 함수를 쓰자.

'library > CRT' 카테고리의 다른 글

메모리 누수 체크  (0) 2011.10.27
errno  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
코딩시 Unicode 관련 고려사항  (0) 2010.11.23
블로그 이미지

란마12

,

시간과 날짜와 관련된 라이브러리 함수

구분

함수 원형과 인자

함수 설명

시간

계산

time_t time(time_t *timeptr);

1970 1 1일 자정부터 경과된 현재 시간을 초단위로 계산

시간을

문자열로

변환

char *asctime(strcut tm *time);

구조체 tm형식의 시간을 문자열로 변환

char *ctime(time_t *time);

함수 time()로부터 계산된 현재 시간을 문자열로 변환

시간을

구조체로

변환

struct tm *localtime(time_t *time);

지역 시간(local time)을 구조체 tm의 형식으로 가져오는 함수

struct tm *gmtime(time_t *time);

Greenwich Meam Time(GMT)을 구조체 tm 형식으로 가져옴

시간

차이

계산

clock_t clock(void);

clock tick으로 경과된 시간

double difftime(time_t time2, time_t time1);

두 시간의 차이를 초단위로 계산

시간

지연

void Sleep(unsigned millisecond);

인자가 지정하는 만큼의 1/1000초 단위의 시간을 지연

void delay(unsigned millisecond);

 

예제

  

#include<stdio.h>

#include<time.h>

 

int main(void)

{

    time_t now;

    time(&now);

    printf("현재날짜와시간: %s", asctime(localtime(&now)));

    printf("현재날짜와시간: %s", ctime(&now));

 

    return 0;

}

 

 

 

strcut tm은 <time.h>에 다음과 같이 정의되어 있다.

 

struct tm {

        int tm_sec;     /* (seconds)       - [0,59] */

        int tm_min;     /* (minutes)       - [0,59] */

        int tm_hour;    /* 시간(hour)        - [0,23] */

        int tm_mday;    /* 날짜(day)         - [1,31] */

        int tm_mon;     /* (month)         - [0,11] */

        int tm_year;    /* 1900년이후의연도수*/

        int tm_wday;    /* dydlf(second)     - [0,6] */

        int tm_yday;    /* 연중날짜(day)     - [0,365] */

        int tm_isdst;   /* 일광절약시간      - [0, 59] */

        };

 

예제

 

#include<stdio.h>

#include<time.h>

int main(void)

{

    time_t curr;

    struct tm *d;

    curr=time(NULL);

    d=localtime(&curr);

    printf("현재날짜\n");

    printf("%d%d%d\n", d->tm_year+1900, d->tm_mon+1, d->tm_mday);

    printf("현재시간\n");

    printf("%d%d%d\n", d->tm_hour, d->tm_min, d->tm_sec);

    return 0;

}

 

 

 

 

time과 clock의 차이

구분

함수 time

함수 clock

원형

time_t time(time_t *timer);

clock_t clock(void);

기능

절대시간 또는 달력시간(calendar time)을 계산

프로세서 시간을 계산

반환 값

1970 1 1일 자정 이후 현재까지 경과된 시간을 초(second)로 반환

프로세서 시간을 반환

 

예제 - 함수 time 사용

  

#include<stdio.h>

#include<time.h>

 

int main(void)

{

    time_t start, end;

    long i=0;

    double pst;

 

    start=time(NULL);

 

    while(i<30000000)

    {

        i++;

    }

 

    end=time(NULL);

    pst=difftime(end, start);

    printf("time: %f\n", pst);

 

    return 0;

}

/**초단위의시간을계산하는함수time()의결과는0초**/

 

예제 - 함수 clock 사용

  

#include<stdio.h>

#include<time.h>

 

int main(void)

{

    time_t start, end;

    long i=0;

    double pst;

 

    start=clock();

 

    while(i<30000000)

    {

        i++;

    }

 

    end=clock();

    pst=(double)(end-start)/CLK_TCK;

    printf("time: %f\n", pst);

 

    return 0;

}

 

 

시간을 지연시키는 함수 Sleep 예제

  

#include<stdio.h>

#include<time.h>

#include<windows.h>    //Sleep()

 

int main(void)

{

    clock_t start, end;

    double pst;

 

    start = clock();

    printf("start!\n");

    Sleep(3500);

    end = clock();

    printf("end\n");

    pst = (double)(end-start)/CLK_TCK;

    printf("경과시간: %f\n", pst);

 

    return 0;

}

 

 

 

시간을 처리하는 함수와는 다른 개념의 함수이지만 유용하게 사용할 수 있는 함수로 kbhit가 있다. 이 함수의 원형은 다음과 같이 함수의 인자가 없으며, 키보드 상의 어떤 키를 누르면 0이 아닌 값을, 누르지 않은 상태라면 0값을 반환하는 함수다. 함수 kbhit는 <conio.h>를 필요로 한다.

kbhit

함수원형

int kbhit(void);

반환 값

입력된 키가 있으면 0이 아닌 값을, 입력된 키가 없으면 0을 반환

 

프로그램 실행중에 아무키나 누르기 전까지만 프로그램을 계속 반복시키고자 한다면 반복문 while과 함께 다음과 같이 사용할 수 있다.

while(!kbhit())

{

    //반복할프로그램

}

 

예제 - 현재 시간을 연속적으로 출력(kbhit, localtime)

 

#include<stdio.h>

#include<time.h>

#include<stdlib.h>

#include<conio.h>

int main(void)

{

    time_t now;

    struct tm *d;

 

    while(!kbhit())

    {

        system("cls");

        now=time(NULL);

        d=localtime(&now);

        printf("현재날짜와시간: %s\n", asctime(d));

    }

    return 0;

}

 

 

 

날짜 수와 요일 계산

기준일(1년 1월 1일)로부터 특정일 사이의 날짜 수의 계싼 함수 total_days

long total_days(int year, int month, int day)

{

    int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    int i;

    long total=0L;

 

    total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

    if(!(year%4) && year%100 || !(ytear%400))

        months[1]++;

    for(i=0;i<month-1;i++)

        total += months[i];

    total += day;

 

    return total;

}

 

특정일의 요일 계산

  

#include<stdio.h>

 

long total_days(int year, int month, int day);

 

int main(void)

{

    int year, month, day;

    char *week_day[] = {"", "", "", "", "", "", ""};

    long total;

 

    printf("특정일의요일구하는프로그램\n\n");

    printf("입력될숫자는space bar로분리하고\n");

    printf("Enter 키를누릅니다.\n");

    printf("예로2005 5 1 Enter\n");

    printf("년월일입력: ");

    scanf("%d %d %d", &year, &month, &day);

 

    total=total_days(year, month, day);

 

    printf("%s 요일입니다.\n", week_day[total%7]);

 

    return 0;

}

 

long total_days(int year, int month, int day)

{

    int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    int i;

    long total=0L;

 

    total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

    if(!(year%4) && year%100 || !(year%400))

        months[1]++;

    for(i=0;i<month-1;i++)

        total += months[i];

    total += day;

 

    return total;

}

 

 

특정일 사이의 날짜 수를 계산

  

#include<stdio.h>

 

long total_days(int year, int month, int day);

 

int main(void)

{

    long total;

   

    total=total_days(2010, 7, 21) - total_days(1987, 4, 16);

 

    printf("두날짜사이의날짜수: %ld\n", total);

 

    return 0;

}

 

long total_days(int year, int month, int day)

{

    int months[]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    int i;

    long total=0L;

 

    total=(year-1)*365L+(year-1)/4-(year-1)/100+(year-1)/400;

    if(!(year%4) && year%100 || !(year%400))

        months[1]++;

    for(i=0;i<month-1;i++)

        total += months[i];

    total += day;

 

    return total;

}

 

 

 


'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
코딩시 Unicode 관련 고려사항  (0) 2010.11.23
디버그 시 malloc 메모리 누수 탐지  (0) 2010.07.16
블로그 이미지

란마12

,

strcpy ... 이런 함수 쓰면 절대 안된다. strncpy를 써야지...
wsprintf ... 이런 함수도 쓰면 안된다. _snprintf를 써야지... (리눅스 같으면 snprintf)
모든 문자열 함수에 경계체크 하는 타입과 하지 않는 타입이 있어서, 하지 않는 타입을 사용하면 문자열이 깨지기도 프로그램이 죽기도 하고 경우에 따라 해킹을 당할 수도 있다. 하도 보안 보안 하다보니 모 이런건 이제 기본이 되었다.

근데...  경계 체크를 하는 함수들도.. 경계 체크하는 방식이 조금씩 차이가 있어서...
lstrcpyn 사용하는 식으로 _snprintf를 쓰다 보면 문제가 생길 때가 있다.

이 이슈는... 예전부터 머리속에 새기고 필요할 때마다 인터넷을 뒤져보던 주제이지만,
사실 일도 많고 귀찮다 보니.. 꼼꼼하게 따지지 않고 그냥 적당~히 넘어가는 것이 사실이다.
에이 설마~ 하던 문제가 오늘 터졌다.. ㅋㅋ
그래서 이번엔 내가 직접 함 정리해보려 한다.

※ 아래의 샘플과 테스트 들은 모두 MBCS 환경을 가정한다.
    유니코드일 때도 달라질 일은 없다.
   - 그냥 바이트수와 글자수를 헷갈리지 말고,
   - T-Type 함수와 T-TYPE 데이터를 사용한다.
      (strXXX 대신 _tcsXXX를 쓴다. CHAR 대신 TCHAR를 사용한다. 등등)


샘플코드다.
  1. #ifndef _countof   
  2. #define    _countof(X)    sizeof(X)/sizeof(X[0])   
  3. #endif   
  4.   
  5. int main(int argc, char* argv[])   
  6. {   
  7.     CHAR    szBuf[4] = {0,};   
  8.       
  9.     // 1. strncpy   
  10.     memset (szBuf, 'X'sizeof(szBuf));   
  11.     strncpy(szBuf, "AAAAAAAAA", _countof(szBuf) -1);   
  12.     printf("strncpy : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  13.   
  14.     // 2. lstrcpyn   
  15.     memset (szBuf, 'X'sizeof(szBuf));   
  16.     lstrcpyn(szBuf, "AAAAAAAAA", _countof(szBuf) -1);   
  17.     printf("lstrcpyn : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  18.   
  19.     // 3. _snprintf   
  20.     memset (szBuf, 'X'sizeof(szBuf));   
  21.     _snprintf(szBuf, _countof(szBuf) -1, "%s""AAAAAAAAA");   
  22.     printf("_snprintf : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  23.   
  24.     // 4. strncat   
  25.     memset (szBuf, 'X'sizeof(szBuf));   
  26.     szBuf[0] = NULL;   
  27.     strncat(szBuf, "AAAAAAAAA", _countof(szBuf) -1);   
  28.     printf("strncat : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  29.   
  30.     return 0;   
  31. }   


4글자 짜리 버퍼에 네가지 문자열 함수로 문자열을 복사해넣되, 버퍼사이즈는 전부 3으로 준다.
그리고 버퍼 4바이트를 한바이트씩 프린트해 본다. (스트링으로 프린트하는게 아님)
어떤 일이 벌어질까?

실행 결과는 다음과 같다. (A는 복사된 문자열, X는 버퍼 초기화된 값이 남아있는 것이다.)
strncpy : AAAX
lstrcpyn : AA X
_snprintf : AAAX
strncat : AAA
Press any key to continue

결과를 하나씩 분석해 보자.

1. strncpy, _snprintf
이 두가지 함수에서 지정된 버퍼사이즈는 곧 복사할 최대 글자수를 의미한다. 버퍼사이즈를 넘어서는 길이의 문자열을 복사하려고 시도할 경우 지정된 글자수만큼만 복사된다.
말하자면... Null로 스트링을 Terminate 시켜주는 따위의 일은 하지 않는다는 것이다. 따라서 이 문자열 함수를 쓸때 글자수는 버퍼사이즈 - 1 만큼을 지정하고, 수동으로 스트링 종결 처리를 해주어야 한다.
다음의 두가지 중 한가지 패턴을 지켜주어야 한다.

// 방법 1 : 일단 전체 버퍼를 초기화시켜 놓고 최대 "버퍼 사이즈 - 1" 글자 만큼만 복사하는 방법
CHAR  szBuf[1024] = {0,}; // 혹은 memcpy(szBuf, NULL, sizeof(szBuf)); 해준다.
strncpy(szBuf, lpSrcStr, _countof(szBuf) - 1);

// 방법 2 : 문자열을 복사한 후 마지막 글자를 NULL로 덮어써서 수동으로 문자열을 Terminate 시켜준다.
CHAR  szBuf[1024] = {0,};
strncpy(szBuf, lpSrcStr, _countof(szBuf));
szBuf[_countof(szBuf) - 1] = NULL;

만약 스트링 종결처리를 안해주었을 땐...버퍼사이즈를 넘어서는 문자열이 입력되었을 때 스트링이 종결되지 않고, 뒤에 쓰레기 값이 붙게 된다. (상기 실행결과를 살펴보라.)

물론.. 입력 문자열이 버퍼를 넘어서지 않을 때는 문제될 게 없다. 하지만, 항상 좋은 입력만 들어올 거라고 누가 장담하겠는가? 심지어 GUI에서 입력문자열 제한을 하는 경우에도 문자열 핸들링시 버퍼체크는 반드시 해주어야 한다.


2. lstrcpyn
가장 쓰기 편한 함수이다. 이 함수에서 지정된 버퍼사이즈에는 말 그대로 버퍼사이즈를 주면 된다.
원본 문자열이 버퍼 사이즈를 초과할 경우, 자동으로 "버퍼사이즈 - 1" 글자 만큼만 복사한 후 나머지 한글자를 NULL로 Terminate 시켜준다. 상기 실행결과를 보면, 버퍼사이즈를 3글자를 주었는데, 결과는 2글자만 복사되고 3번째에는 NULL이 들어가서 문자열이 종결된 것을 알 수 있다.


3. strncat
이 함수는... 좀 특이하다. "n"은 버퍼사이즈가 아니라 복사할 최대 글자수를 의미한다. Src문자열의 길이가 지정된 최대 글자수를 넘어설 경우, 지정된 글자수만큼을 복사한 후 그 뒤에 NULL을 append해준다... 말하자면... 최대 글자수를 N으로 지정했을 경우, 실제로는 N+1 바이트가 복사될 수도 있다는 의미이다. 상기 실행결과를 보면 3글자를 복사했는데, 4번째 글자가 NULL로 덮어씌워졌음을 알 수 있다. 따라서 그냥 생각없이 버퍼 사이즈만큼을 지정해주게 되면... 1바이트 오버플로우가 발생할 수 있다.
게다가 문자열 Concat 함수이기 때문에 다른 함수들 처럼 버퍼사이즈를 생각없이 써주는 게 아니라... 버퍼 중 현재 사용된 사이즈를 제외하고, 실제로 더 쓸 수 있는 바이트 수를 계산하여 다음과 같이 해주어야 한다.

    CHAR    szBigBuf[10] = {'a','b','c','d'};
    CHAR*    lpTest = "TESTSTRING";
    strncat (szBigBuf, lpTest, _countof(szBigBuf) - strlen(szBigBuf) - 1);
         // 덧붙여질 NULL을 고려하여 마지막에 -1을 해준다.
    printf ("%s\r\n", szBigBuf);


... 하여간 복잡한 함수다.


C에서 문자열을 다룬다는 것은... 정말 쉽지 않은 주제인 것 같다.
처음엔 멋도 모르고 쓰다가... 포인터를 이해하게 되고... 그다음엔 버퍼 체크의 개념을 알게 되고, (그땐 내가 문자열을 정복한 줄 알았지 ^^) 그 다음엔 유니코드를 알게 되고... 유니코드를 알고 나면 거꾸로 MBCS 다루기가 얼마나 어려운지를 알게 된다...

http://kuaaan.tistory.com/56

'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
코딩시 Unicode 관련 고려사항  (0) 2010.11.23
디버그 시 malloc 메모리 누수 탐지  (0) 2010.07.16
블로그 이미지

란마12

,
윈도에서 코딩시
유니코드 적용해야 할때
#Define _UNICOE 을 하면
기존에 잘 컴파일 되던 플젝이 에러 투성이로 변하는 경우가 발생하져..ㅜㅜ

이런경우 당황하지 하지 마시고 모든 에러요인을 찾아다니면서 삽질을 하셔야 할껍니다.

아래와 같이 하시면 대략의 삽질은 면하지 않을까요..?? ^^
( 물론 직접 소스를 찾아가 수정을 가해야하는 부분이 발생하지만 아래와 같이 하시면 많은 부분의 수정요인이 사라지리라 생각합니다. )

1. 윈도 API를 사용한다.
sprintf 등 보다는 wsprintf등 과 같은 윈도에서 제공하는 함수나 MFC를 사용한다.
아무래도 UNICODE 설정만으로 한번에 변경하기 위해선 윈도에서 제공하는것을 써야할듯 싶습니다..^^

2. char * aa = NULL; 와 같은 char 사용시 TCHAR 을 사용한다.
ex> TCHAR *aa = NULL;

3. aa = "나텍스트"; 와 같이 텍스트 값을 사용할때는 _T("") 매크로를 사용한다.
ex> TCHAR aa[50] = { 0 , } , *a1 = NULL;
a1 = _T("나텍스트");
wsprintf( aa , _T("%s") , a1 );

3. 메모리 함수 memset , memcpy 등을 사용시 사용되는 스트링의 크기 ( 글자수가 아닌 ) 는 항상 ( sizeof(TCHAR) * ( 글자수 ) ) 로 한다.
ex> TCHAR aa[100];
memset( (BYTE*)aa , 0x00 , ( sizeof(TCHAR) * (100 ) ) );

4. 스트링 펑션을 쓸때는 _tcs... 으로 나가는 매크로를 사용한다.
ex> TCHAR *aa = "나도텍스트" , a1[50] , a2[50];
memset( (BYTE*)a1 , 0x00 , ( sizeof(TCHAR) * 50 ) );
memset( (BYTE*)a2 , 0x00 , ( sizeof(TCHAR) * 50 ) );

if ( 49 >=  _tcslen( aa ) )
{
_tcscpy( a1 , aa );
memcpy( (BYTE*)a2 , (BYTE*)aa , ( sizeof(TCHAR) * _tcslen(aa) ) );
}


** 아래는 4에서 말한 윈도용 매크로 함수들입니다.  참고하시기 바랍니당

1.       Formatted I/O

MACRO

ANSI

UNICODE

설명

_tprintf

printf

wprintf

 

_ftprintf

fprintf

fwprintf

 

_stprintf

sprint

swprintf

 

_sntprintf

_snprintf

_snwprintf

 

_vtprintf

vprintf

vwprintf

 

_vftprintf

vfprintf

vfwprintf

 

_vstprintf

vsprintf

vswprintf

 

_vsntprintf

_vsnprintf

_vsnwprintf

 

_tscanf

scanf

wscanf

 

_ftscanf

fscanf

fwscanf

 

_stscanf

sscanf

swscanf

 

 

2.       Formatted I/O

MACRO

ANSI

UNICODE

설명

_fgettc

fgetc

fgetwc

 

_fgetts

fgets

fgetwc

 

_fputtc

fputc

fputwc

 

_fputts

fputs

fputws

 

_gettc

getc

getwc

 

_gettchar

getchar

getwchar

 

_getts

gets

_getws

 

_puttc

putc

putwc

 

_puttchar

putchar

putwchar

 

_putts

puts

_putws

 

_ungettc

ungetc

ungetwc

 

 

3.       Stdio function

MACRO

ANSI

UNICODE

설명

_tfopen

 

_wfopen

 

_tfreopen

 

_wfreopen

 

 

4.       String conversion function

MACRO

ANSI

UNICODE

설명

_tcstod

strtod

wcstod

 

_tcstol

strtol

wcstol

 

_tcstoul

strtoul

wcstoul

 

_itot

_itoa

_itow

 

_ltot

_ltoa

_ltow

 

_ultot

_ultoa

_ultow

 

_ttoi

atoi

_wtoi

 

_ttol

atol

_wtol

 

 

5.       String function

MACRO

ANSI

UNICODE

설명

_tcscat

strcat

wcscat

 

_tcschr

strchr

wcschr

 

_tcscmp

strcmp

wcscmp

 

_tcscpy

strcpy

wcscpy

 

_tcscspn

strcspn

wcscspn

 

_tcslen

strlen

wcslen

 

_tcsclen

strlen

wcslen

 

_tcsncat

strncat

wcsncat

 

_tcsnccat

strncat

wcsncat

 

_tcsnccmp

strncmp

wcsncmp

 

_tcsncmp

strncmp

wcsncmp

 

_tcsncpy

strncpy

wcsncpy

 

_tcsnccpy

strncpy

wcsncpy

 

_tcspbrk

strpbrk

wcspbrk

 

_tcsrchr

strrchr

wcsrchr

 

_tcsspn

strspn

wcsspn

 

_tcsstr

strstr

wcsstr

 

_tcstok

strtok

wcstok

 

_tcsdup

_strdup

_wcsdup

 

_tcsicmp

_stricmp

_wcsicmp

 

_tcsncicmp

_strnicmp

_wcsnicmp

 

_tcsnicmp

_strnicmp

_wcsnicmp

 

_tcsnset

_strnset

_wcsnset

 

_tcsncset

_strnset

_wcsnset

 

_tcsrev

_strrev

_wcsrev

 

_tcsset

_strset

_wcsset

 

_tcslwr

_strlwr

_wcslwr

 

_tcsupr

_strupr

_wcsupr

 

 

6.       ctype function

MACRO

ANSI

UNICODE

설명

_istalpha

isalpha

iswalpha

 

_istupper

isupper

iswupper

 

_istlower

islower

iswlower

 

_istdigit

isdigit

iswdigit

 

_istxdigit

isxdigit

iswxdigit

 

_istspace

isspace

iswspace

 

_istpunct

ispunct

iswpunct

 

_istalnum

isalnum

iswalnum

 

_istprint

isprint

iswprint

 

_istgraph

isgraph

iswgraph

 

_istcntrl

iscntrl

iswcntrl

 

_istascii

isascii

iswascii

 

_totupper

toupper

towupper

 

_totlower

tolower

towlower

 


http://demo.initech.com/?document_srl=4077



Standard CRT function

StrSafe Function

strcat

StringCchCat, StringCchCatEx, StringCbCat, StringCbCatEx

strcmp

(no equivalent function)

strcpy

StringCchCopy, StringCchCopyEx, StringCbCopy, StringCbCopyEx

strlen

StringCchLength, StringCbLength

Standard CRT function

StrSafe Function

sprintf

StringCchPrintf, StringCchPrintfEx, StringCbPrintf, StringCbPrintfEx

vsprintf

StringCchVPrintf, StringCchVPrintfEx, StringCbVPrintf, StringCbVPrintfEx

 

 

Data Types

ANSI

Wide

TCHAR

char

wchar_t

_TCHAR

_finddata_t

_wfinddata_t

_tfinddata_t

__finddata64_t

__wfinddata64_t

_tfinddata64_t

_finddatai64_t

_wfinddatai64_t

_tfinddatai64_t

int

wint_t

_TINT

signed char

wchar_t

_TSCHAR

unsigned char

wchar_t

_TUCHAR

char

wchar_t

_TXCHAR

 

L

_T or _TEXT

LPSTR
(char *)

LPWSTR
(wchar_t *)

LPTSTR
(_TCHAR *)

LPCSTR
(const char *)

LPCWSTR
(const wchar_t *)

LPCTSTR
(const _TCHAR *)

LPOLESTR
(for OLE)

LPWSTR

LPTSTR

 

 

 

TCHAR String Functions

ANSI

Wide

TCHAR

_access

_waccess

_taccess

_atoi64

_wtoi64

_tstoi64

_atoi64

_wtoi64

_ttoi64

_cgets

_cgetws

cgetts

_chdir

_wchdir

_tchdir

_chmod

_wchmod

_tchmod

_cprintf

_cwprintf

_tcprintf

_cputs

_cputws

_cputts

_creat

_wcreat

_tcreat

_cscanf

_cwscanf

_tcscanf

_ctime64

_wctime64

_tctime64

_execl

_wexecl

_texecl

_execle

_wexecle

_texecle

_execlp

_wexeclp

_texeclp

_execlpe

_wexeclpe

_texeclpe

_execv

_wexecv

_texecv

_execve

_wexecve

_texecve

_execvp

_wexecvp

_texecvp

_execvpe

_wexecvpe

_texecvpe

_fdopen

_wfdopen

_tfdopen

_fgetchar

_fgetwchar

_fgettchar

_findfirst

_wfindfirst

_tfindfirst

_findnext64

_wfindnext64

_tfindnext64

_findnext

_wfindnext

_tfindnext

_findnexti64

_wfindnexti64

_tfindnexti64

_fputchar

_fputwchar

_fputtchar

_fsopen

_wfsopen

_tfsopen

_fullpath

_wfullpath

_tfullpath

_getch

_getwch

_gettch

_getche

_getwche

_gettche

_getcwd

_wgetcwd

_tgetcwd

_getdcwd

_wgetdcwd

_tgetdcwd

_ltoa

_ltow

_ltot

_makepath

_wmakepath

_tmakepath

_mkdir

_wmkdir

_tmkdir

_mktemp

_wmktemp

_tmktemp

_open

_wopen

_topen

_popen

_wpopen

_tpopen

_putch

_putwch

_puttch

_putenv

_wputenv

_tputenv

_rmdir

_wrmdir

_trmdir

_scprintf

_scwprintf

_sctprintf

_searchenv

_wsearchenv

_tsearchenv

_snprintf

_snwprintf

_sntprintf

_snscanf

_snwscanf

_sntscanf

_sopen

_wsopen

_tsopen

_spawnl

_wspawnl

_tspawnl

_spawnle

_wspawnle

_tspawnle

_spawnlp

_wspawnlp

_tspawnlp

_spawnlpe

_wspawnlpe

_tspawnlpe

_spawnv

_wspawnv

_tspawnv

_spawnve

_wspawnve

_tspawnve

_spawnvp

_wspawnvp

_tspawnvp

_spawnvpe

_wspawnvpe

_tspawnvpe

_splitpath

_wsplitpath

_tsplitpath

_stat64

_wstat64

_tstat64

_stat

_wstat

_tstat

_stati64

_wstati64

_tstati64

_strdate

_wstrdate

_tstrdate

_strdec

_wcsdec

_tcsdec

_strdup

_wcsdup

_tcsdup

_stricmp

_wcsicmp

_tcsicmp

_stricoll

_wcsicoll

_tcsicoll

_strinc

_wcsinc

_tcsinc

_strlwr

_wcslwr

_tcslwr

_strncnt

_wcsncnt

_tcsnbcnt

_strncnt

_wcsncnt

_tcsnccnt

_strncnt

_wcsncnt

_tcsnccnt

_strncoll

_wcsncoll

_tcsnccoll

_strnextc

_wcsnextc

_tcsnextc

_strnicmp

_wcsnicmp

_tcsncicmp

_strnicmp

_wcsnicmp

_tcsnicmp

_strnicoll

_wcsnicoll

_tcsncicoll

_strnicoll

_wcsnicoll

_tcsnicoll

_strninc

_wcsninc

_tcsninc

_strnset

_wcsnset

_tcsncset

_strnset

_wcsnset

_tcsnset

_strrev

_wcsrev

_tcsrev

_strset

_wcsset

_tcsset

_strspnp

_wcsspnp

_tcsspnp

_strtime

_wstrtime

_tstrtime

_strtoi64

_wcstoi64

_tcstoi64

_strtoui64

_wcstoui64

_tcstoui64

_strupr

_wcsupr

_tcsupr

_tempnam

_wtempnam

_ttempnam

_ui64toa

_ui64tow

_ui64tot

_ultoa

_ultow

_ultot

_ungetch

_ungetwch

_ungettch

_unlink

_wunlink

_tunlink

_utime64

_wutime64

_tutime64

_utime

_wutime

_tutime

_vscprintf

_vscwprintf

_vsctprintf

_vsnprintf

_vsnwprintf

_vsntprintf

asctime

_wasctime

_tasctime

atof

_wtof

_tstof

atoi

_wtoi

_tstoi

atoi

_wtoi

_ttoi

atol

_wtol

_tstol

atol

_wtol

_ttol

character compare

Maps to macro or inline function

_tccmp

character copy

Maps to macro or inline function

_tccpy

character length

Maps to macro or inline function

_tclen

ctime

_wctime

_tctime

fgetc

fgetwc

_fgettc

fgets

fgetws

_fgetts

fopen

_wfopen

_tfopen

fprintf

fwprintf

_ftprintf

fputc

fputwc

_fputtc

fputs

fputws

_fputts

freopen

_wfreopen

_tfreopen

fscanf

fwscanf

_ftscanf

getc

getwc

_gettc

getchar

getwchar

_gettchar

getenv

_wgetenv

_tgetenv

gets

getws

_getts

isalnum

iswalnum

_istalnum

isalpha

iswalpha

_istalpha

isascii

iswascii

_istascii

iscntrl

iswcntrl

_istcntrl

isdigit

iswdigit

_istdigit

isgraph

iswgraph

_istgraph

islead (Always FALSE)

(Always FALSE)

_istlead

isleadbyte (Always FALSE)

isleadbyte (Always FALSE)

_istleadbyte

islegal (Always TRUE)

(Always TRUE)

_istlegal

islower

iswlower

_istlower

isprint

iswprint

_istprint

ispunct

iswpunct

_istpunct

isspace

iswspace

_istspace

isupper

iswupper

_istupper

isxdigit

iswxdigit

_istxdigit

main

wmain

_tmain

perror

_wperror

_tperror

printf

wprintf

_tprintf

putc

putwc

_puttc

putchar

putwchar

_puttchar

puts

_putws

_putts

remove

_wremove

_tremove

rename

_wrename

_trename

scanf

wscanf

_tscanf

setlocale

_wsetlocale

_tsetlocale

sprintf

swprintf

_stprintf

sscanf

swscanf

_stscanf

strcat

wcscat

_tcscat

strchr

wcschr

_tcschr

strcmp

wcscmp

_tcscmp

strcoll

wcscoll

_tcscoll

strcpy

wcscpy

_tcscpy

strcspn

wcscspn

_tcscspn

strerror

_wcserror

_tcserror

strftime

wcsftime

_tcsftime

strlen

wcslen

_tcsclen

strlen

wcslen

_tcslen

strncat

wcsncat

_tcsncat

strncat

wcsncat

_tcsnccat

strncmp

wcsncmp

_tcsnccmp

strncmp

wcsncmp

_tcsncmp

strncpy

wcsncpy

_tcsnccpy

strncpy

wcsncpy

_tcsncpy

strpbrk

wcspbrk

_tcspbrk

strrchr

wcsrchr

_tcsrchr

strspn

wcsspn

_tcsspn

strstr

wcsstr

_tcsstr

strtod

wcstod

_tcstod

strtok

wcstok

_tcstok

strtol

wcstol

_tcstol

strtoul

wcstoul

_tcstoul

strxfrm

wcsxfrm

_tcsxfrm

system

_wsystem

_tsystem

tmpnam

_wtmpnam

_ttmpnam

tolower

towlower

_totlower

toupper

towupper

_totupper

ungetc

ungetwc

_ungettc

vfprintf

vfwprintf

_vftprintf

vprintf

vwprintf

_vtprintf

vsprintf

vswprintf

_vstprintf

WinMain

wWinMain

_tWinMain

 

'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
디버그 시 malloc 메모리 누수 탐지  (0) 2010.07.16
블로그 이미지

란마12

,

#ifdef _DEBUG
 #define _INC_MALLOC  // exclude standard memory alloc procedures
 #define _CRTDBG_MAP_ALLOC // include Microsoft memory leak detection procedures
 #include "crtdbg.h"
#endif

프로그램 진입점에서 최초에  

#ifdef _DEBUG
 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
#endif


발견된 메모리 누수지점에 브레이크포인트 걸기

Detected memory leaks!
Dumping objects ->
{60} normal block at 0x00324818, 4 bytes long.
Data: <,   > 2C 00 00 00
Object dump complete.

위와같이 누수가 발견되었다면 CrtSetBreakAlloc함수를 다음과 같이 호출

#ifdef _DEBUG
 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
 _CrtSetBreakAlloc(60); //또는 _crtBreakAlloc = 60;
#endif

visual studio를 사용한다면
1. F11로 디버깅을 시작
2. ALT+F9를 눌러서 중단점 창
3. 새로만들기>새데이터중단점에서 주소(0x00324818) 입력
4. 적당한 바이트(위의 경우는 4)를 입력

_CRTDBG_MAP_ALLOC를 선언하면 이렇게 파일명과 라인번호까지 알려준다던데 안되더라..

Detected memory leaks!
Dumping objects ->
D:\VisualC++\CodeGuru\MemoryLeak\MemoryLeak.cpp(67) : {60}
normal block at 0x00324818, 4 bytes long.
Data: <,   > 2C 00 00 00
Object dump complete.

원인이나 해결책 아시는 분은 댓글 부탁드립니다.

문제점: stl사용 시 실제로 누수되지 않았는데도 불구하고 무조건 누수발생 보고됨.

http://msdn.microsoft.com/en-us/library/x98tx3cf
http://msdn.microsoft.com/ko-kr/library/w2fhc9a3.aspx

'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
코딩시 Unicode 관련 고려사항  (0) 2010.11.23
블로그 이미지

란마12

,