2009년 05월 25일
이사갑니다.
이글루는 java script 제한을 걸어나서 좀 불편하군요.. 티스토리로 이사갑니다.


http://buzzan.tistory.com


by buzzan | 2009/05/25 14:10 | 트랙백 | 덧글(0)
2009년 04월 29일
재미있는 C언어5. printf 에서 생긴일

[NSD][CRT][Tim:0][Err:00000][FileSetAttributes(#1286)@ns_file.c]: Native set attr (0) (0) fails. ERR[0]

SQA  도중에 위와 같은 메시지가 출력되었다는 것이었습니다.

[CRT] 레벨의 에러는 절대 발생해서는 안되는 에러이기 때문에 사뭇 심각했습니다. 물론 나중에 별 문제가 아니라고 밝혀 졌지만,

 

진짜 문제는 ERR[0]이었습니다.

 

문제의 코드를 보면

  1. err = pVnode->pVnodeOps->pfnSetAttributes(pVnode, (dwAttribute & FILE_ATTR_MASK));
    if (FERROR_NO_ERROR != err)
    {

    NSD_EMZ((_T("Native set attr (%u) (%d) fails. ERR[%d]"), NsVnodeGetIndex(pVnode), dwAttribute, err));    break;
    }

위 코드를 보면 err이 FERROR_NO_ERROR이 아닐 때 즉, err이 0이 아니어야만 if안으로 진입하여 메시지를 찍을 수 있는 것이었습니다.

 

"에러가 0인데 메시지가 출력되었다!"

어떻게 이런일이 발생 할 수 있을까요??? 

err가 0이 아니었기 때문에 if안으로 진입한 것인데 정작 err은 0이었다니... 송선임님이 안가르쳐 주셨다면 한참 고민했을 겁니다.

 

범인은 %u였습니다. NsVnodeGetIndex()의 리턴값은 unsigned long long형 이었습니다. 크기가 8byte라는 것이지요.

8byte의 변수를 4byte unsigned int 구분자인 %u를 사용하여 4byte만큼 찍고 나머지 4byte는 그대로 뒤로 밀려 버렸습니다.

그결과 (%u)와 (%d)에 NsVnodeGetIndex()의 리턴값 8byte가 4byte씩 나누어 찍히고, ERR[%d]는 뒤 따라오는 dwAttribute가 찍힌 것이었습니다. (우연히도 NsVnodeGetIndex()와 dwAttribute의 값이 모두 0이었습니다.)

정작 err은 출력 구분자(%)가 없어 출력되지 못했습니다.

위 코드를 아래와 같이 고쳐 해결 하였습니다.

  1. NSD_EMZ((_T("Native set attr (%llu) (%d) fails. ERR[%d]"), NsVnodeGetIndex(pVnode), dwAttribute, err));

printf의 출력 구분자와 변수의 type이 맞지 않아도 컴파일러는 경고나, 에러를 발생하지 않습니다.

 

ps 오늘의 교훈 : 에러 메시지 출력을 잘못해도 SQA를 통과 못할 수도 있습니다. 

by buzzan | 2009/04/29 10:55 | 재미있는 C언어 | 트랙백 | 덧글(1)
2009년 04월 29일
재미있는 C언어4. char pBuffer[1]의 역할은?
  1. struct _BUFFER_INFO
    {
        BUFFER_MANAGER                stBM;                // buffer manager
        char                        pBuffer[1];            // global buffer cache buffer
    };

 

_BUFFER_INFO는 buffer cache의 전체정보를 담고 있는 자료구조로 크게 entry들을 관리하는 BUFFER_MANAGER와 실제 data가 저장되는 pBuffer로 나뉘어 있습니다.

 

char pBuffer[1]를 유심히 살펴 보세요. 엥? 이건 무슨 코드인가요?

왜 char *pBuffer 라고 하지 않았을까요?

 

BUFFER_INFO는 buffer cache가 초기화 될 때 전체 메모리를 할당 받게 됩니다. 

만약 char pBuffer[1]이 아닌 char* pBuffer였다면 BUFFER_INFO의 초기화는 이렇게 되었어야 합니다.

  1. static PBUFFER_INFO            g_pBI;       
  2. g_pBI = RtlAllocMem(sizeof(BUFFER_INFO));
    IF (NULL == g_pBI)
    {
       NSD_CMZ((_T("g_pBI allocation fail, ERR[%d]"), err));
       err = FERROR_INSUFFICIENT_MEMORY;
       break;
    }
  3. g_pBI->pBuffer = RtlAllocMem(BCACHE_SIZE);
    IF (NULL == g_pBI->pBuffer)
    {
        NSD_CMZ((_T("g_pBI->pBuffer allocation fail, ERR[%d]"), err));
        RtlFreeMem(g_pBI);
        g_pBI = NULL;
        err = FERROR_INSUFFICIENT_MEMORY;
        break;
    }

즉, BUFFER_INFO와 pBuffer의 메모리를 두번 할당 받아야 합니다. 물론 해제 할때도 마찬가지 입니다.

 

char pBuffer[1]이라면 어떻게 될까요?

  1. static PBUFFER_INFO            g_pBI;   
  2. g_pBI = RtlAllocMem(sizeof(BUFFER_INFO)+ BCACHE_SIZE);
    IF (NULL == g_pBI)
    {
        NSD_CMZ((_T("g_pBI allocation fail, ERR[%d]"), err));
        err = FERROR_INSUFFICIENT_MEMORY;
        break;
     }

메모리 할당이 한번으로 끝났습니다.  중요한점은 BUFFER_INFO와 pbuffer가 메모리상으로 한덩어리로 잡힌다는 것입니다.

이러한 접근은 메모리 할당을 두번에서 한번으로 줄인것 말고도 좋은 점이 있습니다.

만약 이 BUFFER_INFO를 Log와 같이 File에 주기적으로 저장을 한다고 하면 어떨까요? char *pBuffer 로 사용했을 경우는 이 자료구조에 두번 접근해야 합니다. 필요없는 memcpy도 수반될 수 있습니다. 또한, BUFFER_INFO 와 pBuffer가 메모리 상으로 떨어져 있기 때문에 CPU에서 cache miss가 발생하는 확률도 높아집니다.

 

char pBuffer[1];

별거아닌거 같지만, 코드도 짦아지고, 수행시간도 짧아질 수 있어 좋은 테크닉인 것 같습니다.

char pBuffer[0]도 의미는 유효하지만, C90표준에서는 허용하지 않고 있습니다. 일부 컴파일러는 경고나 에러를 낼 수도 있습니다.

 

ps. 하지만, 가독성은 좀 떨어지네요. ㅎㅎ

ps2. C99 표준에서는 가변길이 배열을 허용하고 있습니다. 즉, char pBuffer[] 도 가능합니다.

 

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

by buzzan | 2009/04/29 10:54 | 재미있는 C언어 | 트랙백 | 덧글(0)
2009년 04월 29일
재미있는 C언어3. 넌 어느 엔디안이니?

Endian에 관한 문제는 embedded system에서는 항상 따라다니는 요소입니다.

little-endian에서는 제대로 돌아가는 프로그램이 big-endian에서는 오류를 일으키거나 하는 일은 다반사죠.

그래서 두가지 endian을 지원하기 위해 똑같은 테스트를 반복해서 하는 경우도 있죠.

 

보통은 매크로를 사용하여 빌드시 endian을 정하기도 하지만, runtime시 정할 수도 있습니다.

다음의 코드는 실행환경의 endian을 알아내는 코드입니다. (소스 수정 없이 두가지 endian을 테스트 할 수 있음을 의미합니다.)

 

  1. #include <stdio.h>

    int main(void)
    {
        union
        {
            unsigned long int i;
            unsigned char uc[sizeof(long int)];
        }u = {1};

        if(u.uc[0] == 1) printf("little-endian");
        else if(u.uc[sizeof(long int)-1] == 1) printf("big-endian");
        else printf("unknown endian??");

        return 0;
    }

 

union은 잘사용되지 않는 자료형이지만 유용하게 사용되었네요.

 

이 글은 스프링노트에서 작성되었습니다.

by buzzan | 2009/04/29 10:51 | 재미있는 C언어 | 트랙백 | 덧글(0)
2009년 04월 29일
재미있는 C언어2. \0이라고 안심할 수 없다.

아래 코드의 실행 결과는 어떻게 될까요?

  1. #include <stdio.h>

    #include <string.h>

     

    int main(void)

    {

            int a = strlen("123");

            int b = strlen("123\0");

            int c = strlen("123\012");

            int d = strlen("123\0123\0ABC");

            int e = strlen("123\0ABC");

            int f = strlen("123\0123");

            int g = strlen("123\0""123");

            printf("a:%d, b:%d, c:%d, d:%d, e:%d, f:%d, g:%d\n", a, b, c, d, e, f, g);

            return 0;

    }

 

 

지난번 삼중자와 마찬가지로 결과를 예측하기 쉽지 않습니다.

 

힌트 : vim에서 위 코드를 열어보세요.

 

이 글은 스프링노트에서 작성되었습니다.

by buzzan | 2009/04/29 10:50 | 재미있는 C언어 | 트랙백 | 덧글(0)


<< 이전 페이지 | 다음 페이지 >>