Share Tweet

이 글의 QR 코드입니다.
CArray 는 MFC 에서 제공하는 STL::vector 과 유사한 배열관련 자료형 템플릿입니다.
개발자가 의외로 잘 사용하지 않는 Append 함수를 설명 드립니다.
Append 함수의 설명 |
INT_PTR Append(const CArray& src);
src 에 해당하는 Array 를 복사하여 추가한다.
사용예 |
소스코드
typedef CArray< int, int > IntArray;
// 배열에 값을 추가한다. IntArray a; a.Add( 7 ); a.Add( 8 ); a.Add( 9 );
IntArray b; b.Add( 0 ); b.Add( 1 ); b.Add( 2 );
// b 를 a 에 추가한다. a.Append( b );
for( int i = 0; i < a.GetSize(); i++ ) { TRACE( "a[%d] = %d \n" , i, a[i] ); } |
결과
a[0] = 7 a[1] = 8 a[2] = 9 a[3] = 0 a[4] = 1 a[5] = 2 |
이 글의 QR 코드입니다.
개요.. |
일반적으로STL의 vector는 가장 뒤에 원소를 넣는 방법(push_back)이 속도가 가장 빠릅니다.
하지만 중간에 넣는 경우(insert)가 발생할 때 Stl - vector(벡터)에서 중간에 원소를 삽입하는 방법을 설명드립니다.
소스코드 |
{ IntVector v; v.push_back( 1 ); v.push_back( 2 ); v.push_back( 3 ); v.insert( v.begin(), 0 );
for( int i = 0; i < v.size(); i++ ) { static int nTrace = 0; TRACE( "%s(%d) : v = %d \n" , __FILE__, __LINE__, v[i] ); } }
|
결과.. |
이 글의 QR 코드입니다.
개요.. |
Visual Studio 듀얼코어, 쿼드코어를 활용한 프로그래밍 컴파일(Complie) 속도 향상방법을 소개해드립니다.
기본옵션으로 Visual Studio 2005, 2008 에서는 컴파일 과정에서는 멀티코어 CPU 를 지원하지 않습니다.
따라서 듀얼 코어에서 컴파일을 하면 50%의 CPU 만을 사용하여 멀티 코어 CPU를 사용하는 장점이 없는데 다음과 같은 과정으로 멀티 코어 시피유를 최대한 활용할 수 있습니다.
많은 논란 및 의의가 제기되어 다시 측정하고 작성해봅니다.
관심 감사드려요~
컴파일 속도 향상 방법 |
1. 프로젝트 속성 -> C/C++ -> 명령줄 에 "/MP" 을 입력합니다.
2. 컴파일 및 빌드를 하여 CPU를 100% 사용하는지 확인합니다.
6만 라인 프로젝트의 컴파일 속도 테스트 예 |
아래와 같은 6만 라인 프로젝트의 경우 다음과 같은 결과를 나타냅니다.
ClubPoker.vcproj 62429 42426
/MP 옵션 사용 여부 | 컴파일 & 빌드 속도 |
사용 | 24초 |
미사용 | 33초 |
옵션의 변경만으로 약 30% 가량의 컴파일 & 빌드 속도 향상을 이루었고, 코드의 양이 크면 클수록 그 효과는 크게 작용됩니다.
2010-10-20 업데이트 내용 |
증분링크 및 MP 옵션이 잘 안 되신다는 분들이 많아서 다시 올려드립니다.
- 비쥬얼 스튜디오 2005 Standard 버전을 사용중입니다.
- 2008 버전에서는 정상작동 여부는 확인 못하였습니다.
- /MP 옵션이 추가된 모습
- 증분 링크를 설정 / 사용하는 모습
- 미리 컴파일 된 헤더 (PCH) 를 사용하는 모습
- 컴파일 / 빌드 시작시 어떤 오류도 발생하지 않는 모습
- /MP옵션을 사용 후 컴파일 도중 모든 멀티 CPU를 사용하는 모습
- /MP옵션을 끄고 컴파일 도중 싱글 CPU를 사용하는 모습
빌드 속도 측정 결과 |
- 멀티 프로세서를 사용하는 /MP 옵션 사용시 전체 빌드 시간 (쿼드코어) : 15초
- 싱글 코어만 사용하여 /MP 미사용 시 전체 빌드시간 : 30초 |
죄송합니다만 2008 에서의 정확한 작동여부는 제가 직접 테스트해 보지 못하여 말씀 드리기는 어렵습니다.
이 글의 QR 코드입니다.
개요.. |
Visual Studio 2005 에서 듀얼코어, 멀티 코어 프로세서(CPU)를 이용한 컴파일, 빌드방법을 설명 드립니다.
근본적으로 Visual Studio 에서는 컴파일 또는 빌드 시 2개 이상의 프로세서(CPU)를 이용하지 못합니다. 하지만 2개 이상의 프로젝트 솔루션을 가지고 있을 경우에는 솔루션 빌드라는 방법을 통하여 멀티코어 CPU를 활용할 수 있습니다.
방법 |
1. 도구 -> 옵션에 병렬 프로젝트 빌드 수를 설정합니다.
일반적으로 2 로 값을 설정하면 됩니다.
2. 솔루션 빌드를 합니다.
2-1. 부분적으로 컴파일 또는 빌드를 할 경우 프로젝트를 선택 후 빌드를 합니다.
이 글의 QR 코드입니다.
개요.. |
프로그래밍 코드를 작성하다 보면 임시, 디버그용 코드를 종종 만들게 됩니다.
하지만 문제는 깜빡하고 이 임시 코드를 삭제하지 않고 릴리즈를 하는 경우가 발생하면 오류 또는 치명적 보안, 버그의 원인이 되는 경우가 많기 때문에 이와 같은 실수를 방지하는 방법을 알려드립니다.
모든 임시코드에 이와 같은 방법을 사용하기에는 귀찮은 점이 매우 많습니다만 신중을 기하거나 기간이 걸리는 경우에는 반드시 이러한 자신만의 방법을 사용하여 실수를 최대한 방지하면 좋겠습니다.
임시코드를 작성한 예)
방법 |
1. 아래와 같이 특정 식별 문자를 삽입하여 쉽게 찾고 잊지 않도록 합니다.
항상 같은 표식을 하면 더더욱 좋습니다.
//####### //####### //#######
if( m_UserOption[i].hi_low_swing == USER_SELECT_SWING ) { m_UserOption[i].card_point_hi = FULLHOUSE_POINT; m_UserOption[i].card_point_low = TOP_6; }
//####### //####### //####### |
2. #ifdef _DEBUG 과 같은 구문을 사용하여 릴리즈 버전에서는 디버그 코드가 배포되지 않도록 합니다.
조금 귀찮기는 하지만 이 방법이 정석이기는 하지요.
#ifdef _DEBUG if( m_UserOption[i].hi_low_swing == USER_SELECT_SWING ) { m_UserOption[i].card_point_hi = FULLHOUSE_POINT; m_UserOption[i].card_point_low = TOP_6; } #endif |
이 글의 QR 코드입니다.
개요.. |
CString 또는 char* 의 format 함수 에서 음수, 양수 부호(+,-)를 포함하여 숫자를 문자열(스트링)로 변환하는 방법에 대하여 알아봅니다.
방법 |
format 함수에서 %+d 와 같이 % 코드 옆에 + 부호를 붙여줍니다.
INT64 n = 123456;
CString str; str.Format( "%+I64d", n ); // str "+123456" str.Format( "%+I64d", -n ); // str "-123456"
INT n2 = 123456;
str.Format( "%+d", n2 ); // str "+123456" str.Format( "%+d", -n2 ); // str "-123456"
|
이 글의 QR 코드입니다.
개요.. |
STL map 사용시 할당 안된(초기화 되지 않은) 값의 초기값은 무엇인지 알아봅니다.
코드 출처 : CK IPod, IPhone Game Engine
Case 1 포인터 맵의 경우 |
아래와 같이 포인터로 구성된 map 의 경우에는 할당 안된 값은 NULL로 초기화되어 있습니다.
int n1 = 1; int n2 = 2; map< int, int* > _map;
_map[1] = &n1; _map[2] = &n2;
int* p1 = _map[1]; // *p1 = 1 int* p2 = _map[2]; // *p2 = 2 int* p3 = _map[3]; // *p3 = NULL
|
Case 2 일반 변수의 경우 |
아래와 같이 일반 변수로 구성된 map 의 경우에는 할당 안된 값은 0으로 초기화되어 있습니다.
int n1 = 1; int n2 = 2; map< int, int > _map;
_map[1] = n1; _map[2] = n2;
int p1 = _map[1]; // p1 = 1 int p2 = _map[2]; // p2 = 2 int p3 = _map[3]; // p3 = 0
|
이 글의 QR 코드입니다.
개요.. |
#pragma pack은 구조체의 저장 크기를 결정하는 명령어입니다.
즉 BYTE 의 멤버 변수를 1바이트로 실제 저장하는가 아니면 2, 4바이트로 저장하는가를 설정합니다.
하지만 이 명령어 중 일부는 윈도우와 GCC, G++ 의 작동이 틀리기 때문에 모두 작동이 가능한 소스코드를 설명합니다.
윈도우에서만 통하는 코드 |
#pragma pack(push) <---- 이부분에서 GCC는 오류가 발생합니다. #pragma pack(2) struct KBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; }; #pragma pack(pop)
|
GCC, G++ 에서도 통하는 코드 |
#pragma pack(push,2) <---- 이부분에서 GCC도 컴파일이 성공합니다. struct KBITMAPFILEHEADER { WORD bfType; DWORD bfSize; WORD bfReserved1; WORD bfReserved2; DWORD bfOffBits; }; #pragma pack(pop)
|
이 글의 QR 코드입니다.
개요.. |
Visual Studio 2005의 MFC 다이얼로그(Dialog)에 액티브액스 형식의 웹 브라우저 (Web Browser) 컨트롤을 삽입하는 방법을 설명 드립니다.
삽입된 컨트롤은 인터넷 익스플로러와 동일한 역할을 수행하여 다양한 용도로 사용할 수 있습니다.
추가방법 |
ActiveX 컨트롤을 삽입합니다.
Microsoft Web Browser ActiveX 컨트롤을 삽입합니다.
멤버 변수를 추가합니다.
이 글의 QR 코드입니다.
개요.. |
F10 및 시스템 단축키 비활성화 또는 변경하는 방법 (VC++)을 설명해드립니다.
일반적으로 게임이나 메뉴를 사용하지 않는 어플리케이션을 개발할 경우에는 F10 단축키를 사용할 필요가 없습니다.
특히 게임을 개발 할 경우 F10 키를 사용한다면 게임화면이 멈추는 현상이 발생되기 때문에 해당 기능을 비활성화 하는 방법을 설명 드립니다.
방법 |
1. OnSysCommand 를 재정의한다.
2. SC_KEYMENU 기능을 비활성화 한다...
해당 소스코드.. |
/** @class CMainWnd @date 2006/5/3 @author 채경석(kyuseo99@chol.com) @brief */ class CMainWnd : public CWnd, public CLauncher { DECLARE_DYNAMIC(CMainWnd)
public: CMainWnd(); virtual ~CMainWnd();
BOOL Create( LPCSTR szDllFileName, int nClientLauncherBuild
protected: DECLARE_MESSAGE_MAP() virtual BOOL PreCreateWindow(CREATESTRUCT& cs); virtual BOOL PreTranslateMessage(MSG* pMsg); virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags); afx_msg void OnDestroy(); afx_msg void OnPaint(); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
protected: BOOL m_bRelayEvent; ///< 릴레이이벤트를할수있는가? };
void CMainWnd::OnSysCommand(UINT nID, LPARAM lParam) { // X 버튼을WM_CLOSE 로변환한다. if( nID == SC_CLOSE ) { PostMessage( WM_CLOSE ); } else if( nID == SC_KEYMENU ) { } else { CWnd::OnSysCommand(nID, lParam); } }
|
이 글의 QR 코드입니다.
개요.. |
RTTI 기능은 실행시간에 객체 타입정보, 클래스 정보를 알 수 있는 C++ 의 고급 기능입니다.
하지만 해당 기능을 사용하지 않는다면 해당 기능을 제거함으로써 약간의 속도, 코드 크기의 향상을 기대 할 수 있습니다. (하지만 컴퓨터 성능의 증가로 인하여 사실상 큰 성능향상은 없습니다.)
C++ 런타임 형식 정보 사용 (RTTI) 사용 해제를 통한 속도 및 코드 크기 감소하는 옵션 설정 변경방법을 알려드립니다.
방법 |
아래와 같이 런타입 형식 정보 사용 설정을 아니오 로 변경합니다. (/GR-)
참고 |
RTTI 를 사용할 경우 실행 파일의 용량
RTTI 를 사용하지 않을 경우 실행 파일의 용량
겨우 약 2%의 크기 감소를 가져왔네요. ^^;
이 글의 QR 코드입니다.
개요.. |
윈도우 소켓 (WinSock) 함수인 주소를 IP어드레스로 변환할 수 있는 gethostbyname gethostname gethostbyaddr 함수의 사용 예 및 주의사항을 설명해드립니다.
gethostbyname 함수는 "www.tk.co.kr" 와 같은 영문 이름을 "211.41.11.xxx"와 같은 컴퓨터가 이해할 수 있는 숫자 기반의 주소로 변환하는 함수입니다. 하지만 인터넷을 이용하기 때문에 종종 NULL 값이 리턴이 되므로 반드시 그것에 대한 예외 처리를 해야 프로그램이 죽는 현상을 막을 수 있습니다.
예제 코드 |
in_addr in; LPHOSTENT lphost = gethostbyname( "tk.co.kr" ); if( lphost != NULL ) { in.s_addr = ( ( LPIN_ADDR ) lphost->h_addr )->s_addr;
CString strAddr = inet_ntoa( in ); // 변환된 주소를 사용한다. } else { in.s_addr = 0;
int error = WSAGetLastError(); // 오류코드를 처리한다. }
|
리턴된 error 값은 아래와 같은 오류조회 툴로 확인할 수 있습니다.
오류 코드 : 출처 MSDN 참고 |
Error code | Description |
WSANOTINITIALISED | A successful WSAStartup call must occur before using this function. |
WSAENETDOWN | The network subsystem has failed. |
WSAHOST_NOT_FOUND | An authoritative answer host was not found. |
WSATRY_AGAIN | A nonauthoritative host was not found, or the server failure. |
WSANO_RECOVERY | A nonrecoverable error occurred. |
WSANO_DATA | A valid name exists, but no data record of the requested type exists. |
WSAEINPROGRESS | A blocking Winsock call is in progress, or the service provider is still processing a callback function. |
WSAEFAULT | The name parameter is not a valid part of the user address space. |
WSAEINTR | The socket was closed. |
이 글의 QR 코드입니다.
개요.. |
Visual Studio 듀얼코어, 쿼드코어를 활용한 프로그래밍 컴파일(Complie) 속도 향상방법을 소개해드립니다.
기본옵션으로 Visual Studio 2005, 2008 에서는 컴파일 과정에서는 멀티코어 CPU 를 지원하지 않습니다.
따라서 듀얼 코어에서 컴파일을 하면 50%의 CPU 만을 사용하여 멀티 코어 CPU를 사용하는 장점이 없는데 다음과 같은 과정으로 멀티 코어 시피유를 최대한 활용할 수 있습니다.
컴파일 속도 향상 방법 |
1. 프로젝트 속성 -> C/C++ -> 명령줄 에 "/MP" 을 입력합니다.
2. 컴파일 및 빌드를 하여 CPU를 100% 사용하는지 확인합니다.
6만 라인 프로젝트의 컴파일 속도 테스트 예 |
아래와 같은 6만 라인 프로젝트의 경우 다음과 같은 결과를 나타냅니다.
ClubPoker.vcproj 62429 42426
/MP 옵션 사용 여부 | 컴파일 & 빌드 속도 |
사용 | 24초 |
미사용 | 33초 |
옵션의 변경만으로 약 30% 가량의 컴파일 & 빌드 속도 향상을 이루었고, 코드의 양이 크면 클수록 그 효과는 크게 작용됩니다.
이 글의 QR 코드입니다.
개요.. |
응용 프로그램 인증서는 인터넷을 통한 프로그램(OCX, DLL, EXE) 배포 시 필요한 파일입니다. 가격과 제공 회사가 다양하여 정리를 해 보았습니다.
개인적으로는 펀그랩, 티케이 게임에서 사용중인 가격이 저렴한 TrustBiz 인증서를 추천해드립니다.
응용 프로그램 인증서(Active X 인증서)가 장착된 파일을 실행하는 모습
나이스서트 |
한국 전자인증 |
| |||||||||||||||
|
애니서트 |
써트코리아 |
코모도 코리아 |
현재 응용프로그램 인증서는 판매하지 않음
|
|
|
|
|
|
|
* 부가세 별도
프로그램 인증 가격 | ||
Digitally sign 32-bit . | 1년 인증 8만5천원 | 5000만원 배상 |
이 글의 QR 코드입니다.
개요.. |
숫자를 섞어서 중복 없이 산출하는 방법 (Shuffle Number)을 소개해드립니다.
주로 10개의 문제를 중복 없이 뒤섞어 문제 발행을 하거나, 학생을 무작위로 재배치 하는데 사용이 되는 코드입니다.
방법 |
소스코드:
// 1 ~ 10 까지의숫자를중복없이뒤섞어순차적으로뽑아내는방법 IntArray m_iaShuffleQ; ///< 섞여진문제
// 숫자를입력한다. for( int i = 0; i < 10; i++ ) m_iaShuffleQ.Add( i+1 );
// 숫자를섞는다. (내부적으로swap 으로섞는다.) m_iaShuffleQ.Shuffle();
// 순차적으로숫자를뽑아낸다. for( int i = 0; i < 10; i++ ) { TRACE( " %d ", m_iaShuffleQ[i] ); } |
출력결과:
9 7 2 10 3 4 1 5 8 6 5 2 1 4 8 10 9 7 3 6 9 8 10 3 5 2 6 4 7 1 6 5 7 4 10 1 9 8 3 2 2 1 5 4 9 7 8 3 10 6 6 9 1 5 7 8 2 4 3 10 6 5 4 9 3 10 1 7 8 2 8 3 6 9 1 7 2 10 5 4 6 3 9 7 5 10 4 8 2 1 3 6 10 1 8 2 9 5 7 4 1 2 3 10 7 6 8 9 4 5 1 5 3 8 4 2 6 9 10 7 6 2 7 8 5 4 10 1 9 3 10 1 2 6 9 8 7 5 4 3 |
참고 사항 |
위 코드에서 사용한 IntArray 템플릿 클래스 및 사용한 Shuffle 함수는 다음과 같습니다.
template< class TYPE, class ARG_TYPE > class CPArray : public CArray< TYPE, ARG_TYPE > { public: using CArray2< TYPE, ARG_TYPE >::GetCount;
INT_PTR GetCount( ARG_TYPE t, INT_PTR nStart = 0 ) const; ///< 주어진인자의총개수를구한다. INT_PTR Find( ARG_TYPE t, INT_PTR nStart = 0 ) const; ///< 주어진인자의첫번째것을찾는다. INT_PTR Delete( ARG_TYPE t, INT_PTR nStart = 0 ); ///< 주어진인자에해당하는것을지운다. (리턴:지워진개수)
TYPE& GetHead(); const TYPE& GetHead() const; TYPE& GetTail(); const TYPE& GetTail() const;
TYPE RemoveHead(); TYPE RemoveTail();
void Sort( BOOL bDesc = FALSE ); ///< 정렬한다. ( 해당TYPE 은>, <, == 가정의되어있어야한다. ) void Sort( int ( __cdecl *Compare )( const void *pArg1, const void *pArg2 ) ); ///< 외부함수로정렬한다.
void Shuffle( int nCount = 5 ); ///< 섞는다.
// 자체메모리할당된포인터전용함수 void DeleteRemovtAt( INT_PTR nIndex, INT_PTR nCount = 1 ); ///< delete 하고RemoveAt 한다. void DeleteRemoveAll(); ///< delete 하고RemoveAll 한다.
protected: static int CompareAsc( const void *pArg1, const void *pArg2 ); static int CompareDesc( const void *pArg1, const void *pArg2 ); };
…………………..…………….…. 중략 …………………..…………….….
typedef CPArray< int, int > IntArray;
template< class TYPE, class ARG_TYPE > inline void CPArray< TYPE, ARG_TYPE >::Shuffle( int nCount ) { for( INT_PTR i = 0; i < nCount; i++ ) { for( INT_PTR j = 0; j < GetSize(); j++ ) { Swap( m_pData[j], m_pData[ Rand( GetSize() ) ] ); } } } |
이 글의 QR 코드입니다.
개요.. |
숫자가 1조를 넘어가면 1e+12 , 1e+13 과 같은 문자가 포함된 큰 숫자로 표현이 됩니다.
(특히 PHP 와 같은 스크립트 코드)
이 변수를 다시 int 또는 int64 형식으로 변경하는 방법을 알려드립니다.
atoi로 큰 숫자를 변환한 경우 |
n1 = 1 로 원하는 결과가 나오지 않는다.
INT64 n1 = _atoi64( "1E+12" ); ///< n1 = 1 |
atof 로 큰 숫자를 변환한 경우.. |
n2 = 1000000000000 로 원하는 결과가 나온다.
INT64 n2 = ( INT64 ) atof( "1E+12" ); ///< n2 = 1000000000000 |
이 글의 QR 코드입니다.
개요.. |
Visual Studio 2005 에서 듀얼코어, 멀티 코어 프로세서(CPU)를 이용한 컴파일, 빌드방법을 설명 드립니다.
근본적으로 Visual Studio 에서는 컴파일 또는 빌드 시 2개 이상의 프로세서(CPU)를 이용하지 못합니다. 하지만 2개 이상의 프로젝트 솔루션을 가지고 있을 경우에는 솔루션 빌드라는 방법을 통하여 멀티코어 CPU를 활용할 수 있습니다.
방법 |
1. 도구 -> 옵션에 병렬 프로젝트 빌드 수를 설정합니다.
일반적으로 2 로 값을 설정하면 됩니다.
2. 솔루션 빌드를 합니다.
2-1. 부분적으로 컴파일 또는 빌드를 할 경우 프로젝트를 선택 후 빌드를 합니다.
이 글의 QR 코드입니다.
개요.. |
릴리즈 버전의 프로그램 특히 게임 서버 프로그램이 가끔 죽는(다운되는) 현상은 버그를 찾기가 매우 어렵지만, 다행히 SEH 관련 프로그래밍 기능을 활용하면 Call Stack 가 검출되어 프로그램의 오류를 검출하기가 조금은 쉬워집니다.
아주 오래된 프로그램인 닥터 왓슨(drwtsn32.exe)은 콜스택을 남겨서 오류가 발생한 함수의 위치는 알 수 있지만 조금 더 정확한 디버깅 정보가 남으면 오류를 잡기가 훨씬 쉬워지겠지요.
이번에 개발한 PMango Game Engine / Fungrep Framework SEH은 Visual C++ Runtime Error 오류 및 액세스 위반이 발생한 프로그램 소스코드의 함수뿐만 아니라 파일의 라인까지 기록이 남기 때문에 조금 더 쉽고 정확하게 오류를 추적하고 디버그를 할 수 있습니다.
프로그램 문의 : ks@tk.co.kr
라인 정보까지 검출된 모습의 예)
CCHServerRoom::NextTurn
( d:\fungrepproject\holdem\holdemserver\holdemserver\chserverroomgameprocess.cpp:1095 )
SEH 클래스의 모습 |
/** @class CSSeTranslator @date 2008/10/24 @author 채경석(kyuseo99@chol.com) @brief 서버에서사용하는SeTranslator 닥터왓슨과ppd 파일을이용하면Call Stack 를저장이가능하지만Visual C++ Runtime Error 발생시 메세지박스가발생하여서버가죽지도않고닥터왓슨로그도기록되지않는현상을대처하기위한클래스 기본저장위치는C:\\PMangoSEH.log 파일로저장된다. */ class CSSeTranslator : public CPSeTranslator { public: CSSeTranslator(); virtual ~CSSeTranslator();
void Set( LPCSTR szPpd, LPCSTR szFileName = "C:\\PMangoSEH.log" ); ///< 로그파일및ppd 정보를설정한다.
protected: virtual void OnSeTranslatorProc( DWORD dwExceptionCode, EXCEPTION_POINTERS* pException );
public: int m_n[10]; CString m_str[10];
protected: CTime m_tmStart; CString m_strPpd; CString m_strFileName; };
inline CSSeTranslator& GetSeTranslator() { static CSSeTranslator SSeTranslator;
return SSeTranslator; } |
PMango Game Engine / Fungrep Framework SEH 에서 남은 오류로그의 예 |
>>>>> PMango Game Engine / Fungrep Framework SEH Start ( ks@tk.co.kr ) >>>>>
응용 프로그램 예외 발생: 오류내용 : Exception : ACCESS_VIOLATION (0xc0000005), Address : 0x0040aa81 현재시간 : 2008/10/28, 18:16:43
*----> 실행 파일 정보 <----* 실행파일 : ClubGostopServerApp.exe PPD 파일 : ppd.ppd 현재경로 : D:\FungrepProject\ClubGostop\ClubGostopServer\App\release\
실행시간 : 2008/10/28, 18:16:42 경과시간 : 0일 00:00:01
빌드번호 : 1138 빌드시간 : 2008/10/28, 18:16:19
*----> 디버그 변수 <----* 숫자변수 : ( 0, 0, 0, 0, 0 ) : ( 0, 0, 0, 0, 0 ) 문자변수 0|1: ( | ) 문자변수 2|3: ( | ) 문자변수 4|5: ( | ) 문자변수 6|7: ( | ) 문자변수 8|9: ( | )
*----> 스택 역 추적 <----* Module : D:\FungrepProject\ClubGostop\ClubGostopServer\App\release\ClubGostopServerApp.exe (Base Address : 0x00400000, Exception Address : 0x0040aa81) FramePtr ReturnAd Param#1 Param#2 Param#3 Param#4 Function Name 00 00409C5F 00409F7D 037EFE80 010848CC 0108497C 00000003 CCHServerRoom::GameResult ( d:\fungrepproject\holdem\holdemserver\holdemserver\chserverroomgameprocess.cpp:1218 ) 01 00409F7D 00407BD4 00000001 7C95A360 01076C64 004C3424 CCHServerRoom::NextTurn ( d:\fungrepproject\holdem\holdemserver\holdemserver\chserverroomgameprocess.cpp:1095 ) 02 00407BD4 0040515E 00000001 00000003 00000000 00EA50F8 CCHServerRoom::BetGame ( d:\fungrepproject\holdem\holdemserver\holdemserver\chserverroomgameprocess.cpp:976 ) 03 0040515E 0041AE2F 01076C64 02E7C978 00EA50F8 037EFF30 CCHServer::OnBetGame ( d:\fungrepproject\holdem\holdemserver\holdemserver\chserveronpacket.cpp:235 ) 04 0041AE2F 0041A865 01076C64 02E7C978 02E7C898 00499FA8 CServer::OnReceive ( d:\fungrepproject\commonmultiserver\serveronpacket.cpp:625 ) 05 0041A865 00498403 030F4068 0049A0D9 02E7C928 00000008 CServer::OnReceive ( d:\fungrepproject\commonmultiserver\serveronpacket.cpp:123 ) 06 00498403 00468F7A 00000026 128FED4F 00000000 0116F880 CPServerUser::QueuedComplete ( :0 ) 07 00468F7A 0046901F 00000000 7C824829 0116F880 00000000 _callthreadstartex ( f:\sp\vctools\crt_bld\self_x86\crt\src\threadex.c:348 ) 08 0046901F 00000000 00468FA0 0116F880 00000000 00000000 _threadstartex ( f:\sp\vctools\crt_bld\self_x86\crt\src\threadex.c:326 )
>>>>> PMango Game Engine / Fungrep Framework SEH End ( ks@tk.co.kr ) >>>>> |
닥터왓슨 (drwtsn32.exe) 에서 남은 오류로그의 예 |
*----> 스레드 Id 0x1884의 상태 덤프 <----*
eax=00000002 ebx=00f14f54 ecx=00000001 edx=02ec322c esi=00f14ee4 edi=00000000 eip=00418f61 esp=02abfe20 ebp=02abfe28 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
함수: ClubGostopServerApp_lv0_1!CMission::Reset 00418f3c 0fb64608 movzx eax,byte ptr [esi+0x8] 00418f40 6bc02c imul eax,eax,0x2c 00418f43 8d5c3044 lea ebx,[eax+esi+0x44] 00418f47 e860020000 call ClubGostopServerApp_lv0_1!CPRater<unsigned char>::Lottery (004191ac) 00418f4c 83f801 cmp eax,0x1 00418f4f 75eb jnz ClubGostopServerApp_lv0_1!CMission::Reset+0x5e (00418f3c) 00418f51 0fb64608 movzx eax,byte ptr [esi+0x8] 00418f55 6bc02c imul eax,eax,0x2c 00418f58 8d443044 lea eax,[eax+esi+0x44] 00418f5c e8a0020000 call ClubGostopServerApp_lv0_1!CPRater<unsigned char>::GetLucky (00419201) 오류 -> 00418f61 8a00 mov al,[eax] ds:0023:00000002=?? 00418f63 88443709 mov [edi+esi+0x9],al 00418f67 47 inc edi 00418f68 83ff04 cmp edi,0x4 00418f6b 7ccf jl ClubGostopServerApp_lv0_1!CMission::Reset+0x5e (00418f3c) 00418f6d 8d5e0f lea ebx,[esi+0xf] 00418f70 33c0 xor eax,eax 00418f72 8bfb mov edi,ebx 00418f74 ab stosd 00418f75 8a4608 mov al,[esi+0x8] 00418f78 84c0 test al,al
*----> 스택 역 추적 <----* *** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\kernel32.dll - ChildEBP RetAddr Args to Child 02abfe28 00406362 00000000 00000001 00f05f6c ClubGostopServerApp_lv0_1!CMission::Reset+0x83 02abfeb0 0040628e 00f149ac 00f149ac 02abfee8 ClubGostopServerApp_lv0_1!CCGServerRoom::ProcessStartGame+0x81 02abfec0 00405747 00000000 00f149ac 00000000 ClubGostopServerApp_lv0_1!CCGServerRoom::ProcessGame+0x18 02abfee8 004091b4 00000002 0325ed1c 00483974 ClubGostopServerApp_lv0_1!CCGServerRoom::StartGame+0x139 02abff28 004133fe 247068d8 1fc46cc4 76994f60 ClubGostopServerApp_lv0_1!CCGServerRoom::OnGameLoop+0x338 02abff58 0041303c 0044fd66 00d35138 00000000 ClubGostopServerApp_lv0_1!CServer::OnGameLoopTimerThread+0x5b 02abff5c 0044fd66 00d35138 00000000 00f3e090 ClubGostopServerApp_lv0_1!CServer::GameLoopTimerThreadProc+0xc (FPO: [1,0,0]) 02abff78 0043c8b5 00d35204 1fc46c2c 00000000 ClubGostopServerApp_lv0_1!CPTimerThread::ThreadProc+0x54 02abffb0 0043c95a 00000000 7c824829 00f3e090 ClubGostopServerApp_lv0_1!_callthreadstartex+0x1b 02abffb8 7c824829 00f3e090 00000000 00000000 ClubGostopServerApp_lv0_1!_threadstartex+0x7f WARNING: Stack unwind information not available. Following frames may be wrong. 02abffec 00000000 0043c8db 00f3e090 00000000 kernel32!GetModuleHandleA+0xdf |
이 글의 QR 코드입니다.
개요.. |
C++ 프로그래밍을 하다 보면 몇 가지 기교(?)를 이용한 코딩을 하곤 합니다. 대표적인 것이 3항 연산자와 다양한 한 줄 연산입니다.
코드를 짧고 간결하게 만드는 것도 좋지만 가독성을 올리는 것을 최우선으로 코딩을 해야 좋은 코드가 나옵니다.
참고로 코딩 스타일은 개인 선호도에 따라서 틀리므로 제 생각이 절대적으로 옳지는 않습니다.
예1 |
if 구문과 ++ 연산자의 가독성을 비교 해봅니다.
(A) int nAutoPlayCount = 0; POSITION pos = m_listUser.GetHeadPosition(); while( pos != NULL ) { CFMServerUser* pUser = ( CFMServerUser* ) m_listUser.GetNext( pos );
if( pUser->IsAutoPlayFlag() == TRUE ) { nAutoPlayCount++;
if( nAutoPlayCount >= MAX_SEAT ) return TRUE; } }
(B) int nAutoPlayCount = 0; POSITION pos = m_listUser.GetHeadPosition(); while( pos != NULL ) { CFMServerUser* pUser = ( CFMServerUser* ) m_listUser.GetNext( pos );
if( pUser->IsAutoPlayFlag() == TRUE ) { if( ++nAutoPlayCount >= MAX_SEAT ) return TRUE; } } |
위 (B)코드를 보면 if( ++nAutoPlayCount >= MAX_SEAT ) 구문이 있습니다.
결코 어려운 코드는 아니지만 (A) 안이 더욱 가독성이 좋지 않은가요?
제가 작성하는 코드 중 일부분 |
이 글의 QR 코드입니다.
개요.. |
C++ 코드 작성시 불필요하게 들어간 코드와 잘못된 코딩스타일로 알기 어려운 코드를 수정해 봅니다.
군더더기와 코딩 스타일이 어려운 코드 |
아래와 같은 코드는 "break;"의 위치와 띄어쓰기 그리고 "CEffectBase::MODEBACK" 와 같이 클래스 내부에서 선언된 enum 을 클래스 명칭을 포함하여 작성하여 보기도 어렵고 코드의 크기도 커집니다.
CEffectBase* CEffectBase::GetEffect( CEffectBase::TYPE type, int wparam/*0*/, int lparam/*0*/ ) { CEffectBase* pEffect = NULL; switch( type ) { case CEffectBase::MODEBACK: : 불필요한 클래스 명칭 enum pEffect = new CEffectModeBack( wparam );
break; : 잘못된 줄 바꿈 case CEffectBase::START: pEffect = new CEffectStart;
break; : 잘못된 줄 바꿈 case CEffectBase::READY_GO: pEffect = new CEffectReadyGo;
break; case CEffectBase::WAIT: // Wait : 필요없는 주석 pEffect = new CEffectWait;
case CEffectBase::WAIT_NEW: // WaitNew : 필요없는 주석
pEffect = new CEffectWaitNew;
break;
|
수정된 코드 |
적절한 띄어쓰기와 불필요한 코드를 제거하여 수정하였습니다.
CEffectBase* CEffectBase::GetEffect( TYPE type, int wparam/*0*/, int lparam/*0*/ ) { CEffectBase* pEffect = NULL; switch( type ) { case MODEBACK: pEffect = new CEffectModeBack( wparam ); break;
case START: pEffect = new CEffectStart; break;
case READY_GO: pEffect = new CEffectReadyGo; break;
case WAIT: pEffect = new CEffectWait;
case WAIT_NEW: pEffect = new CEffectWaitNew; break; |
이 글의 QR 코드입니다.
개요.. |
저(Kyuseo)는 MS 에서 배포하는 Consolas Font Pack for Microsoft Visual Studio 2005 or 2008 폰트를 사용하는데
다른 프로그래머 분들께서는 어떤 글꼴, 폰트를 사용하시나요?
제가 사용하는 글꼴, 폰트의 모습 |
Visual Studio 글꼴, 폰트 변경 방법 |
도구 ->옵션 -> 글꼴 및 색 에서 변경합니다.
외국 프로그래머용 인기 폰트 |
출처 : http://www.kuro5hin.org/story/2004/12/6/11739/5249
이 글의 QR 코드입니다.
개요.. |
DrawText 함수는 다양한 DT_... 포맷을 가지고 다양한 출력을 할 수 있습니다.
특히 일반적으로 "김&정호 " 로 출력을 하면 "김정호" 로 원하지 않는 모양으로 출력이 되는 것을 해결하기 위해서는 DT_NOPREFIX 포맷을 사용하면 쉽게 해결됩니다.
실제 사용 모습 |
기타 DT_ 용법 |
/* * DrawText() Format Flags */ #define DT_TOP 0x00000000 #define DT_LEFT 0x00000000 #define DT_CENTER 0x00000001 #define DT_RIGHT 0x00000002 #define DT_VCENTER 0x00000004 #define DT_BOTTOM 0x00000008 #define DT_WORDBREAK 0x00000010 #define DT_SINGLELINE 0x00000020 #define DT_EXPANDTABS 0x00000040 #define DT_TABSTOP 0x00000080 #define DT_NOCLIP 0x00000100 #define DT_EXTERNALLEADING 0x00000200 #define DT_CALCRECT 0x00000400 #define DT_NOPREFIX 0x00000800 #define DT_INTERNAL 0x00001000
#if(WINVER >= 0x0400) #define DT_EDITCONTROL 0x00002000 #define DT_PATH_ELLIPSIS 0x00004000 #define DT_END_ELLIPSIS 0x00008000 #define DT_MODIFYSTRING 0x00010000 #define DT_RTLREADING 0x00020000 #define DT_WORD_ELLIPSIS 0x00040000 #if(WINVER >= 0x0500) #define DT_NOFULLWIDTHCHARBREAK 0x00080000 #if(_WIN32_WINNT >= 0x0500) #define DT_HIDEPREFIX 0x00100000 #define DT_PREFIXONLY 0x00200000 #endif /* _WIN32_WINNT >= 0x0500 */ #endif /* WINVER >= 0x0500 */ |
이 글의 QR 코드입니다.
개요.. |
와일드카드를 사용한 찾기 및 바꾸기 (VisualStudio) 방법을 설명드립니다.
사용예 |
변수명이 m_IdCtrl 로 시작이 되고 Create 함수를 사용한 코드를 찾는 방법은
찾을 내용에 "m_IdCtrl*.Create" 을 입력하고 와일드 카드 사용으로 찾으면
아래와 같은 결과를 얻을 수 있습니다.
D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventCollectionWnd.cpp(60): m_IdCtrl[0].Create( 125, 442, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventCollectionWnd.cpp(61): m_IdCtrl[1].Create( 125+150, 442, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventCollectionWnd.cpp(62): m_IdCtrl[2].Create( 125+300, 442, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventTodayRankingWnd.cpp(112): m_IdCtrlEvent1[i].Create( 90, 254+33*i, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventTodayRankingWnd.cpp(116): m_IdCtrlEvent2[i].Create( 316, 254+33*i, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventTodayRankingWnd.cpp(120): m_IdCtrlEvent3[i].Create( 543, 254+33*i, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventTodayRankingWnd.cpp(125): m_IdCtrlEvent1[0].Create( 90, 253, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventTodayRankingWnd.cpp(129): m_IdCtrlEvent2[0].Create( 316, 253, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Channel\EventTodayRankingWnd.cpp(133): m_IdCtrlEvent3[0].Create( 543, 253, this ); D:\FungrepProject\FunMatGo\FunMatGoClient\Room\ResultDialog.cpp(97): m_IdCtrl.Create( 20, 165, this ); |
와일드 카드 목록 |
출처 : MSDN
다음은 참조 목록에서 사용할 수 있는 와일드카드입니다.
식 | 구문 | 설명 |
임의의 단일 문자 | ? | 단일 문자를 찾습니다. |
모든 단일 숫자 | # | 단일 숫자를 찾습니다. 예를 들어, 7#은 71과 같이 7 뒤에 다른 숫자가 나타나는 숫자를 찾지만, 17은 찾지 않습니다. |
집합에 없는 문자 | [! ] | 집합에 지정되지 않은 단일 문자를 찾습니다. |
Escape | \ | 백슬래시(\) 뒤에 나오는 문자열을 리터럴로 간주하고 일치하는 항목을 찾습니다. 이 와일드카드를 사용하면 * 및 #과 같이 와일드카드 표기에 사용되는 문자를 찾을 수 있습니다. |
하나 이상의 문자 | * | 하나 이상의 문자를 찾습니다. 예를 들어, new*는 newfile.txt와 같이 "new"가 포함된 모든 텍스트를 찾습니다. |
문자 집합 | [ ] | 집합에 지정된 문자 중 하나를 찾습니다. |
이 글의 QR 코드입니다.
개요.. |
리스트 콘트롤(CListCtrl)은 GetTopIndex() 함수는 있지만 반대로 SetTopInex() 함수가 존재하지 않습니다.
SetTopInex 와 유사하게 작동되는 코드를 만들어 봅니다.
코드 |
CWnd 에 존재하는 Scroll 함수를 이용하면 SetTopInex와 유사한 코드를 만들 수 있습니다.
int nOldItem = m_nItem; int nScroll = m_lc.GetTopIndex();
m_lc.SetRedraw( FALSE ); m_lc.DeleteAllItems();
for( int i = 0; i < m_aQResult.GetSize(); i++ ) { QRESULT& r = m_aQResult[i];
m_lc.InsertItem( i, r.s[0] );
for( int j = 0; j < 20; j++ ) { m_lc.SetItemText( i, j, r.s[j] ); }
m_lc.SetItemText( i, 13, (r.n[11]==0||r.n[11]==5) ? "X" : "O" ); m_lc.SetItemText( i, 14, (r.n[12]==0||r.n[12]==5) ? "X" : "O" ); }
m_lc.SetRedraw( TRUE ); m_lc.SetItemState( nOldItem, LVIS_SELECTED, LVIS_SELECTED ); m_lc.Scroll( CSize( 0, (nScroll)*19) );
|
이 글의 QR 코드입니다.
개요.. |
VisaulStdio 2005 에서 "코드 요소가 읽기 전용이므로 추가 / 제거 작업을 할 수 없습니다." 라는 메시지가 발생하면서 다이얼로그에서 이벤트 자동화 코드가 작성이 안 되는 경우가 가끔 있습니다.
그 문제를 해결하기 위한 방법을 설명해드립니다.
해결방법 |
해당 프로젝트의 디렉터리에 있는 .NCB 파일을 삭제하면 해결이 됩니다.
이 글의 QR 코드입니다.
개요.. |
C++ 클래스 문법 중에서 멤버함수를 호출하는 방법은 다음과 같습니다.
올바른 문법
m_btnModeRanking.CreateCheck()
하지만 '.' 의 앞뒤로는 공백이 없어야 하는데 이상하게도 앞뒤에 공백이 있더라도 컴파일이 잘되더군요.
올바르지 않지만 컴파일이 잘 되는 문법
m_btnModeRanking.CreateCheck()
Visual Studio 2005 이상부터 되는것인지 모르겠네요.
코드 |
m_btnModeRanking.CreateCheck( &gsurUpNew02, CRect( 323, 517, 376, 535 ), CRect( 269, 517, 322, 535 ) );
m_btnBattle . CreateCheck( &gsurUpNew02, CRect( 323, 517, 376, 535 ), CRect( 269, 517, 322, 535 ) ); // 이상하게 띄어쓰기가 되어있는데 컴파일이 된다.
m_btnRule1vs1.CreateCheck( &gsurUpNew02, CRect( 323, 422, 376, 440 ), CRect( 269, 422, 322, 440 ) );
|
이 글의 QR 코드입니다.
개요.. |
C++ 에서 WAVEFORMATEX 구조체를 이용하여 사운드 파일의 출력 시간을 얻는 방법을 알려드립니다.
방법 |
시간 구하는 공식
int nPlayTime
= m_nDataSize / ( m_wfx.nSamplesPerSec / 8 * m_wfx.wBitsPerSample * m_wfx.nChannels );
사용예
WAVEFORMATEX m_wfx; ///< 웨이브포멧 int m_nDataSize; ///< 데이타크기
inline int CPSoundFile::GetPlayTime() const { return m_nDataSize / ( m_wfx.nSamplesPerSec / 8 * m_wfx.wBitsPerSample * m_wfx.nChannels ); }
|
이 글의 QR 코드입니다.
개요.. |
MFC 6.0 에서 CArray, CList, CMap 클래스는 GetSize() 함수와 GetCount() 가 혼용으로 사용되어 불편한 경우가 많습니다.
이를 규격화하여 하나의 함수를 호출하는 방법을 알려드립니다.
방법 |
1. MFC 7.0 으로 업그레이드 합니다.
MFC 7.0 에서는 GetSize() 함수와 GetCount()함수를 모두 사용할 수 있습니다.
하지만 하나의 코드로 통일하여 사용하시기를 권장합니다. (Kyuseo 는 GetSize() 를 사용합니다.)
2. MFC 템플릿 클래스를 상속받아 함수를 만듭니다.
아래와 같이 CArray 를 상속받아 자신이 원하는 형태의 CArray 를 만들어 사용합니다.
/** @class CPArray @date 2006/3/3 @author 채경석(kyuseo99@chol.com, http://a.tk.co.kr) @brief */ template< class TYPE, class ARG_TYPE > class CPArray : public CArray< TYPE, ARG_TYPE > { public: using CArray< TYPE, ARG_TYPE >::GetCount;
INT_PTR GetCount( ARG_TYPE t, INT_PTR nStart = 0 ) const; ///< 주어진인자의총개수를구한다. INT_PTR Find( ARG_TYPE t, INT_PTR nStart = 0 ) const; ///< 주어진인자의첫번째것을찾는다. INT_PTR Delete( ARG_TYPE t, INT_PTR nStart = 0 ); ///< 주어진인자에해당하는것을지운다. (리턴:지워진개수)
TYPE& GetHead(); const TYPE& GetHead() const; TYPE& GetTail(); const TYPE& GetTail() const;
TYPE RemoveHead(); TYPE RemoveTail();
void Sort( BOOL bDesc = FALSE ); ///< 정렬한다. ( 해당TYPE 은>, <, == 가정의되어있어야한다. ) void Sort( int ( __cdecl *Compare )( const void *pArg1, const void *pArg2 ) ); ///< 외부함수로정렬한다. void Suffle( int nCount = 5 ); ///< 섞는다.
// 자체메모리할당된포인터전용함수 void DeleteRemovtAt( INT_PTR nIndex, INT_PTR nCount = 1 ); ///< delete 하고RemoveAt 한다. void DeleteRemoveAll(); ///< delete 하고RemoveAll 한다.
protected: static int CompareAsc( const void *pArg1, const void *pArg2 ); static int CompareDesc( const void *pArg1, const void *pArg2 ); }; |
이 글의 QR 코드입니다.
개요.. |
제가 주로 애용하는 독시젠(Doxygen) 형식으로 작성한 헤더(*.h) 파일의 코딩 스타일 모습입니다.
프로그래밍 스타일 작성시 참고하세요.
참고 : Kyuseo's C++ 독시젠을 활용한 주석 작성 스타일 가이드라인(규칙)
스크린샷 |
소스코드의 모습 |
// Copyright (c) kyuseo, dpdpdp, Fungrep co.,ltd All rights reserved.
/** @file QSeeTalkHelper.h @date 2008/3/28 @author 채경석(kyuseo99@chol.com) @brief */
#pragma once
/** @class CQSeeTalkHelper @date 2008/3/28 @author 채경석(kyuseo99@chol.com) @brief */ class CQSeeTalkHelper { public: CQSeeTalkHelper(); ~CQSeeTalkHelper();
void SetText( LPCSTR szText ); ///< 문제를설정한다. : ( ) 등의구문이포함된다. LPCSTR GetText(); ///< 정답을얻는다. : ( ) 등의구문을제거되었다.
BOOL SetCompleteText( char ch ); ///< 문자를입력한다. LPCSTR GetCompleteText(); ///< 완성된문자열을얻는다. BOOL IsComplete(); ///< 문제풀이가완료되었는지검사한다.
protected: CString m_strText; ///< 전체문제 StringArray m_sa; ///< 단어별로잘라진단어가저장된다. IntArray m_iaBold; ///< 강조구문
CString m_strCompleteText; ///< 정답을맞춘문제 int m_nCompletePos; ///< 정답맞춘위치 }; |
이 글의 QR 코드입니다.
이 저작물은 비영리, 출처:Kyuseo의 게임 프로그래밍 이야기 :: http://soonsin.com 를 표시하면 스크랩 하실 수 있습니다. 별도로 출처 표시가 되지 않은 저작물은 Kyuseo에게 저작권이 있습니다. ★──━━ 행복한 하루 되세요 ━━──★ |