재미있는 C언어6. int to long long type casting 기록
2010.05.28 20:20 Edit

윈도우에서 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))
