C语言中指针/字符转换为整数的正确方法

很多人认为32位程序用户空间只有2GB,其实是不对的,比如Linux下就可能有3GB,64位系统下甚至可能有4GB。

下面的写法限制在2GB:

// intptr_t iaddr = (intptr_t)ptr;
// ptrdiff_t iaddr = (ptrdiff_t)ptr;

系统可能认为指针值是一个有符号数,并进行符号扩展。下面的写法仍然限制在2GB:

// int64_t iaddr = (int64_t)ptr;
// long long iaddr = (long long)ptr;

有符号数转换为无符号数会按2的n次方的补码转换,比如-1转换到uint64_t会转换为2的64次方减1。地址跨越2GB时这样的转换会导致无符号数产生一个非常大的数值跳跃,可能会导致未知的bug。下面的写法实际上也是不对的:

// uint64_t uiaddr = (uint64_t)ptr;
// unsigned long long uiaddr = (unsigned long long)ptr;

将地址转换为整数时,应先转换为与指针具有相同宽度的无符号整数类型,然后再转换为更宽的整数类型,比如int64_t或long long、uint64_t或unsigned long long,才会进行零扩展。C语言中与指针具有相同宽度的无符号整数类型是uintptr_tsize_t。下面的写法是正确的:

uintptr_t uiaddr = (uintptr_t)ptr;
uint64_t uiaddr = (uintptr_t)ptr;
int64_t iaddr = (int64_t)(uintptr_t)ptr;

// c89写法
size_t uiaddr = (size_t)uiaddr;
unsigned long long uiaddr = (size_t)ptr;
long long iaddr = (long long)(size_t)ptr;

由于64位程序的地址(包括用户态地址、内核态地址)一般被限制在靠近0的范围内,不会导致有符号数溢出,所以使用int64_t或long long等有符号数也是合适的。

char由于历史原因,在x86架构上默认是有符号数,在ARM架构上默认是无符号数,编译器通常有开关可以改这个特性,但出于通用性考虑,不应该依赖这个特性,特别是进行非ASCII字符运算时。在x86架构上,char参与整数运算时,默认会进行signed char到int的整数提升,也就是进行符号扩展,这对于ASCII字符是合适的,但对于非ASCII字符会产生非预期的结果。对于非ASCII字符编码来说,无符号数是有现实意义的,而有符号数则没有。

当进行非ASCII字符运算时,字符值char应该先转换为无符号数unsigned charuint8_t,再进行整数运算,或赋值给int或更宽的整数。字符串指针char *应该转换为unsigned char *uint8_t *

// 下面写法对于非ASCII字符是错误的
// int charval = c; // 错误!有可能会产生符号扩展
// unsigned int charuval = c; // 错误!有可能会产生补码转换
// int charval = str[0]; // 错误!有可能会产生符号扩展

// 下面写法是正确的
int charval = (uint8_t)c;
unsigned int charuval = (uint8_t)c;
uint8_t *ucstr = (uint8_t *)str;
int charval = ucstr[0];

// c89写法
int charval = (unsigned char)c;
unsigned int charuval = (unsigned char)c;
unsigned char *ucstr = (unsigned char *)str;
int charval = ucstr[0];
发布了29 篇原创文章 · 获赞 1 · 访问量 3416

猜你喜欢

转载自blog.csdn.net/defrag257/article/details/102655132