프로그래밍 팁 모음
TODO 메크로
헤더에 다음과 같은 메크로를 추가해 놓습니다.
#define LINE1(x) #x #define LINE(x) LINE1(x) #define TODO(msg) message ( __FILE__ "(" LINE(__LINE__) "): [TODO] " #msg ) #define NOTE(msg) message ( __FILE__ "(" LINE(__LINE__) "): [NOTE] " #msg )
소스 파일에서 메크로 사용은 다음과 같습니다.
#pragma TODO( "여기에 적어 놓을 말을 적습니다." )
그리고 소스 파일에 다음에 해야할 일이나 특이사항들을 적어 두면 컴파일시 output 창에 나타납니다. 그리고 output창에 나온 메시지를 더블클릭하면 해당 라인으로 이동합니다. 급하게 메모해야할 일이 있으면 유용한 메크로가 되지 않을까 합니다.
- 관련링크%%%
VS.NET의 TODO 주석 기능을 이용하자. - http://jacking75.cafe24.com/Tip/VS_NET-TODO.htm
파일읽기 버퍼 오버플로우 방지 팁
fscanf()등 파일에서 읽기 작업을 할때 보통 이런식으로 많이 사용합니다.
FILE * fp = fopen( "test.dat", "r" ); if( fp == NULL ) return; fscanf( fp, "%s", szBuf );
이때 버퍼를 넘어서 입력값이 들어올 수 있습니다. 이는 입력수를 제한하여 오버플로우를 방지할 수 있습니다.%%% 버퍼의 크기가 256이라고 한다면,
fscanf( fp, "%255s", szBuf );
이런식으로 크기를 제한하여 사용하면 간단히 막을 수 있습니다.%%% 그러나 버퍼의 크기가 바뀐다면 하드코딩된 부분 또한 바꾸어야 합니다. 이를 좀더 편하게 하기 위해서 다음과 같이 합니다.
char szFormat[128]; sprintf( szFormat, "%%%ds", sizeof( szFormat )-1 ); fscanf( fp, szFormat, szBuf );
"%%%ds"
맨앞의 %%는 %로 만들기 위해 넣는 부분이고 다음의 %d는 사이즈 수를 받기 위한 부분입니다. 그리고 마지막 s를 넣어 format을 완성하는 것이죠. 위와 같이 하면 szFormat에는 "%255s"와 같은 값이 들어갑니다.%%% 이렇게 하여 보다 유연하고 안전한 파일읽기가 가능합니다.
VC++ 프로젝트에서 Visual Source Safe 삭제 방법
시작하기에 앞서 파일들이 읽기전용이면 이를 풀어줍니다.
VC++ 6.0 프로젝트에서 Visual Source Safe 삭제 방법
1) 프로젝트 폴더에 있는(하위폴더 포함) *.scc 파일을 모두 삭제 합니다.
보통 프로젝트 메인 폴더에 mssccprj.scc 파일이 있고, 각 폴더마다 vssver.scc 파일이 있습니다. 하위 폴더까지 이 파일이 있으니 검색을 통해서 모두 삭제하세요.
2) *.dsw 파일을 열어 Source Safe 정보를 삭제합니다.
*.dsw 파일을 메모장으로 열어보면 아래와 같은 부분이 있습니다.
begin source code control ....(중략) end source code control
이부분을 삭제합니다.
3) 모든 *.dsp 파일을 열어 Source Safe 정보를 삭제합니다.
*.dsp 파일을 메모장으로 열어보면 아래와 같은 부분이 있습니다.
# PROP Scc_ProjName "(어쩌고저쩌고)" # PROP Scc_LocalPath "."
이 부분을 삭제합니다.
VC++ .NET 2003 프로젝트에서 Visual Source Safe 삭제 방법
1) 프로젝트 폴더에 있는(하위폴더 포함) *.scc 파일을 모두 삭제 합니다.
VC++ 6.0과 동일합니다.
2) *.sln 파일을 열어 Source Safe 정보를 삭제한다.
*.sln 파일을 메모장으로 열어보면 아래와 같은 부분이 있습니다.
GlobalSection(SourceCodeControl) = preSolution ...(중략) EndGlobalSection
이부분을 정확하게 찾아서 삭제합니다.
3) 모든 *.vcproj 파일을 열어 Source Safe 정보를 삭제한다.
*.vcproj 파일을 메모장으로 열어보면 아래와 같은 부분이 있습니다.
SccProjectName="(어쩌고저쩌고)" SccLocalPath="."
이부분을 삭제합니다. 보통 뒤에 '>' 이게 있는데 이건 지우면 안됩니다. 정확하게 해당 부분만 지우세요.
스트링의 해시 값 구하기
배열크기(NHASH)와 해시 값에 곱해지는 값(MULTIPLIER)과 가능하면 데이터 값들이 서로 공통된 약수를 가지지 않도록 하여 균등하게 분산시킬 수 있게 한다. 그러므로 곱해지는 값을 소수로 잡는다. ASCII 스트링의 경우 31과 37이 좋다.
enum { MULTIPLIER = 31 }; // or 37 unsigned int hash( char * str ) { unsigned int h = 0; // 해시 값이 양수가 되게 하기위해 unsigned char로 변환 unsigned char * p = NULL; for( p = ( unsigned char * ) str; *p != '�'; p++ ) h = MULTIPLIER * h + *p; return (h % NHASH); }
- 관련링크%%%
http://www.gpgstudy.com/forum/viewtopic.php?t=795 %%% http://www.flipcode.com/cgi-bin/msg.cgi?showThread=Tip-HashString&forum=totd&id=-1
2차원 배열 동적 할당
이 내용은 게임 개발자를 위한 C++ (민프레스, 서진택 저)에서 가져온 것입니다.
- 첫번째 방법
int (*pArray1)[3]; pArray1 = new int[2][3]; // ok delete [] pArray1; int (*pArray2)[3][4]; pArray2 = new int[2][3][4]; // ok delete [] pArray2;
이렇게 할당하면 맨끝 인덱스가 고정되어야 하는 단점이 있다.
참고
int (* pointer)[3] => 크기가 3인 정수 배열의 배열의 시작주소 : pointer + 1 == pointer[1] int * pointer[3] => int * 를 요소로 가지는 일차원 배열
- 두번째 방법
double ** pData = NULL; pData = new double*[nSize1]; for( int i = 0; i < nSize1; i++ ) pData[i] = new double[nSize2]; // ... for( int j = 0; j < nSize1; j++ ) delete [] pData[j]; delete [] pData;
가장 일반적인 방법이다. 이 방법이 가장 이해하기 쉽다.
- 세번째 방법
하나의 일차원 배열을 렙퍼하는 형식의 클래스를 만들고 그 클래스를 또 일차원 배열로 만들면 관리하기 편하고 조금 더 직관적인 코드를 만들 수 있다.
class CInt { public: CInt( int nSize = 10 ) { m_pArray = new int[nSize]; m_nSize = nSize; } ~CInt() { delete [] m_pArray; } int & operator[]( int nIndex ) { return m_pArray[nIndex]; } private: int * m_pArray; int m_nSize; }; // main CInt * pInt; pInt = new CInt[3]; // ... pInt[i][j] 로 사용. delete [] pInt;
참고%%% 기본 new 연산자를 이용하여 디폴트 생성자가 없는 클래스를 동적 배열 할당으로 만들 수 없다.
typename 키워드
typename 키워드는 다음에 나오는 식별자가 타입이라는 것을 명시하기 위해 사용된다. 예를 보면,
template< class T > class MyClass { typename T::SubType * ptr; // ... };
여기서 typename은 ?SubType이 class T의 서브 타입이라는 사실을 명확하게 한다. 그래서 ptr 변수는 T::?SubType 타입의 포인터가 된다. 물론 타입 T 안에 ?SubType가 정의 되어야 한다. typename 키워드를 빼버린다면, ?SubType는 static 변수로 간주된다. 그래서 붙이지 않을 경우
T::SubType * ptr;
은 타입 T의 ?SubType static 변수와 ptr과 곱한 결과로 인식한다.
말이 안되는 것 같지만 컴파일러는 typename이 없다면 값으로 판단하기 때문에 반드시 필요하다. VC 6.0에서는 크게 문제가 없었지만, VC.NET 2003에서는 반드시 typename을 적어주어야한다.
MS VC++에서 __int64 값 출력하기
만약 MS VC++에서 __int64를 printf() 등으로, 값을 출력하고자 할때 %d 로 출력을 하면 4바이트만 출력된다. 더욱 %d 뒤에 또 다시 %d 가 나왔을때 뒤 부분의 값은 올바르게 출력되지 않는다. 아마, %d 가 4바이트를 기준으로 끊어버리기 때문인 듯하다.
그럼 __int64 를(unsigned __int64 도 마찬가지 이다.) 출력하고자 한다면 64비트라는 것을 알려줘야 한다. 이것을 해주는 것이 MS VC++에서는 %I64d 이다 I64 가 64비트라는 것을 알려준다.
__int64 n64SumVal; ... printf( "Val: %I64d", n64SumVal );
이런 식으로 하면 된다.
xxx[ 1 ] 배열의 활용법 한가지
다른 경우라면 이런 상황이 잘 발생하지 않지만.. 지금하는 것에서는 다음과 같은 일이 발생했다.
패킷을 보내는 데 해더와 카운터를 하나 있다. 그리고 그뒤에 구조체가 카운터 개수만큼 붙어 온다. 즉,
{ HEADER - count - (struct Data * count) }
이런 식으로 하나에 묶어서 온다. 아 그리고 count 는 0이 아니다. 그리고 각각 count 값은 다를 수 있다.
위의 모두를 구조체로 만들기는 불가능하다. 크기가 가변이기 때문에 MAX 값을 정해놓고 해도 되지만 불필요한 공간이 생긴다. 시도한 방법은 다음과 같다.
struct Data { ... } struct AllData { HEADER; count; }
이렇게 정의해 놓고 ?AllData 다음에 count만큼 Data를 붙이는 것이다. 이것을 참조할때의 예를 보자.
Data * pData = (Data*)( ( (char*)alldata ) + sizeof( AllData ) ); for( int i = 0l; i < alldata.count; i++ ) { pData->.... pData++; }
말이 길었는데 여기서 조금더 편하게 사용하기 위해 다음과 같이 했다.
struct AllData { HEADER; count; Data data[1]; // data[0]으로 선언해도 됩니다. }
이렇게 하면 사용하기 편하다.
for( int i = 0; i < count; i++ ) { alldata.data[i]. .... }
편한 방법인 것 같다.
가상 함수가 있는 클래스를 memset()으로 초기화하는 것은 안된다.
클래스 내에 가상함수가 있으면 클래스 인스턴스를 전체를 memset()을 이용하여 인스턴스를 초기화할 경우 그 인스턴스가 가지고 있던 가상함수 테이블 포인터까지 초기화 되어 가상함수 호출할 수 없는 일이 일어난다. 그러므로 클래스 전체를 초기화할때는 사용하지 말자.