32bit 에서 64bit로 소스호환을 위한 고려사항 정리
변수형의 크기는 어떻게 달라지는가? 32bit 환경에서 64bit 환경으로 옮겨가면서 달라진 변수형은 다음과 같습니다. ¶
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 | - | only kernel support, no 64-bit
| | | | | executes 32-bit PA-RISC code
zSeries (s390x) | s390x | LP64 | big | lib64 | executes s390 code natively
크기가 다른 변수형에 대한 포인터를 사용한 경우 (스택붕괴가 우려되는 경우) ¶getsockopt의 size인자가 socklen_t 로 정의되는 경우 아래의 코드는 s_socklen 변수를 int로 정의한것에서 문제가 발생할수 있습니다.
int s_socklen; s_result = getsockopt(s_socket, s_level, s_optname, &s_optval, &s_socklen); 이것은 다음과 같이 변경되어야 합니다. 이것은 int와 socklen_t 는 크기가 다르기 때문입니다. socklen_t s_socklen; s_result = getsockopt(s_socket, s_level, s_optname, &s_optval, &s_socklen); 조건식을 int 로 받는 함수에 포인터를 넘겨줄경우 (컴파일러 경고) 일반적으로 다음과 같은 함수가 있을때
void my_assert(int s_expression)
{
if(!s_expression)return;
(void)fprintf(stderr, "assert....\n");
}
위와 같은 함수에 조건문을 사용할때 void *s_ptr = NULL; my_assert(s_ptr); 다음과 같이 변경하여야 합니다. 이것은 포인터가 int 형과 다른 크기이므로 포인터는 int로 casting 되면서 잘리게 되고 NULL포인터가 아님에도 하위 32bit가 0인경우 my_assert 는 의도하지 않은 동작을 할수 있기 때문입니다. void *s_ptr = NULL; my_assert(s_ptr != NULL); dword 정의 (만약 이렇게 써왔다면..) ¶다음과 같이 my_DWORD를 정의하여 사용했다면
#define my_DWORD unsigned long int 다음과 같이 변경하여야 합니다. #define my_DWORD unsigned int x86계열 어셈블리 호환성 (안바꿔도 동작한다.) ¶예를 들어서 atomic exchange 를 다음과 같이 C함수로 구현했다면 전혀 수정할 필요없이 그대로 컴파일 가능합니다. 하지만 int형이 아닌 long 형을 사용했다면 register 는 eax, ebx, .. 가 아닌 rax, rbx, ... 로 변경을 고려해야 합니다.
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 를 한번 감싸서 다음과 같이 자신만의 함수를 만들었다면
void *my_memcpy(void *s_to, const void *s_from, int s_size)
{
return(memcpy(s_to, s_from, s_size));
}
당연히 s_size 변수는 64bit를 담을수 없는 그릇으로 전달되기 때문에 원하지 않은 결과를 발생할수 있습니다. 때문에 다음과 같이 변경되어야 합니다.
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 을 지정해줘야 될수 있다는 것을 말합니다.)
#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도 많으니까..
#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 를 효과적으로 사용할수 있도록 하는 방법이 제공되기 때문에 잘만 사용하면 성능이 극대화 될것으로 생각됩니다.
|
Mind your own business, Spock. I'm sick of your halfbreed interference. |











