현재 가장 많이 사용되는 컴퓨팅 시스템은 32bit 주소기반이지만, 향후 PC를 중심으로 64bit 으로 넘어갈 전망이다.

C언어의 경우 프로그램에서 사용되는 정수형을 살펴보면, short, int, long, long long 이 있는데 사용되는 시스템의 성격에 따라 32bit과 64bit에서 그 크기가 다를 수 있다.

예를 들어 int 형의 크기가 32bit 시스템에서는 32bit 으로 표현되지만, 64bit 시스템에서는 64bit으로 표현될 수도 있다. (ILP64)
이것은 32bit 시스템에서 잘 돌아가던 프로그램이 64bit에서는 오류를 일으킬 수 있다는 것인데, 프로그램 작성시 신경을 쓸 필요가 있다.

이러한 규칙을 정한 모델이 있는데 아래와 같다.


원래 int형은 그 시스템에서 가장 효율적이라고 추천하는 데이터 형이기 때문에 그 시스템의 word형으로 정한다.
따라서 64bit 시스템에서는 int형을 64bit으로 정하는 것이 이치이다. 하지만, 앞서 언급한 32bit과의 호환 문제 때문인지는 확실하지 않으나 Windows나 Unix 진형 모두 64bit 시스템에서도 int형을 32bit으로 유지하고 있다. 
int형과 달리 long형에서는 서로 다른 입장을 보이고 있다.

LP64진영의 주장은 프로그램에서 메모리 주소를 처리하는 변수를 int형으로 할 경우 64bit시스템의 주소를 모두 커버할 수 없어 주소를 담는 변수는 long형으로 하여 64bit시스템에서 문제가 없게 하자는 것이다. 개인적으로 이 주장은 설득력이 있다.

이러한 혼란을 막기 위해 프로그램시 int, long과 같이 정수형 예약어 그대로 사용하지 않고 tint32 또는 tint64 식의 재정의 하여 사용하는 기법이 주로 사용된다.

그러나, 잘 생각해 보면 primitive형을 사용해도 상관없다.  왜냐하면 ILP64나 SILP64는 거의 사용되지 않는 데이터모델이기 때문에 제외하고 LLP64와 LP64만 고려하면,  long형만 다른데 long형은 사용하지 않으면 된다.

메모리의 주소를 다룰때도 보통 포인터형을 사용하기 때문에 문제가 없다.

결론은 LP64, LLP64 고민하지 말고 프로그래밍시 int와 long long만 사용하도록 하자.

저작자 표시 비영리 변경 금지
신고
Write your message and submit
예전(2.4.x) 리눅스 커널의 double linked list 관련 소스를 보다보면 약간 당황스런 코드를 접하게 된다.

/**
 * list_entry - get the struct for this entry
 * @ptr:        the &struct list_head pointer.
 * @type:       the type of the struct this is embedded in.
 * @member:     the name of the list_struct within the struct.
 */
#define list_entry(ptr, type, member)     ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

이 매크로는 어떤 의미일까??  매크로를 보기 편하게 분해 해 보면.

(  (type *)   ( (char *)(ptr) - (unsigned long)(&((type *)0)->member))  )

빨간색에서 파란색을 뺀 값을 type의 포인터 형으로 형변환하는것을 알 수 있다. 좀 더 자세히 풀면,
char 포인터형인 ptr에서  시작주소가 0인 type의 맴버변수인 member의 주소를 unsigned long형태로 빼고 있다.

그림으로 살펴보면 아래와 같은데.


주소가 0인 type 구조체의 맴버변수인  member의 주소는 결국 type구조체에서 member의 offset을 의미한다.
종합해 보면 ptr은 type구조체의 member를 가리키고 있는데 여기에서 type 구조체내의 member의 offset을 뺀것을 의미한다.

결국 원하는 것은 member의 주소를 가지고 type구조체의 시작주소를 얻고 싶은것이다.
list_entry 구조체를 사용하면 member list에 엮여 있는 type구조체의 시작주소를 얻을 수 있고, 시작주소를 얻는다는 것은 type구조체의 모든 맴버변수에 접근할 수 있다는 것이다.

지금까지 어렵게 list_entry 매크로를 언급한 이유는 offetof 매크로를 설명하기 위함인데..

offsetof 는 표준 라이브러리 <stddef.h> 에서 제공하고 있는 매크로로 어떠한 맴버변수가 구조체안에서 어떤 위치에 있는 지 알려준다. offsetof와 sizeof연산자를 활용하면 어떤 구조체가 메모리에 배치되는 모양을 완벽하게 그려낼 수 있다.

사용법은 offsetof(구조체형, 맴버명) 을 입력하면 size_t형의 정수로 맴버의 구조체 상의 배치되어 있는 offset을 알려준다.

struct foo 
{
	char c;
	short int si;
	double d;
};


int main()
{
	printf("%lu\n", (unsigned long)sizeof(struct foo));
	printf("%lu\n", (unsigned long)sizeof(short int));
	printf("%lu\n", (unsigned long)offsetof(struct foo, si));
	printf("%lu\n", (unsigned long)sizeof(double));
	printf("%lu\n", (unsigned long)offsetof(struct foo, d));
	
	return 0;
}

위 예제의 결과는 임플리멘테이션에 따라 다르게 나올 것이다.

다시 list_struct로 돌아와서 offsetof연산자를 이용하면 위 매크로는 좀더 단순하게 바꿀 수 있다.

((type *)((char *)(ptr)-(unsigned long)(offsetof(struct type, member))))

ps. 실제로 offsetof는 아래와 같이 정의되어 있다.

#define offsetof(s,m)   (size_t)&(((s *)0)->m)



저작자 표시 비영리 변경 금지
신고
Write your message and submit
ATPCS 란?

C/C++ 언어에서 함수를 호출하거나 호출 받을 때는 매개변수나 반환값을 주고 받게 되어있다. 이러한 규칙을 PCS (Procedure Call Standard)라고 한다.

우리가 편지를 보낼때 좌측상단에는 보내는곳의 주소, 우측하단에는 받는 곳의 주소를 적으면 어느나라든 보낼 수 있는 것 처럼 함수간에도 그러한 규칙이 존재 하는 것이다.

ATPCS 는 ARM모드와 Thumb 모드 둘 다 호환되는 함수 호출 규약을 말한다.

ARM 레지스터

ARM 아키택쳐에는 다음과 같은 ARM모드와  Thumb모드에서 사용하는 레지스터 들이 있다.


ARM모드에서는 r0 부터 r15까지 사용하고 Thumb 모드에서는 r0부터 r7 에 r13, r14, r15 레지스터를 사용한다.
r0부터 r3까지의 4개는 함수 호출시 매개변수와 함수 수행수 반환되는 값을 저장하는 용도로 사용된다. 일반적으로 r4부터 r11까지는 함수의 지역변수로 사용된다. (물론 Thumb모드에서는 r7까지 사용한다.)

프로그램 성능 최적화를 거론할 때 매개변수는 4개, 지역변수는 8개 이내로 사용하는 것이 좋다는 이야기는 레지스터 사용을 효율적으로 하기 위함이다. 레지스터가 부족하게 되면 매개변수는 스택에 공간을 마련해야 하며, 지역변수는 힙(Heap)영역에 저장해야 한다.

r12부터 r15까지는 특수한 용도로 사용되는데 그 역할이 특별하여 IP, SP, LR, PC 라고 불린다.

함수가 호출되는 과정

함수 호출은 LR에는 돌아올 주소를 저장하고 PC에는 호출되는 함수의 주소를 넣어 그곳으로 이동한다.
돌아올 때는 저장한 LR을 다시 PC에 넣어주면 된다.

매개변수는 r0 ~ r4까지 사용하는데 만약 32bit이 넘는 long long 같은 변수는 32bit씩 쪼개어 r1, r2로 나누어 저장한다.
(함수의 원형을 long long형 변수를 int형 변수 2개로 분리하여 구현된 함수가 있는데 사실 그럴필요는 없다.)

리턴값은 r0를 사용한다. 매개변수와 마찬가지로 long long형을 반환할때는 r0, r1를 사용한다.

그림. 매개변수와 리턴값 저장

STACK UNWINDING

굳이 우리말로 번역하자면 스택 복기 정도가 되겠다.

함수 호출이 되풀이 되면 각 함수마다 하위 함수 호출시 자신의 정보(지역변수, 리턴값)를 복구하기 위해 함수 호출 규약에 의해 d위 그림과 같이 스택에 쌓게된다. 스택을 따라 역으로 추적하면 자신의 함수를 호출한 함수들의 정보를 알 수가 있는 것이다.
이 정보는 디버깅시 유용하게 사용되는데, 시스템이 예외가 발생하거나, 무한 루프에 빠질경우 이런한 원인을 찾는데 꼭 필요하다.

STACK OVERFLOW

위와 같이 스택은 함수 호출관계에 있어 중요한 정보를 담고 있다. 만약 스택으로 할당된 공간이 부족하여 넘치거나 힙영역에서 침범해 오는 경우. 시스템의 치명적인 오류가 발생하게 되는 것이다.


저작자 표시 비영리 변경 금지
신고
Write your message and submit

Simple Memory Allocation Algorithms

Posted 2009.06.09 10:11
Have you got stucked in the development while aiming to implement a modern and very efficient alorithm? Here's a start up point before getting to implement them.

Simple Memory Allocation Algorithms

Memory allocation is a process that assigns chunks of memory upon request of the various processes running on a machine. Today, there are several well known efficient algorithms used on high-end operating systems but their mathematical models are quite difficult to comprehend from the first time by most amateur operating system developers. That’s why most of the simple operating systems available on the amateur websites use less efficient memory allocation algorithms but highly comprehensible and very educative.

This article focuses on three simple algorithms you may want to experiment on your osdev projects.

The Simplest Algorithm

The complexity of an algorithm is given by the number of steps, their iteration and their timing. Since we are talking about the simplest one, it will have the fastest memory release function: it will do nothing!

The allocator will just store the address of the next free available memory block and allocate requested chunks continuously. A brief implementation follows:

#define MAX_MEMORY 1024 * 1024 * MEMORY_SIZE_IN_MEGABYTES

int peak;

void *malloc(size_t size) {
    void *current;
if( MAX_MEMORY ? peak < size ) return null;
    current = (void *)peak;
    peak += size;
    return current;
}

void free(void *ptr) {
    return;
}
Extremely inefficient, but stable, no different block will overlap to cause memory sharing violations. Since the memory will be consumed after a certain number of allocations, we must implement a mechanism to re-enter the released blocks back in a list of available locations.

The “First Fit” Algorithm

The first fit algorithm basically extends the previous one by implementing a list of free blocks. When the allocator will receive a request of X bytes, it will lookup the list for an available block to fit X bytes. Since aiming to find a perfect match my yield negative results we will allocate memory for the X bytes in the first block equal or greater than X.

Of course, this will lead to an undesired effect: memory fragmentation. Larger block will be split into smaller ones to fit the requested amounts of memory and we may end up with the impossibility of serving a process requesting Y bytes although the sum of all free, but small, blocks is way greater than Y.

A brief C-like implementation follows.

#define MAX_MEMORY 1024 * 1024 * MEMORY_SIZE_IN_MEGABYTES
// using a fixed size array isn’t the best idea,
// the best implementation would be to manage the data with a chained list
#define LIST_ITEMS 100

typedef struct {
    size_t size;
    size_t first_byte_ptr;
} list_item;

list_item list[LIST_ITEMS];
list_item allocted[LIST_ITEMS];

int j = 0;

void *malloc(size_t size) {
    int i;
    void *current;

    if( j < LIST_ITEMS ) return null; // no slots

    for( i = 0 ; i < LIST_ITEMS ; i++ )
        if( list[i].size >= size ) break;

    if( i == LIST_ITEMS ) return null; // no suitable block found

    current = (void *)list[i].first_byte_ptr;
    list[i].first_byte_ptr += size;
    list[i].size -= size;

    allocated[j].first_byte_ptr = current;
    allocated[j].size = size;
    j++;

    return current;
}

void free(void *ptr) {
    int i;

    for( i = 0 ; i < LIST_ITEMS ; i++ )
        if( allocated[i].first_byte_ptr == ptr ) break;
    
    for( k = 0 ; k < LIST_ITEMS ; k++ )
        if( list[k].size == 0 ) break;

    list[k].size = allocated[i].size;
    list[k].first_byte_ptr = allocated[k].first_byte_ptr;

    allocated[i].first_byte_ptr = 0;
    allocated[i].size = 0;

    return;
}

There is enough room for optimization for the above code, especially for the management of the two lists. Chained links are optimal and sorting them when the CPU is idle is a good idea, especially if followed by testing if any blocks are adjacent in order to be merged.

The “Buddy” Algorithm

The buddy algorithm is an optimized version of the “first fit” allocator. It will only allocate blocks of certain sizes, maintaining lists for each series of blocks of a specific size. The permitted sizes are often powers of two, aiming to match blocks of basic types which have sizes of powers of two and because splitting a lager block in two will result a pair of permitted blocks of inferior permitted size. Some implementations use other series of sizes, such as the Fibonacci sequence.

The main advantage compared to “first fit” is speed. We know exactly where to lookup for free blocks, and if we don’t find one we go straight to the next list, of larger blocks. But as its ancestor, it suffers from memory fragmentation.

Let’s suppose a process requests a block of 7 bytes. The allocator will automatically lookup the list of 8 bytes blocks and assign one. There will be a one byte loss in the allocated block. But what if the process then requests a block of 9 bytes? It will look up the 16 bytes block list and return one block. The loss will consist in 7 unused bytes.

That may not sound very tragic, but multiply the amounts with 1K or 1MB to get the figures quite closed to the reality. The lost memory space is common known as slack space. Fragmentation can be reduced by making the block sizes closer together.



저작자 표시 비영리 변경 금지
신고
Write your message and submit
어떤 객체를 표현하는 여러 변수를 맴버로 가지고 있는 구조체가 있다고 가정하자.

이 구조체의 인스턴스를 캐슁 해야는 상황이라면 이 구조체가 클수록, 인스턴스가 많을 수록 메모리를 많이 필요로 하게 된다.

하지만, 구조체의 맴버 변수들이 아주 작은 범위의 값(char형 보다 작은)을 갖는다고 한다면 메모리가 아깝다는 생각이 든다. 이때 요긴하게 사용할 수 있는 기능이 비트필드이다.

선언 방법은 맴버를 일반적인 정수형으로 선언하되 콜론(:)과 비트필드의 크기를 적어주면 된다.

struct foo 
{
	unsigned int a:4;
	unsigned int b:5;
	unsigned int c:6;
};
위와같이 선언하면 비록 unsigned int로 선언되어 있지만, 각각 4, 5, 6bit씩 메모리에 할당되게되어 그만큼 메모리를 절약할 수 있다.

sizeof(struct foo) 를 출력해보면 4가 나오는 것을 확인 할 수 있다. 이것은 실제 사용하는 bit의 값이 4 + 5 + 6 = 15bit 이지만, 변수 타입이 unsigned int이기 때문에 그렇게 되는 것이다.

struct foo 
{
	unsigned short int a:4;
	unsigned short int b:5;
	unsigned short int c:6;
};
이 경우 sizeof(struct foo)의 값은 2가 된다. 사용법은 일반적인 구조체의 사용방법과 다르지 않지만, 변수 대입시 설정된 비트 이상은 표현하지 못한다.
struct foo 
{
	unsigned int a:4;
	unsigned int b:5;
	unsigned int c:6;
};


int main()
{
	struct foo o;

	o.a = 0xFF;
	o.b = 0xFF;
	o.c = 0xFF;
	
	printf("foo size : %d\n", sizeof(o));
	printf ("%08x, %08x, %08x\n", o.a, o.b, o.c);
	return 0;
}
프로그램의 결과는 다음과 같다. 

 foo size : 4

0000000f, 0000001f, 0000003f 

상황에 따라 나름대로 유용하게 사용될 수도 있는 기능이지만, 애석하게도 사용에 많은 제약이 있다. 

임플리멘테이션(보통 컴파일러라고 표현한다.)에 따라 비트의 메모리상 배치가 다를 수 있어 주의하지 않으면 환경에 따라 중대한 버그를 만들 수 있다.

이러한 이식성 문제 때문에 실제 사용되는 코드를 만나기란 쉬운일이 아니다.

C언어 표준이 좀더 일찍, 임플리멘테이션까지 규칙을 정했으면 좀더 재미있는 C언어가 되지 않았을까? 하는 생각이 든다.

저작자 표시 비영리 변경 금지
신고
Write your message and submit

Windows XP에서 exFAT사용하기

Posted 2009.06.02 13:44
http://support.microsoft.com/?kbid=955704 에서 관련 system file을 다운로드 받을 수 있습니다. WindowsXp SP2, SP3를 지원합니다.

설치후 리부팅하면 exFAT으로 포멧하여 사용할 수 있습니다.


exFAT은 SD카드 협회에서 32GB이상의 카드에서 공식적으로 사용하기로한 filesystem입니다.

신고
Write your message and submit

버퍼 캐쉬

Posted 2009.06.02 10:42
그 좁디 좁은 통로에서 
왜 서로 버둥기며 다투나

들어 온 순서대로 나가라는 법이 있나?

급하면 급한대로 
삼삼오오 짝을지어

다시오지 않을 사람이야 
미련을 버리네.

신고
Write your message and submit


윈도우에서 File의 현재 포인터를 변경 하고자 할 때 사용하는 SetFilePointer()라는 함수가 있다.
재미있는 것은 새로 옮길 파일의 위치를 가리키는 변수로 LONG lDistanceToMove, PLONG lpDistanceToMoveHigh 두개를 사용하고 있다는 점이다. 
이유는 4Byte의 주소로는 4GByte이상의 큰 파일에서 한계가 있기 때문이다.

왜 POSIX [ off_t lseek(int fildes, off_t offset, int whence) ]와 같이 새로운 타입을 정의하여 long long형을 사용하지 않았을까? 하는 의문이든다.

어째뜬, SetFilePointer() 함수는 4Byte 형 두개의 변수를 8Byte 변수로 합쳐야 할 필요가 생긴다.
하지만, 이 역시 간단하지 않다.

오늘은 이 이야기를 하고자 한다.

#define INT_TO_LONGLONG(high, low)            ((((long long) (high)) << 32) | (low))

int 형 변수 두개를 long long형으로 만드는 매크로 함수 이다. 적당히 shift 하고 OR연산하고 있다.
얼핏 보면 문제가 없어 보이지만, 아래 예제를 실행해 보면 이상한 점을 발견할 수 있다.
#define ULONGLONG_TO_LOW_UINT(eight)		((unsigned int) ((eight) & 0x00000000FFFFFFFFULL))		
#define ULONGLONG_TO_HIGH_UINT(eight)		((unsigned int) (((eight) >> 32) & 0x00000000FFFFFFFFULL))
#define INT_TO_LONGLONG(high, low)			((((long long) (high)) << 32) | (unsigned int)(low))

int main()
{
	long long	input = 3221225472;
	int			low;
	int			high;
	long long	output;

	low = ULONGLONG_TO_LOW_UINT(input);
	high = ULONGLONG_TO_HIGH_UINT(input);

	output = INT_TO_LONGLONG(high, low);	

	printf("input = %lld, \t", input);
	printf("output = %lld", output);

	return 0;
}
이 프로그램의 결과는 다음과 같다.
input = 3221225472,     output = -1073741824

이유를 살펴보도록 하자.

3221225472는 16진수로 표현하면 0xC0000000 이다. 4byte로 표현이 되므로 high는 0, low는 0xC0000000이 들어가게 된다.
문제는 변수의 최상위 bit이 1이면 그 수는 음수로 인식이 된다는 것이다.

INT_TO_LONGLONG 매크로는 high 를 왼쪽으로 32bit만큼 shift한다면 low를 OR연산하고 있다.
high는 0이므로 shift해도 0이다. 하지만, low를 OR 할 때 컴파일러는 0xC0000000를 음수로 인식하여 계산결과는 0xC0000000이 되지 않고, 0xFFFFFFFFC0000000 이 되어 -1073741824 로 저장되는 것이다.

참 똑똑한(?) 컴파일러이다.
개발자는 어떤 임플리멘테이션으로 컴파일 될지 모르기 때문에 애매한 표현은 하지 말아야 한다.

따라서 INT_TO_LONGLONG 매크로는 다음과 같이 고쳐져야 한다.

#define INT_TO_LONGLONG(high, low)            ((((long long) (high)) << 32) | (unsigned int)(low))

unsigned 로 명시하여 애매모호함을 없앴다.
하지만,  high가 0이고 low 가 -1 일때는 어떻게 될까? 과연 output도 -1이 될 수 있을까?

 그렇지 않다.
-1은 16진수로 표현하면 0xFFFFFFFF이다. 위 INT_TO_LONGLONG을 거치면 0x00000000FFFFFFFF 가 되어 양수가 된다.

이사항 까지 고려 한다면 INT_TO_LONGLONG 매크로는 다음과 같이 되어야 한다.

#define INT_TO_LONGLONG(high, low)            ((high) ? ((((long long) (high)) << 32) | (unsigned int)(low)) : (low))
신고
  1. Favicon of http://blueasa.tistory.com BlogIcon blueasa

    | 2012.01.09 12:02 신고 | PERMALINK | EDIT | REPLY |

    좋은 정보 감사합니다. :)

  2. Favicon of http://rudalson.tistory.com BlogIcon 없다캐라

    | 2014.06.26 14:05 신고 | PERMALINK | EDIT | REPLY |

    재밌게 잘 봤습니다. 이부분 캐스팅때 생각보단 고려해야 될게 많네요...

    전체 예제 코드중에서 이부분
    #define INT_TO_LONGLONG(high, low) ((((long long) (high)) << 32) | (unsigned int)(low))

    (unsigned int) 부분이 빠져야 되는것 아닌가요?
    설명을 읽다 보니 저렇게 변경되었다는 것을 알겠네요.

Write your message and submit
C언어에 공용체 union이라는 것이 있는데 그렇게 많이 사용되지 않는다.
하지만, 왜 만들었는지 알 필요는 있다. 아래 코드를 보자
#include "STDIO.H"

typedef struct
{   
    void *pfun[8] ;    
} pData ;

typedef    union
{
    struct
    {
        void (*init)(struct _sData *pm, char *pstr) ;     ---(1) void (*init)() ; 으로 정의해도 error가 발생하지 않는다.
        void (*add)(struct _sData *pm, char *pstr) ;
        void (*out)(struct _sData *pm) ;
        void (*add_int)(struct _sData *pm, int num) ;
        int  (*number)(struct _sData *pm) ;
        void (*array)(struct _sData *pm) ;
        int  (*check)(struct _sData *pm, char ch) ;
        void (*sub)(struct _sData *pm, char ch) ;
    };
    pData fun_list ;   
} uFun;

typedef struct _sData
{
    int num ;
    char str[100] ;

    const uFun* const op;
} sData ;


void    init(sData *pm, char *pstr) ;
void    add(sData *pm, char *pstr) ;
void    out(sData *pm) ;
void    add_int(sData *pm, int num) ;
int        number(sData *pm) ;
void    array(sData *pm) ;
int        check(sData *pm, char ch) ;
void    sub(sData *pm, char ch) ;

void main()
{
    const uFun fun_list = { init, add, out, add_int, number, array, check, sub } ;
    sData data = {0, "\0", &fun_list};
    sData obj = {0, "\0", &fun_list};

    char str[100] = "CArA" ;
    int k ;

    printf("[ Data fun ]\n") ;
    data.op->init(&data, str) ;
    data.op->add(&data, "a7A") ;            data.op->out(&data) ;

    k = data.op->number(&data) ;            printf("k : [ %d ]\n", k) ;
    data.op->array(&data) ;                    data.op->out(&data) ;
    data.op->add_int(&data, 7045) ;            data.op->out(&data) ;

    k = data.op->check(&data, 'A') ;        printf("k : [ %d ]\n", k) ;
    data.op->sub(&data, 'A') ;                data.op->out(&data) ;


    printf("\n\n[ Obj fun ]\n") ;   
    obj.op->init(&obj, "Buzzan") ;            obj.op->out(&obj) ;
}

int check(sData *pm, char ch)
{
    int u, d ;
    for(u=0, d=0 ; pm->str[u] !='\0' ;u++)
        if(pm->str[u] == ch)
            d++ ;
    return d ;
}

void sub(sData *pm, char ch)
{
    int u, d ;
    for(u=0, d=0 ; pm->str[u]!='\0' ; u++, d++)
        ((pm->str[u]==ch)?(d--):(pm->str[d]=pm->str[u])) ;
    pm->str[d]='\0' ;
    pm->num=d ;
}

void add_int(sData *pm, int num)
{
    int n = num ;
    while(num>10) {
        int u ;
        for(u=1 ; n>10 ; n=n/10, u = u*10) ;       
        pm->str[pm->num++] = n + '0' ;       
        num = num - n*u ;
        if(!((u/10 < num) && (num < u)))
            pm->str[pm->num++] = '0' ;       
        n = num ;       
    }   
    pm->str[pm->num++] = num + '0';   
    pm->str[pm->num] = '\0' ;
}

int number(sData *pm)
{
    int u, d ;
    for(u=0, d=0 ; unum ; u++)
        if(('0'<=pm->str[u])&&(pm->str[u]<='9'))
            d = d * 10 + (pm->str[u]-'0') ;
    return d ;
}

void array(sData *pm)
{
    char temp ;
    int u ;
    for(u=0 ; unum-1 ; u++) {
        int d ;
        for(d=1 ; dnum-u ; d++)
            if(pm->str[d-1] > pm->str[d]) {
                temp = pm->str[d-1] ;
                pm->str[d-1] = pm->str[d] ;
                pm->str[d] = temp ;
        }
    }
}

void init(sData *pm, char *pstr)
{
    int u ;
    for(u=0 ; pstr[u]!='\0' ; pm->str[u]=pstr[u++]) ;
    pm->str[u]='\0' ;
    pm->num=u ;
}

void add(sData *pm, char *pstr)
{
    int u, n ;
    for(u=0, n=pm->num ; pstr[u]!='\0' ; u++, n++)
        pm->str[n]=pstr[u] ;
    pm->str[n]='\0' ;
    pm->num=n ;
}

void out(sData *pm)
{
    printf("[ Num ] : %d\t[ Str ] : %s\n", pm->num, pm->str) ;
}


프로그램의 결과 보다는 구현방법만 보도록 하자,

union을 사용하지 않는다면 sData의 맴버함수인 uFun의 함수포인터들을 초기화하는 것이 매끄럽지 못하다.
위와 같은 구조는 리눅스 virtual file system 에서 자주 발견된다.

하지만, 이방법을 사용할 때 주의 해야 할 점이 있는데
(1)과 같이 함수의 호출하는 곳과 호출 받는 곳의 prototype이 다를 경우 error나 warning이 발생하지 않는다.
즉, 맴버함수를 정의해 놓은 곳과 구현하는 곳의 prototype이 항상 같은지 눈으로 확인을 해주어야 한다.
만약 매개변수 하나를 더 보내거나 덜 보내 문제가 생길 경우 디버깅하기 까다로운 경우가 발생하기 쉽다.
신고
Write your message and submit
[NSD][CRT][Tim:0][Err:00000][FileSetAttributes(#1286)@ns_file.c]: Native set attr (0) (0) fails. ERR[0]


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

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

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

 문제의 코드를 보면
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은 출력 구분자(%)가 없어 출력되지 못했습니다.

위 코드를 아래와 같이 고쳐 해결 하였습니다.
NSD_EMZ((_T("Native set attr (%llu) (%d) fails. ERR[%d]"), NsVnodeGetIndex(pVnode), dwAttribute, err));

printf의 출력 구분자와 변수의 type이 맞지 않아도 컴파일러는 경고나, 에러를 발생하지 않습니다.
 
ps 오늘의 교훈 : 에러 메시지 출력을 잘못해도 SQA를 통과 못할 수도 있습니다.


신고
Write your message and submit