출처 : http://ngpiki.ngps.net/index.php?display=프로그래밍%20조각지식

프로그래밍 조각지식

malloc & calloc

일반적으로 C++이 도입되면서 malloc 과 free라는 함수의 사용빈도는 많이 줄어들고 있지만 클래스 기반이 아닌 오리지널 C나 C/C++의 중간 개념적인 소스의 경우 여전히 malloc와 free에 의해 메모리 할당, 해제를 하게 됩니다.(개인적으로는 malloc가 더 편하다는 -_-;;)

malloc라는 함수의 존재를 알고 있으면서도 calloc라는 함수에 대해선 모르고 지나치는 사람들이 많이 있는데 malloc와 calloc는 같은 맥락의 함수이지만 약간의 차이점을 가지고 있습니다.

malloc는 지정된 크기만큼의 메모리를 할당하지만 그 메모리 자체가 초기화 되어지지 않은체로 그저 공간만이 할당되는 역할을 하는 반면 calloc는 malloc과 마찮가지로 지정된 크기만큼의 메모리를 할당하면서 할당된 메모리를 모두 0으로 체워서 반환합니다. 간단하게 말하면 malloc 와 memset 을 (또는 bzero ) 한번에 사용 해 놓은 것이죠.

단, malloc 보다는 calloc 가 조금 더 느리다고 합니다.

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 != '\0'; 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 == pointer1 int * pointer3 => 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 연산자를 이용하여 디폴트 생성자가 없는 클래스를 동적 배열 할당으로 만들 수 없다.

    크리티컬세션을 사용할때 기억해야할 한마디

    크리티컬세션을 사용할때 이말을 상기하자.

    "크리티컬세션은 코드에 거는 것이 아니라 리소스에 거는 것이다."

    "이중 크리티컬 세션은 되도록 사용하지 말라. 데드락의 위험이 있다. 혹시 사용하게 된다면 모든 곳에서 각 크리티컬 세션을 똑같은 순서로 Lock하고 UnLock하게 하라."

    예)
    csResource1.Lock();
    csResource2.Lock();
    ...
    csResource2.UnLock();
    csResource1.UnLock();

    ...

    /************
    csResource2.Lock();
    csResource1.Lock(); // 데드락 위험!!
    **************/
    csResource1.Lock();
    csResource2.Lock();
    ...
    csResource2.UnLock();
    csResource1.UnLock();

    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을 적어주어야한다.

    C와 C++의 비용 모델

    The Practice of Programming 라는 책에 보면 각 C와 C++의 연산이나 함수의 비용을 수만번 실행해서 평균값을 적어 놓은 것이 있습니다. 테스트를 250MHz MIPS R10000이라는 컴퓨터 사양으로 했다고 합니다. 컴퓨터가 일반적으로 사용하는 Intel x86이 아니라서 아쉽지만, 일단 참고삼아 볼 수 있고 궁금하면 테스트를 해보면 되니 여기에 적어 봅니다. (단위는 nanoseconds 입니다.)

    Int operations

    i1++;8
    i1 = i2 + i3;12
    i1 = i2 - i3;12
    i1 = i2 * i3;12
    i1 = i2 / i3;114
    i1 = i2 % i3;114

    Float Operations

    f1 = f2;8
    f1 = f2 + f3;12
    f1 = f2 - f3;12
    f1 = f2 * f3;11
    f1 = f2 / f3;28

    Double Operations

    d1 = d2;8
    d1 = d2 + d3;12
    d1 = d2 - d3;12
    d1 = d2 * d3;11
    d1 = d2 / d3;58

    Numeric Conversions

    i1 = f1;8
    f1 = i1;8

    Integer Vector Operations

    v[i] = i;49
    v[v[i]] = i;81
    v[v[v[i]]] = i;100

    Control Structures

    if( i == 5 ) i1++;4
    if( i != 5 ) i1++;12
    while( i < 0 ) i1++;3
    i1 = sum1( i2 );57
    i1 = sum2( i2, i3 );58
    i1 = sum3( i2, i3, i4 );54

    Input/Output

    fput( s, fp );270
    fgets( s, 9, fp );222
    fprintf( fp, "%d\n", i );1820
    fscanf( fp, "%d", &i1 );2070

    Malloc

    free( malloc( 8 ) );342

    String Functions

    strcpy( s, "0123456789" );157
    i1 = strcmp( s, s );176
    i1 = strcmp( s, "a123456789" );64

    String/Number Conversions

    i1 = atoi( "12345" );402
    sscanf( "12345", "%d", &i1 );2376
    sprintf( s, "%d", i );1492
    f1 = atof( "123.45" );4098
    sscanf( "123.45", "%f", &f1 );6438
    sprintf( s. "%6.2f", 123.45 );3902

    Math Functions

    i1 = rand();135
    f1 = log( f2 );418
    f1 = exp( f2 );462
    f1 = sin( f2 );514
    f1 = sqrt( f2 );112

    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 );

    + Recent posts