메뉴 건너뛰기


Developer > Application
이번에 작성하려고 하는 글은 C언어 개발자들 중에 멀티 플랫폼에 포팅을 해야 하는 경우나 유닉스와 윈도우즈를 모두 사용하는 개발자들에게 필요한 각 플랫폼별로 차이점 들에 대해서 이야기 하도록 하겠습니다.
 
각 플랫폼별로 같은 기능의 함수가 있는데 원형이나 함수의 쓰임이 다르다거나 유사 기능은 있지만 같은 기능의 함수가 없다거나... 등등의 문제를 해결하기 위해 팁이라면 팁을 만들어 보도록 하겠습니다.
 
먼저 말씀드리는 것은 이미 만들어져 있는 것을 여러분들에게 보여드리는게 아니라 제가 직접 테스트 하면서 알려드리도록 할테니 보시다가 잘못된 거나 더 쉬운 방법이 있는 경우 알려 주시기 바랍니다.
 
윈도우즈는 MS Visual C 6.0과 8.0을 기준으로 이야기 하도록 하구요.
다른 유닉스나 리눅스는 모두 gcc를 기준으로 이야기 하도록 하겠습니다.
 
1장. 멀티 플랫폼 C개발자 (64 비트 정수 처리)
 
멀티 플랫폼 C개발자가 알아야 할 내용 중 첫번째로 64비트 정수 처리에 대해서 알아보도록 하겠습니다.
본 설명에 들어가기 전에 앞으로 설명에 편의를 위해 몇가지 표기방법을 좀 정하겠습니다. 각 CPU별 OS에 대해서 다음과 같이 표기하도록 하지요.
CPU종류OS 종류표기방법
Intel x86, x64 계열WindowsWIN
LinuxLINUX
SolarisSOL(86)
SUN SparcSolarisSOL
HP RISC PAHP-UXHPUX
IBM PPCAIXAIX
Intel Itanium 2WindowsWIN(IA)
LinuxLINUX(IA)
HP-UXHPUX(IA)
 
또한 표기방법 뒤에 32비트 환경인지 64비트 환경인지에 따라 32혹은 64의 숫자를 붙이도록 하겠습니다.
 
한가지 더 이야기 드리고 싶은 것은 32비트라는 것은 CPU가 32비트 CPU라는 말이 아니라 컴파일러가 32라는 의미입니다. 64비트도 마찬가지의 의미이구요.
 
1. ANSI C 데이터 타입과 데이터의 크기
 
우선 대부분 비슷하지만 다른 부분이 몇개 있는 각 플랫폼별 ANSI C 데이터 형의 그 크기를 비교해 보겠습니다. 모두 sizeof()에 의해 출력된 값들입니다.
  
WIN32LINUX32SOLARIS32HPUX32AIX32HPUX(IA)32LINUX64WIN64
char

1

1

1

1

1

1

1

1
unsigned char

1

1

1

1

1

1

1

1
signed char

1

1

1

1

1

1

1

1
short

2

2

2

2

2

2

2

2
unsigned short

2

2

2

2

2

2

2

2
int

4

4

4

4

4

4

4

4
long

4

4

4

4

4

4

8

4
unsigned long

4

4

4

4

4

4

8

4
float

4

4

4

4

4

4

4

4
double

8

8

8

8

8

8

8

8
long double

8

12

16

16

8

16

16

8
char *

4

4

4

4

4

4

8

8
 
참고로 포인터 형은 char *만 했는데 뭐 포인터 형의 크기는 뭐로 하든 같다는 거 아시죠?
 
 
2. 플랫폼별 64 정수형 데이터 타입
 
위에서 보았듯이 32비트 환경에서 기본 ANSI 데이터형으로 정수형을 표기 하기 위해 unsigned long형을 사용하였을 경우 최대 2의 32승(4,294,967,296)의 값 까지를 사용할 수 있습니다.
제가 보기에는 이값도 충분히 크긴합니다. 하지만 1970년 1월 1일 0시 0분 0초 부터 지나온 초의 값을 갖는 time_t형과 같은 경우는 long형을 사용하고 있는데 이 값으로는 곧 있으면 부족하게 됩니다.
2007년 7월 25일 21시 50분 03초의 time_t값은 1,185,367,803이군요. 최대값을 입력해 보니 2038년 1월 12시 14분 07초까지 사용할 수 있군요. ㅎㅎ 또 이때 되면 2000년 될 때 처럼 난리 피우는 거 아닌가 모르겠네요.
어찌 되었건 32비트 환경에서 2의 32승 값보다 더 큰값을 표시하여야 하는 경우가 생기는데 그래서 생겨난 것인 long long 혹은 __int64 타입의 데이터형 입니다. 참고 64비트 정수는 2의 64승(18,446,744,073,709,551,616)의 값 까지 사용할 수 있습니다. 물론 부호형 정수의 경우는 -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 의 값을 사용할 수 있겠죠?
 
64비트 환경에서만 동작하는 어플리케이션을 짠다면 굳이 이런 데이터타입을 사용할 필요는 없겠지만... 호환을 위한다면 오히려 이러한 값을 사용하여야 합니다.
그런데 이런 데이터타입을  사용하기가 좀 까다롭습니다. 이번 절에서는 이 들에 대해서 알아보도록 하죠.
 
윈도우즈에서는 정확히는 VC++에서는 이러한 데이터 타입이 __int64와 unsigned __int64형이 있습니다.
그외 리눅스/유닉스에서는 정확히는 gcc에서는 이러한 데이터 타입으로 long long과 unsigned long long형이 있습니다.
 
또한 모두 크기는 8바이트로 되어 있습니다.
그럼 각각의 사용법에 대해서 알아보도록 하겠습니다.
 
1) VC++에서의 64비트 정수 사용
 
우선 64비트 정수를 표현하는 방법입니다.
사실 VC++에서는 그냥 64비트 정수가 표현할 수 있는 만큼 숫자를 적어도 됩니다.
명확성을 부여하고자 하거나 숫자 자체로 8바이트로 잡게 하고 싶다면 숫자 뒤에 I64나 UI64를 붙여주면 됩니다. 마치 long형의 숫자를 적을 때 숫자 뒤에 L을 붙이는 것과 같은 방식입니다.
 
다음은 64비트 정수를 printf()함수로 출력하는 방법입니다.
 
printf()함수에 %d를 사용하면 정수를 출력한다는 사실은 다 아시겠죠?
64비트 정수를 출력하기 위해서는 %I64d 라고 하면 되고 unsigned 형을 출력하려면 $I64u 하면됩니다.
 
자 실제 코드를 한번 보도록 하겠습니다.
 
__int64 pbignum=0x7FFFFFFFFFFFFFFFi64; __int64 mbignum=0x8000000000000000i64; unsigned __int64 ubignum=0xFFFFFFFFFFFFFFFFui64; printf("%I64d\n", pbignum); printf("%I64d\n", mbignum); printf("%I64u\n", ubignum);
 
위의 코드의 실행결과는 다음과 같습니다.
 
9223372036854775807 -9223372036854775808 18446744073709551615
 
뭐 예상하셨겠지만 처리가능한 최대 값이 나오는 것이겠죠?
재미삼아 질문입니다. 제일 마지막에 나온 값을 뭐라고 읽을까요?  ㅋㅋㅋ
 
천팔백사십사경육천칠백사십사조칠백삼십칠억구백오십오만천육백십오
 
와우~ 대단히 큰값이네요. 그쵸? ^^;
 
 
 
 
2) GNU C 에서의 64비트 정수 사용
 
우선 64비트 정수를 표현하는 방법입니다.
GNU C즉, gcc에서 64비트의 정수를 사용할때는 숫자 뒤에 LL 혹은 ULL을 붙이면 됩니다.
 
다음은 gcc에서 64비트 정수를 printf()함수로 출력하기 위해서는 는 방법입니다.
gcc의 printf()함수에서는 %lld와 %llu를 사용하여 64비트 정수를 출력할 수 있습니다. 
 
이미 이해가 되셨을 테니 그냥 예제를 보도록 하겠습니다.
 
 
long long pbignum=0x7FFFFFFFFFFFFFFFLL; long long mbignum=0x8000000000000000LL; unsigned long long ubignum=0xFFFFFFFFFFFFFFFFULL; printf("%lld\n", pbignum); printf("%lld\n", mbignum); printf("%llu\n", ubignum);
 
위의 코드의 실행결과는 다음과 같습니다.
 
9223372036854775807 -9223372036854775808 18446744073709551615
 
결과는 VC++의 경우와 같습니다.
 
 
3) 컴파일러가 64비트 정수를 지원하지 않는 경우
 
사실 컴파일러가 64비트 정수를 지원하지 않는다면 차라리 그 컴파일러 사용하지 말고 gcc깔아서 사용하세요라고 말씀드리고 싶네요. 왜냐면 64비트 정수를 지원하지 않는데 굳이 사용하려고 한다면 +, -, *, /와 같은 연산자와 printf()함수나 기타 등등의 함수 여기에 사용할 수 없기 때문입니다.
그럼에도 불구하고 호환을 위해서 그 데이터의 크기를 지원해야 하는 경우 다음과 같이 구조체를 정의하여 사용하기도 합니다.
 
 typedef struct {
   unsigned long lo, hi;
 } longlong; 
 
 typedef struct {
   unsigned long hi, lo;
 } longlong; 
 
 
 
3.  64비트 정수 정리 
 
자 여기까지 알아 봤으니깐... C에서 멀티 플랫폼에서 64비트 정수를 사용하기 쉽도록 정리를 해 보도록 하겠습니다.
오픈소스들을 관심있게 봐본 분들이 계신다면 아래에 공개하는 헤더 파일의 내용과 유사한 코드를 한번쯤은 보셨을 거라 생각합니다. 예전에는 무슨 의미인지 몰랐을지 모르지만 이제 위의 내용을 읽어본 후 코드를 보니깐 무슨 내용인지 알겠죠? ^^ 흐믓.
 
1절에서 이야기한 내용을 토대로 우선 OS에 의존성을 갖는 header를 정의합니다.
 
#if defined(WIN32) || defined(WIN64) || defined(__GNUC__) #define HAVE_LONG_LONG 1 #endif #if defined(WIN32) || define(WIN64) || define(LINUX32) #define IS_LITTLE_ENDIAN 1 #else #define IS_BIG_ENDIAN 1 #endif #if defined(WIN32) || define(WINIA32) || define(LINUX32) || define(LINUXIA32) || define(SOLARIS32) || define(HPUX32) || define(HPUXIA32) || define(AIX32) #define NIC_BYTES_PER_BYTE 1L #define NIC_BYTES_PER_SHORT 2L #define NIC_BYTES_PER_INT 4L #define NIC_BYTES_PER_INT64 8L #define NIC_BYTES_PER_LONG 4L #define NIC_BYTES_PER_FLOAT 4L #define NIC_BYTES_PER_DOUBLE 8L #define NIC_BYTES_PER_WORD 4L #define NIC_BYTES_PER_DWORD 8L #else // 32비트가 아니면 모두 64비트라고 간주한다. #define NIC_BYTES_PER_BYTE 1L #define NIC_BYTES_PER_SHORT 2L #define NIC_BYTES_PER_INT 4L #define NIC_BYTES_PER_INT64 8L #define NIC_BYTES_PER_LONG 8L #define NIC_BYTES_PER_FLOAT 4L #define NIC_BYTES_PER_DOUBLE 8L #define NIC_BYTES_PER_WORD 4L #define NIC_BYTES_PER_DWORD 8L #endif
 
위의 헤더의 이름을 nickosdep.h이라고 저장합니다.
위의 헤더는 OS와 컴파일러에 의존성을 갖는 메크로를 정의합니다.
WIN32나 WIN64는 VC++을 사용하면 기본으로 정의되는 메크로이므로 VC++을 사용하고 있다는 것을 의미하게 됩니다. gcc를 사용하게 되면 __GNU_C__ 가 기본으로 정의되게 됩니다.
즉, 제일 처음 VC나 gcc를 컴파일러로 사용하고 있다면 HAVE_LONG_LONG이라는 메크로를 1로 정의하게 됩니다.
그다음은 각 OS별로 Big Endian과 Little Endian을 구별하기 위해 IS_LITTLE_ENDIAN과 IS_BIG_ENDIAN 메크로를 OS에 따라 1로 정의하고 있습니다.
그리고 OS별 그리고 컴파일러의 비트별로 각 데이터 타입의 바이트 수를 정의하는 메크로를 정의하고 있습니다. 1절에서 본것 처럼 사실은 long형, long double형과 포인터 형을 빼고는 32비트나 64비트 컴파일러나 바이트 수가 같으므로 long double형은 거의 사용하지 않으므로 long형에 대해서만 구별해 주면 되겠습니다. 물론 위에서 이야기한 플랫폼에서 사용할 경우에 한해서 하는 이야기 겠지요.  
그리고 실제 컴파일시에 컴파일 옵션의로 각 플랫폼 이름과 컴파일러의 버전이 붙은 메크로를 정의해 줘야 겠죠? 예로 아이테니움 HPUX에서 32비트 gcc컴파일러로 컴파일하는 경우라면 다음과 같이 해야 할 것입니다.
 
gcc -o prog -DHPUXIA32 prog.c

위의 nickosdep.h파일을 이용해서 실제로 64비트 정수형을 쉽게 사용하기 위한 헤더를 또 작성해 보도록 하지요. 다음의 파일은 nickctype.h 라고 이름 짖겠습니다.  
 
#include "nickosdep.h" #if NIC_BYTES_PER_INT == 4 typedef unsigned int nickuint32; typedef int nickint32; #define NICK_INT32(x) x #define NICK_UINT32(x) x ## U #elif NIC_BYTES_PER_LonG == 4 typedef unsigned long nickuint32; typedef long nickint32; #define NICK_INT32(x) x ## L #define NICK_UINT32(x) x ## UL #else #error No suitable type for nickuint32/nickint32 #endif #if NIC_BYTES_PER_SHORT == 2 typedef short nickint16; typedef unsigned short nickint16; #else #error No suitable type for nickuint16/nickint16 #endif #if NIC_BYTES_PER_BYTE == 1 typedef unsigned char nickuint8; typedef char nickint8; #else #error No suitable type for nickuint8/nickint8 #endif #ifdef HAVE_LONG_LONG #if NIC_BYTES_PER_LonG == 8 typedef long nickint64; typedef unsigned long nickuint64; #elif defined(WIN16) typedef __int64 nicknt64; typedef unsigned __int64 nickuint64; #elif defined(WIN32) && !defined(__GNUC__) typedef __int64 nickint64; typedef unsigned __int64 nickuint64; #define NICK_INT64(x) x #define NICK_UINT64(x) x #else typedef long long nickint64; typedef unsigned long long nickuint64; #define NICK_INT64(x) x ## LL #define NICK_UINT64(x) x ## ULL #endif #else typedef struct { #ifdef IS_LITTLE_ENDIAN nickuint32 lo, hi; #else nickuint32 hi, lo; #endif } nickint64; typedef nickint64 nickuint64; #endif
 
위 헤더 파일은 다음과 같은 내용을 정의하고 있습니다.
 
정수형의 데이터 타입을 다시 정의하는데 nickuint와 nickint로 부호없는 경우와 부호가 있는 경우의 데이터 타입 이름을 사용하고 그 뒤에 몇비트 데이터 타입인지를 표시하게 됩니다.
즉, nickuint16 이면 부호없는 정수형 16비트 즉, 2바이트 데이터 타입이라는 것을 의미하는 이름이 됩니다. 실제로는 위의 헤더 파일을 분석해 보면 아시겠지만 unsigned short형으로 정의되고 있습니다.
뭐 이런식이죠.
 
단, long형과 64비트 정수형의 숫자를 만들어 낼때는 약간 주의가 필요합니다.

nickint32 num1;
nickuint64 num2;
 
위와 같이 num1과 num2를 정의했다면
다음과 같이 값을 입력하여야 합니다.
 
num1 = NICK_INT32(123456);
num2 = NICK_UINT64(123456);
 
이렇게 하는것이 더 번거롭게 보이겠지만...
멀티 플랫폼에서 조금이라도 쉽게 포팅을 하기 위한 방법입니다.
 
위에서 제가 모든 OS나 모든 컴파일러로 테스트 한 결과가 아니므로 필요한 경우 적절히 고쳐서 사용해야 합니다. 하지만 이제 위의 코드를 이해 하셨다면 쉽게 고치실 수 있을 것입니다.
 
64비트 정수 처리에 대한 이야기는 여기까지입니다.
 
Creative Commons License
Creative Commons License이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://bbs.nicklib.com