'개발이야기'에 해당되는 글 26건

  1. C코드 메모리릭 잡기
  2. MFC 버튼 마우스 커서 변경
  3. fopen_s 파일읽기와 저장
  4. 부모 클래스의 기본생성자가 없을때 메모리 누수 현상 2
  5. MFC 디렉토리 생성과 삭제
  6. 마방진 원리 및 문제 4
  7. 뮤텍스를 이용한 프로그램 중복실행 방지
  8. 일정 날짜 기준 로그파일 제거 1
  9. 로컬 IP주소 얻기
  10. 유니코드 ↔ 안시 변환함수


프로그램을 개발하다보면 사람인지라 버그나 메모리릭이 생기는 것은 당연합니다.


참조: 네이버웹툰, 스마트폰 게임 개발 이야기에서...


따라서 이를 빠르게 발견하고 해결하는 것이 중요합니다.

여기서는 C코드로 짠(콘솔 응용프로그램) 환경에서 메모리릭을 발견하고 잡는 방법을 알아보겠습니다.

다음과 같이 메인 프로젝트를 만들고 메모리릭을 감지하는 코드를 추가합니다. (강조된 부분)

  
// MemoryLeak.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"

#ifdef _DEBUG
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	// 메모리릭 발생코드
	char * szName = new char[100];
	szName = "mooyou";
	printf("%s", szName);

	return 0;
}


위의 코드를 실행하면 출력창에 메모리릭이 발생되었다고 나타납니다.

출력 정보를 보면 memoryleak.cpp 파일의 18번째 라인에서 100 bytes 크기의 메모리릭이 발생되었습니다.

이 정보만으로도 충분히 메모리릭을 잡을수 있습니다.



여기서 더 나아가 메모리 블록 위치 정보를 통해서 직접적으로 메모리릭이 발생한 코드로 이동까지 해보겠습니다.

이를위해 다음과 같이 _CrtSetDbgFlag 바로아래 _CrtSetBreakAlloc( 97 ); 코드를 추가합니다.

이는 메모리릭 발생 위치에 BreakPoint를 설정합니다.

  
// MemoryLeak.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
//

#include "stdafx.h"

#ifdef _DEBUG
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
	_CrtSetBreakAlloc( 97 );
#endif

	// 메모리릭 발생코드
	char * szName = new char[100];
	szName = "mooyou";
	printf("%s", szName);

	return 0;
}


즉, 출력된 메모리 블록 위치 정보를 이용하여 BreakPoint를 설정하고, 실행시 설정된 곳으로 이동하겠다라는 것입니다 !

코드를 추가하고 실행하면 다음과 같은 메시지 박스가 나타납니다.

BreakPoint에 의해 중단점을 트리거 했다라는 내용이고 여기서는 중단을 누르면 됩니다.

하지만 누르고 나서 코드를 보면 메모리릭이 발생한 원하는 위치가 아닌 dbgheap.c 파일의 내용이 보이게 됩니다.

모르는 코드화면이 나왔다고 해서 전혀 걱정할 필요가 없습니다. 정상적인 화면이고... 제대로 잘 찾아온 것입니다. 

메모리릭이 발생한 곳으로 이동하려면 호출 스택에서 작업위치를 찾아가면 됩니다.

호출 스택은 보통 출력창과 같은 탭에 있고, 없다면 Ctrl + Alt + C를 눌러 호출 스택을 불러옵니다.

호출 스택은 현재 프로그램에서 실행된 명령을 순차적으로 나열한 것으로 맨 위에 찍힌 스택이 최근에 사용한 명령입니다.

현재 보이는 위치(노란색 화살표)가 msvcr100.dll로 dbgheap.c의 내용을 가르킵니다.

즉, 작업한 코드 영역이 아니죠. 따라서 작업한 영역인 MemoryLeak 스택을 더블클릭하여 이동합니다.

짜잔~ 왼쪽에 초록색 화살표가 보이시나요?

호출 스택을 이용하여 메모리릭이 발생한 위치로 이동하였습니다.




간단한 코드 추가로 위 그림과 같이 버튼의 커서를 변경시킬 수 있습니다.

변경하고자 하는 버튼의 컨트롤 변수를 만들고, OnInitDialog 함수에 다음과 같이 코드를 추가합니다.


  
BOOL CTestDlg::OnInitDialog()
{
	// 자동으로 생성되는 코드는 생략합니다.
	// TODO: 여기에 추가 초기화 작업을 추가합니다.

	// 버튼의 마우스 커서 변경
	// (m_ctrlButton: 해당 버튼의 컨트롤 변수)
	HCURSOR hCursor; 
	hCursor = AfxGetApp()->LoadStandardCursor(IDC_HAND); 
	SetClassLong(m_ctrlButton.GetSafeHwnd(),GCL_HCURSOR,(LONG)hCursor);

	return TRUE;  // 포커스를 컨트롤에 설정하지 않으면 TRUE를 반환합니다.
}


SetClassLong 함수는 해당 윈도우 핸들을 통하여 클래스 정보를 수정하는 함수입니다.

이를 통해 버튼 클래스의 커서 정보를 수정하는 원리입니다.



fopen_s 함수를 이용하여 파일을 읽는 코드와 저장하는 코드입니다.


  
//
// fopen_s 파일읽기
//
errno_t err;
FILE *fp;
if( (err = fopen_s(&fp, "file_path", "rt")) != 0 )
{
	AfxMessageBox(_T("File open error!"));
	return false;
}

fscanf_s(fp,"format", ...); // 사용법은 c의 scanf함수와 동일
fclose(fp)

//
// fopen_s 파일저장
//
errno_t err;
FILE *fp;
if( (err = fopen_s(&fp, "file_path", "wt")) != 0 )
{
	AfxMessageBox(_T("File open error!"));
	return ;
}

fprintf_s(fp,"format", ...); // 사용법은 c의 printf함수와 동일
fclose(fp)


특히 fscanf_s 함수를 사용하여 문자열을 읽어올때 c의 scanf함수 사용법과 동일하지 않습니다.

이와 관련하여 다음 링크를 참조하세요.

fscanf_s 주의사항: http://mooyou.tistory.com/6



C++로 개발되어진 모듈을 수정하던중 생각지도 않는 문제가 발생했습니다.


문제가 되는 기본 전제는 이러합니다. 부모의 기본생성자가 없다라는 점과 

자식 클래스에서는 이 부모 클래스를 상속받아 생성자를 오버라이딩해서 사용한다라는 점입니다.


여기서 부모 클래스 생성자에서는 메모리 할당을 하고 오버라이딩된 자식 클래스 생성자에서

이를 다시 메모리 할당하고자 할때 메모리 누수가 발생하였습니다. 지금 생각해도 참으로 복잡한 문제이죠...

에러가 난 코드를 최대한 간소화 시켜보았습니다.


//
// 메모리 누수 확인 코드
//
#ifdef _DEBUG
#include <crtdbg.h>
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

//
// Class A
//
class A {
public:
	char* szA;

public :
	A::A(int a)
	{
		szA = new char[a];

		OutputDebugString(_T("A생성자 호출\n"));
	}

	A::~A()
	{
		delete [] szA;

		OutputDebugString(_T("A소멸자 호출\n"));
	}
};

//
// Class B
//
class B : public A {

public:
	B::B(int b)
		// 1.
		// 부모가 기본생성자를 갖고있지 않으므로 자식은 생성자를 만들기 위해서
		// 부모의 사용가능한 생성자를 직접 지정해주어야 한다.
		: A(b)		
	{
		// 2.
		// 부모에서 생성된 객체를 메모리 할당
		szA = new char[b];

		OutputDebugString(_T("B생성자 호출\n"));
	}

	B::~B()
	{
		// 3.
		// 할당한 메모리를 제거하려 했으나 에러 발생
		// 여기서 메모리 누수 발생
		// delete [] szA;

		OutputDebugString(_T("B소멸자 호출\n"));
	}
};

//
// Main Function
//
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef _DEBUG
	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

	A a(3);
	B b(3);	// 메모리 누수 발생

	return 0;
}


이를 해결하고자 다음과 같이 Class B를 수정하였습니다.

  
//
// Class B
//
class B : public A {

public:
	B::B(int b)
		// 1.
		// 부모가 기본생성자를 갖고있지 않으므로 자식은 생성자를 만들기 위해서
		// 부모의 사용가능한 생성자를 직접 지정해주어야 한다.
		: A(b)		
	{
		// 4.
		// 지정해준 [: A(b)] 부모 생성자에서 할당했던 객체를 미리 해제하고
		// 해당 생성자에서 할당된 객체는 이후 A소멸자가 호출될때 제거하게 해준다.
		delete [] szA;

		// 2.
		// 부모에서 생성된 객체를 메모리 할당
		szA = new char[b];

		OutputDebugString(_T("B생성자 호출\n"));
	}

	B::~B()
	{
		// 3.
		// 할당한 메모리를 제거하려 했으나 에러 발생
		// 여기서 메모리 누수 발생
		// delete [] szA;

		OutputDebugString(_T("B소멸자 호출\n"));
	}
};


'개발이야기 > C, C++' 카테고리의 다른 글

C언어 pthread 사용 방법  (0) 2014.03.05
포인터 배열 테스트 코드  (0) 2012.08.07
C코드 메모리릭 잡기  (0) 2012.07.18
마방진 원리 및 문제  (4) 2012.05.19
Mangled Name  (0) 2012.05.13


MFC코드 기반 디렉토리 생성과 삭제 코드 입니다.

디렉토리 삭제 참조: http://sanaigon.tistory.com/120

  
//
// 디렉토리 생성
// 디렉토리 생성 성공: TRUE, 실패: FALSE 반환
// 함수 사용예: CreateDir(_T("C:\\dir_name\\"));
//
BOOL CreateDir(CString dir)
{
	CFileFind file;
	CString strFile = _T("*.*");
	BOOL bResult = file.FindFile(dir + strFile);

	if(!bResult)
	{
		bResult = CreateDirectory(dir, NULL);
	}

	return bResult;
}

//
// 디렉토리 삭제
// 디렉토리내에 존재하는 하위 폴더 및 모든 파일 삭제
// 함수 사용예: DeleteDir(_T("C:\\dir_name\\*.*"));
//
BOOL DeleteDir(CString dir)
{
	if(dir == _T(""))
	{
		return FALSE;
	}

	BOOL bRval = FALSE;
	int nRval = 0;
	CString szNextDirPath = _T("");
	CString szRoot = _T("");
	CFileFind find;

	// Directory가 존재 하는지 확인 검사
	bRval = find.FindFile(dir);

	if(bRval == FALSE)
	{
		return bRval;
	}

	while(bRval)
	{
		bRval = find.FindNextFile();

		// . or .. 인 경우 무시한다.
		if(find.IsDots() == TRUE)
		{
			continue;
		}

		// Directory 일 경우
		if(find.IsDirectory())
		{
			szNextDirPath.Format(_T("%s\\*.*"), find.GetFilePath());

			// Recursion function 호출
			DeleteDir(szNextDirPath);
		}

		// file일 경우
		else
		{
			//파일 삭제
			::DeleteFile(find.GetFilePath());
		}
	}

	szRoot = find.GetRoot();
	find.Close();

	Sleep(1);
	bRval = RemoveDirectory(szRoot);

	return bRval;
}



  • 마방진?

마방진이란 아래 그림과 같이 가로, 세로, 및 대각선에 있는 각각의 합이 같도록 배열한 것을 의미



  • 문제

위의 3 * 3 마방진을 구현하시오. 화면에 출력.

(For문 또는 While문, if문, 배열, 함수 사용)


  • 원리



  • 코드보기

마방진.cpp


헤더파일: App Class (.h)

  
// 뮤텍스 핸들 선언
protected:
	HANDLE m_hMutex;


소스파일: App Class (.cpp)

  
#define STRING_MUTEX_NAME		"뮤텍스 이름"
#define STRING_CAPTION_NAME		"프로그램 이름(Caption)"

//
// 생성자 뮤텍스 핸들 초기화
//
CClassApp::CClassApp()
{
	m_hMutex = NULL;
}

//
// 프로그램 중복실행 방지
//
CClassApp::InitInstance()
{
	if(NULL == ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, STRING_MUTEX_NAME))
	{
		// 중복실행이 아니므로 뮤택스 생성
		m_hMutex = ::CreateMutex(NULL, FALSE, STRING_MUTEX_NAME);
		if(m_hMutex == NULL)
		{
			AfxMessageBox(_T("프로그램 실행에 실패하였습니다."), MB_OK|MB_ICONEXCLAMATION);
			return FALSE;
		}
	}
	else
	{
		// 중복실행일 경우 기존 열려있는 창을 맨 앞으로 보여줌
		// 만약, 창이 최소화 상태라면 최소화 상태 해제
		CWnd*  pWnd = NULL;
		pWnd = CWnd::FindWindow(NULL, STRING_CAPTION_NAME);
		if(pWnd != NULL)
		{
			if(pWnd->IsIconic()) 
				pWnd->ShowWindow(SW_RESTORE);
			else     
				pWnd->SetForegroundWindow();
		}
		else
		{
			pWnd = CWnd::FindWindow(NULL, STRING_CAPTION_NAME);
			if(pWnd != NULL)
			{
				if(pWnd->IsIconic()) 
					pWnd->ShowWindow(SW_RESTORE);
				else     
					pWnd->SetForegroundWindow();
			}
		}
		return FALSE;
	}  

	// 기존 코드
}


'개발이야기 > MFC' 카테고리의 다른 글

fopen_s 파일읽기와 저장  (0) 2012.06.14
MFC 디렉토리 생성과 삭제  (0) 2012.05.25
일정 날짜 기준 로그파일 제거  (1) 2012.05.17
로컬 IP주소 얻기  (0) 2012.05.17
유니코드 ↔ 안시 변환함수  (0) 2012.05.16


본 코드는 30일이 지난 로그파일을 삭제합니다.

강조된 부분의 코드수정을 통해 제거 일정 기준을 변경시킬 수 있습니다.

  
//
// 폴더 경로 얻음
//
CString GetFolderPath(CString path)
{
	if(path.Right(1) == _T('\\'))
	{
		return path;
	}
	else
	{
		CString strFolderPath;
		strFolderPath = path.Left(path.ReverseFind(_T('\\'))+1);
		return strFolderPath;
	}
}

//
// 일정 날짜 기준 로그파일 제거
//
void DeleteLogfiles()
{
	// 로그파일 형태 20111116.log
	// 30일 기준 로그파일 삭제
	CTime CurTime = CTime::GetCurrentTime();
	CTime Day30Time;
	Day30Time = CurTime - CTimeSpan(30, 0, 0, 0); // 일, 시, 분, 초
	CString path, file_path, file_name;
	path.Format(_T("%s*.*"), _("로그디렉토리경로\\"));
	CFileFind finder;
	BOOL bRes;
	bRes = finder.FindFile(path);
	while(bRes)
	{
		bRes = finder.FindNextFile();
		if(!finder.IsDirectory()) // 폴더가 아니고 파일일 경우
		{
			// 삭제 상태 변수 초기화
			bool bDelete = false;
			// 현재 정보가 파일인 경우, file_data.cFileName에 파일이름이 들어있다.
			file_name = finder.GetFileName();
			file_path = GetFolderPath(path) + file_name;
			CString strLogDate;
			strLogDate = file_name.Left(8);
			// 문자 길이가 맞고, 숫자로만 구성되었는지 확인
			if(strLogDate.GetLength() == 8 && IsStringDigit(strLogDate))
			{
				int nLogYear = _ttoi(strLogDate.Left(4));
				int nLogMonth = _ttoi(strLogDate.Mid(4, 2));
				int nLogDay = _ttoi(strLogDate.Right(2));
				CTime LogTime(nLogYear, nLogMonth, nLogDay, 0, 0, 0, 0);
				if(LogTime < Day30Time)
					bDelete = true;
			}
			else
			{
				// 예외사항
				bDelete = true;
			}    
			if(bDelete)
			{
				// 30일이 지난 로그파일은 삭제
				DeleteFile(file_path);
			}
		}
	}
}



로컬 IP주소를 얻을 수 있는 함수입니다.

CString 타입으로 _T("xxx.xxx.xxx.xxx")와 같이 반환합니다.

  
#include <afxsock.h>
CString GetIPAddress()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	char name[255];
	CString ip; // 여기에 lcoal ip가 저장됩니다.
	PHOSTENT hostinfo;
	wVersionRequested = MAKEWORD( 2, 0 );
	if ( WSAStartup( wVersionRequested, &wsaData ) == 0 ) 
	{
		if( gethostname ( name, sizeof(name)) == 0)
		{
			if((hostinfo = gethostbyname(name)) != NULL)
			{
				ip = inet_ntoa (*(struct in_addr *)*hostinfo->h_addr_list);
			}
		}      
		WSACleanup( );
	} 
	return ip;
}



VS2008을 사용하게되면서 UNICODE와 ANCI간의 스트링 상호변환 함수를 정리한 것입니다.

프로젝트 문자집합 속성(유니코드, 멀티바이트)에 상관없이 변환 가능합니다.

  
//
// CString → Char
//
char* StringToChar(CString str)
{
	char *szStr = NULL;
#if defined(UNICODE) || defined(_UNICODE)
	int nLen = str.GetLength() + 1;
	TCHAR *tszTemp = NULL;
	tszTemp = new TCHAR[nLen];
	memset(tszTemp, 0x00, nLen*sizeof(TCHAR));
	_tcscpy(tszTemp, str);
	// Get size (실제사용되는바이트사이즈)
	int nSize = WideCharToMultiByte(CP_ACP, 0, tszTemp, -1, NULL, NULL, NULL, NULL);
	szStr = new char[nSize];
	memset(szStr, 0x00, nSize);
	WideCharToMultiByte(CP_ACP, 0, tszTemp, -1, szStr, nSize, NULL, NULL);
	if(tszTemp)
	{
		delete [] tszTemp;
		tszTemp = NULL;
	}
#else
	int nLen = str.GetLength() + 1;
	szStr = new char[nLen];
	memset(szStr, 0x00, nLen);
	strcpy(szStr, str);
#endif
	return szStr;
}

//
// CString → TCHAR
//
TCHAR* StringToTCHAR(CString str)
{
	TCHAR *tszStr = NULL;
	int nLen = str.GetLength() + 1;
	tszStr = new TCHAR[nLen];
	memset(tszStr, 0x00, nLen*sizeof(TCHAR));
	_tcscpy(tszStr, str);

	return tszStr;
}

//
// Char → CString
//
CString CharToString(char *str)
{
	CString cStr;
#if defined(UNICODE) || defined(_UNICODE)
	int nLen = strlen(str) + 1;
	TCHAR *tszTemp = NULL;
	tszTemp = new TCHAR[nLen];
	memset(tszTemp, 0x00, nLen*sizeof(TCHAR));
	MultiByteToWideChar(CP_ACP, 0, str, -1, tszTemp, nLen*sizeof(TCHAR));
	cStr.Format(_T("%s"), tszTemp);
	if(tszTemp)
	{
		delete [] tszTemp;
		tszTemp = NULL;
	}
#else
	cStr.Format("%s", str);
#endif
	return cStr;
}

//
// Char → TCHAR
//
TCHAR* CharToTCHAR(char *str)
{
	TCHAR *tszStr = NULL;
#if defined(UNICODE) || defined(_UNICODE)
	int nLen = strlen(str) + 1;
	tszStr = new TCHAR[nLen];
	memset(tszStr, 0x00, nLen*sizeof(TCHAR));
	MultiByteToWideChar(CP_ACP, 0, str, -1, tszStr, nLen*sizeof(TCHAR));
#else
	int nLen = strlen(str) + 1;
	tszStr = new TCHAR[nLen];
	memset(tszStr, 0x00, nLen*sizeof(TCHAR));
	_tcscpy(tszStr, str);
#endif
	return tszStr;
}

//
// TCHAR → CString
//
CString TCHARToString(TCHAR *str)
{
	CString cStr;
	cStr.Format(_T("%s"), str);
	return cStr;
}

//
// TCHAR → Char
//
char* TCHARToChar(TCHAR *str)
{
	char *szStr = NULL;
#if defined(UNICODE) || defined(_UNICODE)
	int nSize = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, NULL, NULL, NULL);
	szStr = new char[nSize];
	memset(szStr, 0x00, nSize);
	WideCharToMultiByte(CP_ACP, 0, str, -1, szStr, nSize, NULL, NULL);
#else
	int nLen = strlen(str) + 1;
	szStr = new char[nLen];
	memset(szStr, 0x00, nLen);
	strcpy(szStr, str);
#endif
	return szStr;
}


'개발이야기 > MFC' 카테고리의 다른 글

일정 날짜 기준 로그파일 제거  (1) 2012.05.17
로컬 IP주소 얻기  (0) 2012.05.17
폴더선택 다이얼로그와 초기폴더경로 설정  (0) 2012.05.15
MFC Control 오픈소스 모음  (0) 2012.05.13
3D 다이아몬드  (0) 2012.05.13