#keywords 32,64,bits,호환,소스,고려,정리,포팅 #title 32bit 에서 64bit로 소스호환을 위한 고려사항 정리 [wiki:Home 대문] / [wiki:CategoryProgramming 프로그래밍] / [wiki:32bitCodeTo64bit 32bit 에서 64bit로 소스호환을 위한 고려사항 정리] ---- == [wiki:32bitCodeTo64bit 32bit 에서 64bit로 소스호환을 위한 고려사항 정리] == * 작성자 조재혁 ([mailto:minzkn@minzkn.com]) * 고친과정 2007년 6월 17일 : 처음씀 [[TableOfContents]] === 개요 === 이 내용은 제가 기존에 만들었던 여러 소스(mzapi) 들을 64bit 에서 동작하도록 포팅하면서 발생했던 고려사항들을 정리합니다. === 변수형의 크기는 어떻게 달라지는가? 32bit 환경에서 64bit 환경으로 옮겨가면서 달라진 변수형은 다음과 같습니다. === 1. int 형은 32bit 를 유지합니다. 1. short 형은 16bit 를 유지합니다. 1. long 형은 32bit에서 64bit로 확장됩니다. 1. long long 형은 64bit 를 유지합니다. 1. pointer 형은 32bit 에서 64bit로 확장딥니다. 1. long double 형이 12bytes 에서 16bytes로 확장됩니다. 1. size_t과 ssize_t 형이 32bit 에서 64bit 로 확장됩니다. 1. 이와 관련된 typedef 문 역시 확장됩니다. === 64bit linux platforms === || Architecture || uname -m || Size || Endian || Libpath || Miscellaneous || ||Alpha || alpha || LP64 || little || lib || || ||AMD64 || x86_64 || LP64 || little || lib64 ||executes x86 code natively || ||IPF || ia64 || LP64 || little || lib ||executes x86 code via emulation || ||MIPS64 || mips64 || LP64 || both || lib64 ||executes MIPS code natively || ||PowerPC64 || ppc64 || LP64 || big || lib64 ||executes PowerPC code natively || ||Sparc64 || sparc64 || LP64 || big || lib64 ||executes Sparc code natively || ||PA-RISC64 || parisc64 || LP64 || big || - ||executes 32-bit PA-RISC code (only kernel support, no 64-bit) || ||zSeries (s390x) || s390x || LP64 || big || lib64 ||executes s390 code natively || === 문제가 되는 코드들 === ==== 크기가 다른 변수형에 대한 포인터를 사용한 경우 (스택붕괴가 우려되는 경우) ==== getsockopt의 size인자가 socklen_t 로 정의되는 경우 아래의 코드는 s_socklen 변수를 int로 정의한것에서 문제가 발생할수 있습니다. {{{#!enscript c int s_socklen; s_result = getsockopt(s_socket, s_level, s_optname, &s_optval, &s_socklen); }}} 이것은 다음과 같이 변경되어야 합니다. 이것은 int와 socklen_t 는 크기가 다르기 때문입니다. {{{#!enscript c socklen_t s_socklen; s_result = getsockopt(s_socket, s_level, s_optname, &s_optval, &s_socklen); }}} 조건식을 int 로 받는 함수에 포인터를 넘겨줄경우 (컴파일러 경고) 일반적으로 다음과 같은 함수가 있을때 {{{#!enscript c void my_assert(int s_expression) { if(!s_expression)return; (void)fprintf(stderr, "assert....\n"); } }}} 위와 같은 함수에 조건문을 사용할때 {{{#!enscript c void *s_ptr = NULL; my_assert(s_ptr); }}} 다음과 같이 변경하여야 합니다. 이것은 포인터가 int 형과 다른 크기이므로 포인터는 int로 casting 되면서 잘리게 되고 NULL포인터가 아님에도 하위 32bit가 0인경우 my_assert 는 의도하지 않은 동작을 할수 있기 때문입니다. {{{#!enscript c void *s_ptr = NULL; my_assert(s_ptr != NULL); }}} ==== dword 정의 (만약 이렇게 써왔다면..) ==== 다음과 같이 my_DWORD를 정의하여 사용했다면 {{{#!enscript c #define my_DWORD unsigned long int }}} 다음과 같이 변경하여야 합니다. {{{#!enscript c #define my_DWORD unsigned int }}} ==== x86계열 어셈블리 호환성 (안바꿔도 동작한다.) ==== 예를 들어서 atomic exchange 를 다음과 같이 C함수로 구현했다면 전혀 수정할 필요없이 그대로 컴파일 가능합니다. 하지만 int형이 아닌 long 형을 사용했다면 register 는 eax, ebx, .. 가 아닌 rax, rbx, ... 로 변경을 고려해야 합니다. {{{#!enscript c int my_atomic_exchange(int * volatile s_to, int s_value) { __asm volatile ("xchgl (%2), %0\n\t" : "=r"(s_value) : "0"(s_value), "r"(s_to) : "memory"); return(s_value); } }}} ==== implementation 함수의 잘못된 고려 (size_t 등의 의미를 전혀 활용하지 못한경우 포팅이 쉽지 않을수 있음.) ==== 예를 들어서 memcpy 를 한번 감싸서 다음과 같이 자신만의 함수를 만들었다면 {{{#!enscript c void *my_memcpy(void *s_to, const void *s_from, int s_size) { return(memcpy(s_to, s_from, s_size)); } }}} 당연히 s_size 변수는 64bit를 담을수 없는 그릇으로 전달되기 때문에 원하지 않은 결과를 발생할수 있습니다. 때문에 다음과 같이 변경되어야 합니다. {{{#!enscript c void *my_memcpy(void *s_to, const void *s_from, size_t s_size) { return(memcpy(s_to, s_from, s_size)); } }}} size_t, ssize_t 의 용도를 전혀 중요하게 생각지 않은 개발자라면 이 문제로 64bit 포팅에 좌절을 겪을수도 있습니다. 고려하지 않은 개발자분들은 위의 예제처럼 memcpy 에서보다는 memory offset 연산에서 후회할수 있습니다. ==== 정렬 ==== 기존에는 구조체의 sizeof() 에 의한 정렬된 값이 보통 4byte align로 사용되었으나 이것은 이제 기본값으로 믿을수 없습니다. 이제는 보통 8byte align 이 기본값으로 사용됩니다. 때문에 반드시 의도적으로 align을 해야 되는 경우라면 다음과 같이 작성되어야 합니다. (이것은 뻔한 내용이지만 그것을 이야기 하려고 한것이 아니고 신중한 library 개발자라면 모든 구조체는 명시적으로 align 을 지정해줘야 될수 있다는 것을 말합니다.) {{{#!enscript c #pragma pack(push,8) struct ts_my_struct { unsigned char b; unsigned short w; unsigned int d; unsigned long d; unsigned long long q; long double paragraph; } #pragma pack(pop) }}} ==== predefine 으로 64bit 를 검출 ==== 각 architecture 별 predefine의 종류가 많아서 간단하게 검출할수 있는 내용은 아니지만 제가 지금까지 포팅한 architecture는 다음과 같이 하여 검출하였습니다. 사실 이것이 모두 완벽하게 검출한다고 할수는 없습니다. 들어보지도 못한 architecture도 많으니까.. {{{#!enscript c #if defined(__x86_64__) || defined(__ia64__) || defined(_M_AMD64) || defined(_M_IA64) || defined(_WIN64) || defined(__alpha__) || defined(__s390__) /* ... */ /* 오~ 나 64bit 에서 컴파일 되네.. 어허 요렇게 처리하자~ */ #endif }}} === 캐쉬(cache) 조정 === gcc 사용자라면 64bit 에서 보다 높은 성능을 위하여 __builtin_expect((long)(m_expression),(long)(m_value)) 내장함수도 한번 검토해보시면 좋을것 같습니다. 이것은 조건식이 거의 실행되지 않을 확률 또는 실행될 확률을 조정함으로써 컴파일러로 하여금 Cache 의 최적사용을 위한 최적화가 가능해지도록 유도될수 있습니다. 64bit 기계어 코드를 보시면 이러한 Cache 를 효과적으로 사용할수 있도록 하는 방법이 제공되기 때문에 잘만 사용하면 성능이 극대화 될것으로 생각됩니다.