메뉴 건너뛰기


Developer > Application

C,C++ 프로그래밍 팁 모음

2013.11.27 02:48

푸우 조회 수:23745


출처 : http://blog.naver.com/minzkn?Redirect=Log&logNo=60003908545

프로그래밍 팁 모음


프로그래밍을 할 때 유용하게 쓸 수 있는 간단한 팁을 소개하는 페이지! 
  1. 블럭 데이타를 출력하는 함수
  2. 스트링 버퍼를 선언과 함께 초기화
  3. 가변인수를 가지는 함수 및 매크로 만들기
  4. 실시간용 디버그 프린트 플래그 삽입하기
  5. int형 버퍼를 초기화 할 때 주의할 점
  6. 배열변수를 가변인자로 선언할 때와 static하게 선언할 때의 차이점
  7. Byte Alignment
  8. 파일전송과 소켓이 연관될 때 strcpy는 쓰지 말 것
  9. UNIX상에서 한글출력이 깨져 나올경우
  10. gcc extension
  11. C++에서 전역함수는 이름변형에 주의하라
  12. VC++에서 인자전달 방식 비교
  13. ISO C와 ISO C++의 차이점
  14. C FAQ
  15. new와 delete로 배열을 선언할 때
  16. 천단위 comma 삽입 코드
  17. strace, truss, tusc를 활용한 디버깅
  18. Log 함수

1 블럭 데이타를 출력하는 함수

네트웍이나 디스크와 같은 블럭형태의 raw 데이타를 다루는 프로그래밍에서 디버그용도로 특정 메모리 영역을 출력하고자 할 때 다음과 같은 함수를 쓰면 유용하다. 
void watch_data(unsigned char *data, int len) 
{ 
        unsigned char* temp; 
         int i; 
 
        temp = (unsigned char *)data; 
        printf("==== DATA size is %d===", len); 
        for(i = 0; i < len ; i++) 
        { 
                if(i%8 == 0) printf("\n"); 
                printf("0x%02x ",temp[i]); 
        } 
        printf("\n================\n"); 
} 

2 스트링 버퍼를 선언과 함께 초기화

함수 내에서 로컬변수로 스트링 버퍼를 사용할 때 사전에 반드시 초기화를 해야한다는 것은 누구나 알고 있다. 
아마 대부분은 다음과 같은 방법으로 버퍼를 초기화 할 것이다. 
void do_something_function() 
{ 
    char szBuffer[MAX_BUF_SIZE]; 
             .... 
 
    memset(szBuffer, 0, sizeof(szBuffer)); 
             ....     
} 
그러나 버퍼를 선언함과 동시에 초기화가 이루어지도록 하면 다음과 같이 선언하면 된다. 
char szBuffer[MAX_BUF_SIZE] = {0}; 
char szBuffer[MAX_BUF_SIZE] = {}; 
char szBuffer[MAX_BUF_SIZE] = ""; 
스트링 버퍼를 로컬이 아닌 글로벌로 선언하면 초기화를 명시하지 않아도 모두 0으로 초기화가 된다. 이는 초기화 되지 않는 변수는 bss영역에 배치되는데 이는 c의 startup코드가 bss영역을 null로 초기화하기 때문이다. 그러나 이는 c의 표준인지 확실치 않으며 안전한 프로그래밍을 위해서는 글로벌 변수도 초기화하는 습관을 들이는 것이 좋다. --철이(Terry) 

3 가변인수를 가지는 함수 및 매크로 만들기

Written by 철이(Terry) 
ANSI C버전 
#include <stdarg.h> 
#include <stdio.h> 
 
#define ERR_FUNC(fmt, args...) \ 
err_func(__FILE__, __LINE__, fmt, ## args) 
 
void err_func(const char *, const int, char *, ...); 
 
 
 
void err_func(const char *name, const int line, char *fmt, ...) 
{ 
    va_list ap; 
    va_start(ap, fmt); 
    fprintf(stderr, "Error in %s, line %i:", name, line); 
    vfprintf(stderr, fmt, ap); 
    va_end(ap); 
} 
 
int main() 
{ 
    ERR_FUNC("value=%d",12); 
} 
GCC버전 
#include <stdarg.h> 
#include <stdio.h> 
 
#define ERR_FUNC(fmt, args...) \ 
err_func(__FILE__, __FUNCTION__, __LINE__, fmt, ## args) 
 
void err_func(const char *, const char *, const int, char *, ...) 
__attribute__ ((format (printf, 4, 5)))  
; 
 
 
 
void err_func(const char *name, const char *func, const int line, char *fmt, ...) 
{ 
    va_list ap; 
 
    va_start(ap, fmt); 
    fprintf(stderr, "Error in %s, line %i, in %s():", name, line, func); 
    vfprintf(stderr, fmt, ap); 
    va_end(ap); 
} 
 
int main() 
{ 
    ERR_FUNC("value=%d",12); 
} 

4 실시간용 디버그 프린트 플래그 삽입하기

GDB나 막강한 기능의 상업용 디버거를 사용할 수 없는 임베디드 시스템에서 프로그램을 개발할 때 디버그용도로 프린트문을 가장 많이 쓴다. 일반적으로 다음과 같이 쓰는 것을 많이 보았다. 
#define DEBUG_PRINT_ON   1 
    ........... 
void someFunc(.....) 
{ 
      ........ 
      #ifdef DEBUG_PRINT_ON 
          printf("%s, %d: some_err_flag=%d\n", __FILENAME__, __LINENO__, someFlag); 
      #endif 
      ........ 
} 
이렇게 전처리기로 디버그용 프린트 플래그를 쓰면 나중에 패키지를 릴리즈할 때 프린트 플래그를 undefine시키면 되고 디버그용 코드가 라이브러리나 바이너리 파일에 첨가되지 않는다는 장점이 있지만 실제 현장에서 문제가 발생해서 디버깅을 하려고하면 디버그 플래그를 define해서 전체 코드를 다시 재컴파일하고 빌드해서 다운로드해야한다는 문제점이 발생한다. 나같으면 다음과 같이 쓰겠다. 
int debug_prt_flag = 0; 
   .......... 
void flag_set(int set) 
{ 
    debug_prt_flag = set; 
} 
 
void someFunc(....) 
{ 
      ....... 
    if (debug_prt_flag) printf("%s, %d: some_err_flag=%d\n", __FILENAME__, __LINENO__, someFlag); 
      ....... 
} 
위에서 flag_set이라는 함수는 특정 키를 눌렀을 때 발생하는 이벤트에 매핑시키거나 쉘 상에서 커맨드화 해두면 언제든 플래그를 on/off시킬 수 있기때문에 디버깅이 필요한 상황에서 전체 코드를 재빌드할 필요가 없다. 이것에서 더 나아가 플래그를 레벨별로 차별화 해서 프린트문을 쓰고 싶다면 다음과 같이 하면 된다. 
#define DEBUG_PRINT_LEVE1   0x00000001 
#define DEBUG_PRINT_LEVE2   0x00000002 
#define DEBUG_PRINT_LEVE3   0x00000004 
          ............. 
 
int debug_prt_flag; 
   ......... 
 
void flag_set(int set) 
{ 
    debug_prt_flag |= set;  (또는 debug_prt_flag |= 1 << set;) 
} 
 
void someFunc(....) 
{ 
      ........ 
    if (debug_prt_flag & DEBUG_PRINT_LEVE1)  
         printf("%s, %d: some_err_flag=%d\n", __FILENAME__, __LINENO__, someFlag); 
      ........ 
} 
이렇게 해두면 각 레벨별로 프린트문을 제어할 수 있으므로 상황별로 더 유연하게 사용할 수 있다. 
주의할 점은 디버그용 프린트문을 지나치게 남발하면 도리어 화면이 혼란스럽게 될뿐만 아니라 쓸데없는 디버그용 코드가 패키지 안에 포함되므로 꼭 확인이 필요한 부분, 코드내에 에러에 민감한 부분에 최소로 사용하도록 한다. 
    참고로 define문에서 앞뒤 문자열을 합쳐주는 토큰합치기 ##를 이용해서 이렇게 해도 된다. 
         #define COMMENT / ## / 
         ... 
         COMMENT printf("Test code\n"); 
     
      아니면 이렇게 하면 어떤가 
            #ifdef _DEBUG_PRT 
               #define aprintf printf 
            #else 
               #define aprintf / ## / 
            #endif 
       
            int main(int argc, char* argv[]) 
            { 
                   aprintf("Hello World!\n"); 
       
                   return 0; 
            } 
        
        다른 방법 --geniusdm 
              #ifdef _DEBUG 
                  #define DBG(func)        func 
              #else 
                  #define DBG(func)  
              #endif 
         
             int main(int argc, char* argv[]) 
             { 
                 DBG( printf("안녕하세요.") );  
         
                 return 0; 
             } 
           
          가변인자를 받을 수 있도록 매크러 정의 --genie 
                [LogSystem.h] 
           
                #include <stdarg.h> 
           
                #ifdef _DEBUG 
                     void PrintDebug( const char* Format, ... ); 
                #else 
                     #define PrintDebug NULL 
                #endif 
           
                [LogSystem.cpp] 
           
                #ifdef _DEBUG 
                   void PrintDebug( const char* Format, ... ) 
                   { 
                        char Buffer[2048]; 
           
                        va_list ArgPtr; 
                        va_start( ArgPtr, Format ); 
                        _vsnprintf( Buffer, ARRAY_COUNT(Buffer), Format, ArgPtr ); 
                        va_end( ArgPtr ); 
           
                        OutputDebugString( Buffer ); // 이 함수는 로그 파일에 쓰거나 하는 식으로 바꿀 수 있다. 
                   } 
                #endif 
              
               [Main.cpp] 
           
               #include "LogSystem.h" 
                       ... 
           
               PrintDebug( " Draw ( count = %i, fps = %i )", DrawCount, FramePerSecond ); 
              

5 int형 버퍼를 초기화 할 때 주의할 점

예를 들어 int형 배열을 다음과 같이 선언했다고 해보자 
#define MAX_NUM_SIZE  1024 
 
int main(void) 
{ 
      int buf[MAX_NUM_SIZE]; 
        ............. 
} 
이 버퍼를 초기화하고자 할 때 memset함수를 쓴다. 그런데 다음과 같이 쓴다면 어떻게 될까 
      memset(buf, 0, MAX_NUM_SIZE); 
이것은 생각없이 코딩을 하다가 무심코 저지르는 실수로 memset에 파라미터로 넘기는 크기는 바이트단위이다. 즉, 이렇게 쓰면 전체 버퍼크기 중에서 1024바이트만 초기화를 해버리고 만다. 전체 버퍼를 초기화하려면, 
      memset(buf, 0, MAX_NUM_SIZE*sizeof(int)); 
너무 당연한 것이지만 코딩을 하다가 저지르기 쉬운 실수이니 주의하자 

6 배열변수를 가변인자로 선언할 때와 static하게 선언할 때의 차이점

프로그램을 작성하다가 간혹 배열을 static하게 선언하기 보다 아래와 같이 런타임시 가변인자로 선언하고 싶을 때가 있다. 
int someFunc(int num_array, ........) 
{ 
      int var[num_array]; 
       ........... 
} 
위와 같이 사용하고자 할 때 어떤 문제점이 있을까. 예를 들면 다음의 짧은 함수를 디스어셈블하면 다음과 같다. 
void test(int num_array) 
{ 
        int var[nun_arary]; 
 
        var[1] = 0x1236; 
} 
test.c 
00000024  55                push ebp 
00000025  89E5              mov ebp,esp                   ; stack pointer 저장 
00000027  53                push ebx                    
00000028  83EC14            sub esp,byte +0x14            ; 20바이트(0x14) 스택영역을 잡는다. 
0000002B  89E3              mov ebx,esp 
0000002D  8B4D08            mov ecx,[ebp+0x8]             ; num_array변수값을 카운터 레지에 저장한다 
00000030  49                dec ecx 
00000031  894DF0            mov [ebp-0x10],ecx 
00000034  C745F400000000    mov dword [ebp-0xc],0x0 
0000003B  8B45F0            mov eax,[ebp-0x10] 
0000003E  8B55F4            mov edx,[ebp-0xc] 
00000041  0FA4C205          shld edx,eax,0x5 
00000045  C1E005            shl eax,0x5 
00000048  83C020            add eax,byte +0x20 
0000004B  83D200            adc edx,byte +0x0             ; 왜 이런 쓸데없는 짓을 하는지 알수 없음. --; 
0000004E  6BC104            imul eax,ecx,byte +0x4        ; eax = ecx * 0x4 
00000051  83C004            add eax,byte +0x4             ; eax = eax + 0x4 
00000054  83C00F            add eax,byte +0xf             ; eax = eax + 0xf 
00000057  C1E804            shr eax,0x4                   
0000005A  89C0              mov eax,eax 
0000005C  C1E004            shl eax,0x4                   ; 16바이트 이하 단위 절삭 :D 
0000005F  29C4              sub esp,eax                   ; stack영역을 확보한다. 
00000061  89E0              mov eax,esp 
00000063  C7400436120000    mov dword [eax+0x4],0x1236 
0000006A  89DC              mov esp,ebx 
디스어셈블된 코드를 보면 EBX레지스터값을 백업하고 28h번지에서 기본적으로 20바이트의 스택영역을 잡고있는 것을 볼 수 있다. 이 함수의 local variable인 var영역하고 직접적으로 관계된 영역이 아니고 var배열의 바이트수를 계산하기 위해 파라미터를 복사하는데 일부 영역을 쓰는데도 이렇게 많이 잡는 이유는 아마 에러에 대비한 것이 아닌가 싶다. var영역을 잡기위한 계산은 4Eh번지부터다. 파라미터로 넘어온 num_array를 2Dh번지에서 ECX에 복사하는 것을 볼 수 있다. 이 값을 4Eh번지에서 int크기인 4바이트로 곱한 후 이 값에 4바이트를 더한다. 이는 30h번지에서 num_array값을 복사한 ECX에서 1을 뺀 작업을 상쇄하는 것인데 왜 이런 짓을 하는지는 잘 모르겠다. (이런 쓸데없는 짓은 34h번지부터 4Bh번지까지에서도 눈에 띈다. EDX:EAX레지스터에 파라미터값을 넣고 비트시프팅을 하거나 바이트연산을 하면서 지지고 볶지만 정작 EAX는 4Eh번지에서 새로 할당되고 EDX는 함수 어디에서도 쓰는 곳이 없다. -_-;). 바이트 단위로 계산한 배열크기를 EAX에 넣고 54h번지에서 61h번지까지는 그 값에 기반해 스택영역을 잡는 곳이다. 이곳을 보면 배열크기를 16바이트 단위로 잡는 것을 알 수 있다. 즉, num_array가 2든 3이든 var를 위한 스택영역은 16바이트가 잡히고 만약 num_array가 5라면 32바이트가 잡히는 식이다. 
자, 그럼 다음의 코드를 디스어셈블하면 어떻게 될까. 
void test(int c) 
{ 
        int var[5]; 
 
        var[1] = 0x1236; 
} 
test1.c 
00000014  55                push ebp 
00000015  89E5              mov ebp,esp 
00000017  83EC28            sub esp,byte +0x28 
0000001A  C745DC36120000    mov dword [ebp-0x24],0x1236 
00000021  C9                leave 
00000022  C3                ret 
우선 척 보기에도 무지하게 간단해졌다. :)  실제로 메커니즘도 매우 간단하다. 배열인 var변수를 위해 17h번지에서 40바이트의 스택영역만 잡으면 끝이다.(실제로는 5*4 = 20바이트 영역만 잡으면 되지만 좀 넉넉하게 잡는 것도 로칼영역과 EBP, return address가 저장된 스택영역 사이에 공간을 둠으로써 안전한 실행환경을 만들기 위함으로 추측된다.) 

7 Byte Alignment

32비트 플랫폼에서 byte alignment는 4바이트, 8바이트 단위라는 것을 기억해야한다. 물론 이는 컴파일러에 따라 틀리다. 32비트 UNIX플렛폼에서 gcc를 기준으로 구조체를 다음과 같이 잡았을 경우, 
struct _test 
{ 
      int i; 
      char c; 
      int j; 
      char d; 
 
}; 
이 구조체가 필요한 크기는 10바이트지만 실제 시스템에서는 16바이트로 잡힌다. 똑같은 구조체라도 배열이 틀리면 사이즈도 틀려진다. 
struct _Test 
{    
        int i; 
        int j; 
        char c; 
        char d; 
}; 
4바이트 alignment이므로 시스템은 이 구조체를 위해 12바이트 메모리를 잡는다. 다음과 같은 구조체일경우 몇 바이트가 잡힐까 
struct _Test 
{   
    int j; 
    double i; 
    char d; 
    char c; 
};  
UNIX에서는 16바이트 메모리를 잡는 것을 확인했는데 Visual C++에서는 24바이트를 잡는다고 한다. (이유인 즉슨, Visual C++ 컴파일러에서는 구조체의 크기를 메모리에 올릴때 일단은 제일 큰 바이트로 나눈다. 위의 double형이 있는 경우 처음에는 int 형을 올리면서 8바이트로 올린다. 그 다음에 double 형이 오니까 나머지 4바이트에 다 못올려서 다시 8바이트를 잡는다. 그리고 8바이트를 잡아 놓고 나머지 char 형 변수 2개는 다 들어 가니까 더이상 안잡잡는 것이다. 그래서 합이 24 바이트가 된다는 것이다. 확인요
그렇다고 구조체변수를 무턱대고 4바이트 단위로 잡는다고 생각하면 안된다. 예를 들어, 
struct _Test 
{ 
     char i; 
}; 
라면 1바이트로 할당되기 때문이다. 예제로 다음과 같은 c코드를 짰다고 가정해보자. 
#include <stdio.h> 
 
struct _test 
{ 
    int j; 
 
    double i; 
 
    char d; 
 
    int c; 
 
    char k; 
}; 
 
int main(void) 
{ 
        struct _test a; 
 
        a.j = 1; 
        a.i = 2; 
        a.d = 3; 
        a.c = 4; 
        a.k = 5; 
 
        return 1; 
} 
이를 디스어셈블링하면 다음과 같다. 
[leedw@dasomnetwork test]$ndisasm -b 32 test.bin 
 
00000000  55                push ebp 
00000001  89E5              mov ebp,esp 
00000003  83EC28            sub esp,byte +0x28 
00000006  C745D801000000    mov dword [ebp-0x28],0x1 
0000000D  C745DC00000000    mov dword [ebp-0x24],0x0 
00000014  C745E000000040    mov dword [ebp-0x20],0x40000000 
0000001B  C645E403          mov byte [ebp-0x1c],0x3 
0000001F  C745E804000000    mov dword [ebp-0x18],0x4 
00000026  C645EC05          mov byte [ebp-0x14],0x5 
0000002A  B801000000        mov eax,0x1 
0000002F  C9                leave 
00000030  C3                ret 
00000031  8D7600            lea esi,[esi+0x0] 
6h번지부터 구조체 변수에 할당하는 부분인데 위에서 보듯 int는 4바이트, double형은 8바이트, 1Bh번지에서 byte로 값을 넣지만 실제로 스택 프레임에서는 4바이트가 잡힌다. 이후에도 4바이트 단위로 스택영역이 할당되므로 이 구조체는 도합 24바이트의 크기를 가지게 된다. (Visual C++에서는 32바이트가 잡힌다고 하는데 이 역시 확인해봐야한다.) 

8 파일전송과 소켓이 연관될 때 strcpy는 쓰지 말 것

우선 파일 전송이니 파일과 소켓이 연관되어있으리라고 짐작하신 분들, 맞습니다. 이글은 우선 제가 일하다가 자주 실수하는 상황들을 글의 목적에 맞는 사람들(저같은 초보적인 개발자)을 위하여 쓴 글이라는 것을 우선 알려드립니다. 우선 파일을 열어서 거의 아래와 같이.. char 배열을 이용해 주로 파일을 전송했을꺼라고 봅니다. 또 대용량 파일 전송시에는 고정사이즈 패킷을 사용하는게 일반화 되어 있기 때문에 패킷을 다음과 같이 작성해보았습니다. 
우선 전송용 패킷을 다음과 같이 정의해 놓고 
typedef struct _header 
{ 
    unsigned int uiFlag; 
    unsigned int uiType; 
    unsigned int uiLen; 
    unsigned int uiCmd; 
}HEADER, *LPHEADER; 
 
typedef struct _packet 
{ 
    HEADER head; 
    char szBody[BODY_SIZE]; 
}PACKET, *LPPACKET; 
아래는 전송용 패킷포맷해주는 함수. 
void CClientSocket::PreparePacket(unsigned int nActType, unsigned int nCmd, unsigned int nFlag, unsigned  
                                   int nLen, LPCTSTR strPacket) 
{ 
    //패킷을 0값으로 초기화 시킴 
    memset(&m_Packet, 0, sizeof(packet)); 
 
    LPHEADER    pPacketHead = NULL; 
    pPacketHead = (LPHEADER)&m_Packet.head; 
    pPacketHead->uiCmd  = htonl(nCmd); 
    pPacketHead->uiFlag = htonl(nFlag); 
    pPacketHead->uiLen  = htonl(nLen); 
    pPacketHead->uiType = htonl(nActType); 
 
    strcpy(m_Packet.szBody , strPacket); 
 
    //패킷이 포맷되었음 
    m_bPrepare = TRUE; 
} 
파일을 읽어서 보낼때 다음과 같이 보내는 경우가 많더군요..파일을 char형으로 읽어 읽어온 길이만큼 m_Packet.szBody에 복사하고요 소켓으로 보냅니다. 아래 코드와 같습니다.물론 로직상으로는 문제가 없습니다. 하지만.. 
char filebuf[BUF_SIZE]=""; 
 
int nRead = m_File.Read(filebuf, BUF_SIZE); 
 
PreparePacket(파일전송, 파일보낸다, 체크, nRead, filebuf); 
 
Send(&m_Packet, sizeof(packet); 
위의 코드는 문제가 있습니다.어디일까요?? 저도 가끔 간과하지만.. strcpy이 함수를 사용해서는 안됩니다..파일이기 때문에 어떤 값이 들어올지 모릅니다..strcpy는 NULL이 나올때까지 복사해주는 함수이기 때문에.. 
 strcpy(m_Packet.szBody , strPacket);  를 memcpy(m_Packet.szBody , strPacket, nLen); 로 바꾸어야 겠죠. 그렇지 않으면
"1213우에이오우 'NULL' 우하하하 EOF" 
위와 같은 내용의 파일이 있다면 한번에 다 읽었다고 하더라도.. 
"1213우에이오우 0000000000000000000" (0이 BODY_SIZE-strlen(strPacket)만큼 나오겠죠..) 
까지만 전송하고 나머지는 다 0값이 되어서 전송되게 됩니다.. 
위와 같은 오류를 안만들려면 우선 파일 버퍼나 패킷을 정의할때 먼저 파일의 특성을 고려해야 합니다. (NULL이나 다른 값이 들어갈수 있다 라는..) 패킷도 보고 바로 알수 있게.. 
typedef struct _packet 
{ 
    HEADER head; 
    BYTE body[BODY_SIZE]; 
    //char szBody[BODY_SIZE]; 
}PACKET, *LPPACKET; 
또한 패킷 포맷용 함수도.. 
void CClientSocket::PreparePacket(unsigned int nActType, unsigned int nCmd, unsigned int nFlag, unsigned int nLen, BYTE* PacketBody) 
{ 
    //패킷을 0값으로 초기화 시킴 
    memset(&m_Packet, 0, sizeof(packet)); 
 
    LPHEADER    pPacketHead = NULL; 
    pPacketHead = (LPHEADER)&m_Packet.head; 
    pPacketHead->uiCmd  = htonl(nCmd); 
    pPacketHead->uiFlag = htonl(nFlag); 
    pPacketHead->uiLen  = htonl(nLen); 
    pPacketHead->uiType = htonl(nActType); 
 
    //strcpy(m_Packet.szBody , strPacket); 
 
    memcpy(m_Packet.body , PacketBody, nLen); 
 
    //패킷이 포맷되었음 
    m_bPrepare = TRUE; 
} 
받는 쪽에서 읽을때도 마찬가지로 같은 방식으로 읽어온 만큼 memcpy를 사용해서 쓰면 되겠죠.고정 패킷이지만 BODY_SIZE보다 보낼 파일의 내용이 작을수도있기 때문에.. 
OnReceive() 
{ 
    switch(ntohl(m_Packet.head.uiType)) 
    { 
        case 파일전송: 
        { 
            BYTE fileBuf[BUF_SIZE]=""; 
            UINT uiBufLen = ntohl(m_Packet.head.uiLen); 
            memcpy(fileBuf , m_Packet.body, uiBufLen); 
            m_SaveFile.Write(fileBuf, uiBufLen); 
        } 
        break; 
    } 
} 
읽어 올때 만약 소켓에서 읽어온 만큼 파일로 쓰면 되지않습니까? 맞습니다만.. memcpy가 아닌 strcpy같은 함수를 사용하시면 물론 읽다가 다읽은 패킷도 짤라먹게 되겠죠. 에초의 의도가 발생할수 있는 에러이기 때문에 이정도에서 끊어야겠죠. 그럼 패킷은 제대로 받았는데 파일이 이상하게 저장됐다는 분들 참조하십시오. --이현배(leehb1592@hotmail.com

9 UNIX상에서 한글출력이 깨져 나올경우

유닉스상에서 한글을 stdout출력할 경우 가끔 출력되는 문자들이 몽땅 깨져서 나오는 경우가 있다. 이때부터는 프로그램이 종료된 이후에도 쉘 프람프트를 비롯, 쉘에서 입력하는 모든 커맨드가 깨져서 나온다. 이는 ascii code 로 ^n 에 해당하는 문자가 출력될 때 나오는 현상으로 그 이후로는 MSB가 모두 켜지기 때문이다. 문자가 깨져나오는 이후부터 ascii code 로 ^o 에 해당하는 문자를 출력하면 반대로 된다. 쉘 커맨드 상에서라면, 
# echo ^v^o 
라고 해야겠지만 커맨드가 깨져나오므로 shell이 해석을 못한다. 따라서, command line에서 
^v^o를 치고 enter 하면 된다.

10 gcc extension

C99 의 gcc extension 이라는데 배열의 크기를 run-time 시 결정하고 초기화를 일부만 하는것이 가능하다. 
예제 
#include "stdio.h" 
 
char array[10] = 
{ 
    [1] = 2, 
    [3] = 3, 
    [5] = 35, 
    [7] = 99, 
};  
void nim(int i) 
{ 
    char hello[i]; 
 
    if(i > 10) { 
        strcpy(hello, "world"); 
        printf("hello %s\n", hello); 
    } 
 
} 
int main() 
{ 
    int i; 
 
    for(i=0;i<10;i++) 
    { 
        printf("array[%d] = %d\n", i, array[i] ); 
 
    }  
 
    nim(20); 
 
    return 0; 
} 

11 C++에서 전역함수는 이름변형에 주의하라

C++ 컴파일러는 함수를 컴파일 할 때 이름 변형name mangling을 하게 된다.(중복정의Overloading 때문에) 이것에 주의하지 않고 전역함수를 그냥 선언해서 다른 모듈에서 호출하면 undefined symbol 에러가 발생한다. 내부 모듈이 아닌 다른 오브젝트에서 전역함수를 호출해서 쓸 경우는 반드시 extern "C" 키워드를 붙여줘야 하며 윈도우 프로그래밍에서 함수 이름을 인자로 넘겨주는 API들(예를 들면 GetProcAddress같은)에서 함수주소를 얻어 올 때 이름변형을 막기 위해서 인자로 넘겨주는 함수에는 extern "C"로 선언된 함수여야 한다. 예를 들면 다음의 코드를 보자. 
simple.c 
__declspec (dllexport) void Demo(void) 
{ 
    ...... 
} 
 
int main(void) 
{ 
    HMODULE hmod; 
    void (*zzz)(); 
 
    if ( (hmod=LoadLibrary("simple.exe")) && (zzz=(void(*)())GetProcAddress(hmod, "Demo"))) 
         zzz(); 
 
    return 0; 
} 
위의 코드는 문법적 결함은 없지만 VC++에서 빌드하고 실행해보면 메모리에 simple.exe가 로딩되지만 GetProcAddress에서 함수를 찾지 못하고 NULL을 반환할 것이다. C++컴파일러에서 Demo함수를 재정의 했기 때문이다. 이때 Demo함수를 다음과 같이 선언해서 재정의 되는 것을 막아야 한다. 
extern "C" __declspec(dllexport) void Demo(void) 
참조: [WWW]Re: Name mangling 

12 VC++에서 인자전달 방식 비교

Written by jazonsim 
비주얼씨에서 사용하는 인자전달방식은 __cdecl, __stdcall, __fastcall등이 있다. 
__cdecl은 인자를 오른쪽에서 왼쪽으로 스택에 저장하고(스택정리는 호출한 쪽) 
__stdcall은 __cdecl하고 비슷하나 스택정리를 호출당한 쪽이 하고, 
__fastcall은 register(ECX, EDX)에 인자 두개를 넣고 남는 것은 스택에 저장한다. 
이중에서 VC는 __cdecl방식이 Default로 정의되어 있다. 그런데 Dos게임제작에 잘 사용되었던 WatcomC는 Default가 레지스터방식인 __fastcall방식을 사용하여 타사의 C컴파일보다 실행화일의 속도를 보다 향상 시켜주었다. (물론 WatcomC와 VC의 레지스터방식은 사용하는 레지스터갯수가 다르다) 
그래서 필자는 VC에서 __cdecl방식의 함수와 __fastcall방식의 함수의 실행속도를 비교해 보았더니 인자의 수에 따라서 속도의 차이가 많이 났다. 함수의 인자수가 2개이하일 경우는 속도의 차가 거의 없었다. 왜냐하면 Disasseble해보니 서로가 아주 약간의 명령어가 더 추가되거나 없을 뿐이기 때문이었다. 그러나 인자의 수가 3, 4개인 경우는 __fastcall방식이 __cdecl방식보다 적어도 3배이상은 빠른 모습을 보여주었다. (시간을 체크하기 위해 루프를 100000번 돌렸을 경우) 
하지만 다시 인자의 수가 5개로 늘어난 경우에는 __cdecl방식이 __fastcall방식보다 빨라지는 현상을 보였다. 궁금하여 이를 분석해보니 인자수가 많아질수록 __fastcall은 인자전달에 사용되는 register를 함수내부에서 사용하기 위해 인자값을 push하고 다시 pop하는 루틴이 많아져 __cdecl방식보다 느려지는 결과를 나타내는 것이었다. 
사실 인자가 3, 4개인 경우를 Disasseble해 보았더니 함수 그 자체만을 보았을 경우에는 __fastcall방식이 __cdecl방식보다 코드수가 더 많은 것을 볼수가 있다. 그런데 인자의 수가 3, 4개일경우 __cdecl방식이 함수를 호출하기전에 스택에 인자를 처리하는 작업 때문에 __fastcall방식보다 절묘한 차이로 보다 느린 것으로 보인다. 
그러므로 __fastcall방식으로 구현할 함수는 내용이 간단하고 인자가 3개에서 4개가 가장 적당하다고 할수 있다. 왜냐하면 함수의 내용이 복잡하거나 길면 인자수가 2개이하일때와 비슷한 현상이 발생하기 때문이다. 
see also calling convention 

13 ISO C와 ISO C++의 차이점

see http://david.tribble.com/text/cdiffs.htm 

14 C FAQ

see http://www.eskimo.com/~scs/C-faq/top.html :)

15 new와 delete로 배열을 선언할 때

보통 필자도 클래스 객체를 할당할 때는 new를 쓰고 배열이나 구조체를 할당할 때는 malloc을 쓰고 있다. 그러나 C++에서 프로그래밍하는 것이라면 malloc/free 보다는 new/delete를 쓰는 습관을 들이는 것이 좋다. 클래스의 객체를 할당하는 경우 malloc으로 객체를 할당하면 할당될 당시 생성자가 호출되지 않기 때문이다. 
일반 배열이나 구조체도 new로 할당해서 쓸 수 있다. 이때 주의할 점은 배열을 할당받았다면 delete로 메모리를 반환할 때는 변수 앞에 []를 붙여줘야 한다. 
char *pstrTest = new char[100]; 
    .... 
delete [] pstrTest; 
위에서 delete로 메모리를 반환할 때 []를 빼버리면 pstrTest가 가리키는 한 바이트의 메모리 영역만 반환된다. []를 붙여줘야 100바이트의 영역이 모두 반환된다. 
하나의 원소만을 할당받을 땐 [] 없이 메모리를 반환하면 되지만 둘 이상의 원소를 할당받을 땐 []를 만드시 delete 다음에 표기해 주어야 한다. 
그외 malloc으로 할당했는데 delete로 해제한다던지, new로 할당받고 free로 해제하는 실수를 하지 않도록 주의한다. 

16 천단위 comma 삽입 코드

GUI 프로그래밍을 하다보면 금액을 출력하는 필드에 천단위 마다 comma를 삽입해야할 필요가 있다. 이때 다음의 코드를 참고하자. 
1. python 
import string 
 
def convert_num(number): 
        if number[0] in ['+', '-']: 
                sign_mark, number = number[:1], number[1:] 
        else: 
                sign_mark = '' 
 
        try: 
                tmp = string.split(number, '.') 
                num = tmp[0]; decimal = '.' + tmp[1] 
        except: 
                num = number; decimal = '' 
 
        head_num = len(num)%3 
        result = '' 
        for pos in range(len(num)): 
                if pos == head_num and head_num: 
                        result = result + ',' 
                elif (pos - head_num)%3 == 0 and pos: 
                        result = result + ',' 
                result = result + num[pos] 
        return sign_mark + result + decimal 
2. C 
3. MFC 
CString formatCurrency(_int64 amount) 
{ 
        int nCntLen = 0; 
        int nCntThree = 0; 
        char szBuffer[20]; 
        CString strSource; 
         
        strTemp.Empty(); 
         
        _i64toa(amount, szBuffer, 10); 
 
        strSource.Format("%s", szBuffer); 
 
        nCntLen = strSource.GetLength(); 
        for (int i = nCntLen - 1; i >= 0; i-- )  
         { 
                strTemp = strSource[i] + strTemp; 
                nCntThree++; 
 
                if ( (i != 0) && (nCntThree == 3) ) {  
                        strTemp = "," + strTemp; 
                        nCntThree = 0; 
                } 
        } 
        if(strTemp.Find('-', 0) > -1){ 
                int npos; 
                npos = strTemp.FindOneOf(","); 
                if(npos == 1) 
                        strTemp.Delete(npos, 1); 
        } 
        return strTemp; 
} 

17 strace, truss, tusc를 활용한 디버깅

[WWW]디버깅 이야기: strace, truss, tusc 

18 Log 함수

윈도우 프로그래밍을 하다보면 가끔 dll 소스코드를 디버깅해야할 때가 있는데 이때 MSDEV의 디버거를 이용할수도 없기 때문에 디버깅하기가 까다롭다. 때론 printf대용인 MessageBox를 사용하기도 했는데 아무래도 썩 좋은 방법은 아니다. 이때 로그 함수를 만들어서 로그 파일을 생성하는 방법도 유용한 대안이다. 
아래는 Nasser Remy Rowhani라는 외국 친구가 쓴 코드인데 써보니 쓸만한 코드라 생각되서 소개한다. 굳이 디버깅 용도뿐만 아니라 API를 후킹하는 응용에서도 유용하게 쓸 수 있다. 
#define LogFile "d:\\Logs\\LOG.txt"                   // log파일이 생성되는 위치 
#define Append(text) AppendLog(text, strlen(text))    // log를 기록하는 함수 
 
HANDLE   hLogFile=0;                // log파일의 핸들 
BOOL     IsLogging=false;           // log파일이 제대로 open되었는지 체크하는 플래그 
 
HANDLE OpenLog(char *Filename);     
BOOL CloseLog(HANDLE h=hLogFile);    
DWORD AppendLog(char *str, DWORD uSize, HANDLE h=hLogFile); 
 
HANDLE OpenLog(char *Filename) 
{ 
        HANDLE hLogFile; 
 
        hLogFile = CreateFile( Filename, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_ALWAYS,0,0); 
        if(hLogFile!=INVALID_HANDLE_VALUE) 
                IsLogging = true;//SetFilePointer(hLogFile, 0,0, FILE_END);//*/ 
         
        return hLogFile; 
} 
 
BOOL CloseLog(HANDLE h) 
{ 
        IsLogging = false; 
        return CloseHandle(h); 
} 
 
DWORD AppendLog(char *str, DWORD uSize, HANDLE h) 
{ 
        DWORD written; 
        if(!IsLogging) return 0; 
 
        SetFilePointer( h, 0, 0, FILE_END ); 
        WriteFile(h, str, uSize, &written, 0); 
 
        return written; 
} 
용례: 
hLogFile = OpenLog( LogFile );   // 처음엔 log파일을 open한다. 
   ... 
sprintf(buf, "Error Code: %d", dwErr); 
Append(buf); 
   ... 
CloseLog();                     // 프로그램에서 exit할 때 log를 close