2011. 4. 10. 17:32 주절주절

프로그래밍 팁 모음

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()을 이용하여 인스턴스를 초기화할 경우 그 인스턴스가 가지고 있던 가상함수 테이블 포인터까지 초기화 되어 가상함수 호출할 수 없는 일이 일어난다. 그러므로 클래스 전체를 초기화할때는 사용하지 말자.

posted by 부풍