1 前言
当项目中需要将原来32位架构处理器的代码移植到ARM64架构处理器上时,在编译过程中会遇到许多的warning,这些警告大多与指针与整型变量的类型转换和printf函数参数的数据类型不相符有关。而intptr_t
类型和PRIu64
这些宏定义提高了代码在不同架构平台的可移植性。
2 intptr_t类型
intptr_t
和uintptr_t
类型用来存放指针地址。它们提供了一种可移植且安全的方法声明指针,而且和系统中使用的指针长度相同,对于把指针转化成整数形式来说很有用,这两个数据类型是ISO C99定义的,具体代码在linux平台的/usr/include/stdint.h头文件中
/* Types for `void *' pointers. */
#if __WORDSIZE == 64
#ifndef __intptr_t_defined
typedef long int intptr_t;
#define __intptr_t_defined
#endif
typedef unsigned long int uintptr_t;
#else
#ifndef __intptr_t_defined
typedef int intptr_t;
#define __intptr_t_defined
#endif
typedef unsigned int uintptr_t;
#endif
这样定义的原因是因为不同的数据类型在不同字长机器上长度大小是不同的:
machine | char | short | int | long | ptr |
---|---|---|---|---|---|
16 | 1byte | 2byte | 2byte | 4byte | 2byte |
32 | 1byte | 2byte | 4byte | 4byte | 4byte |
64 | 1byte | 2byte | 4byte | 8byte | 8byte |
指针在32位平台和64位平台下均与long
类型的长度一致,然而在16位机器上,long
为4个字节,而指针为2个字节。
因此,就可以发现intptr_t
和uintptr_t
定义的巧妙之处:
在64位机器上,intptr_t
为long int
,uintptr_t
为unsigned long int
。而在非64位机器上,intptr_t
为int
,uintptr_t
为unsigned int
。
这样就可以保证intptr_t
和uintptr_t
的长度与机器的指针长度一致,因此在进行整数与指针的相互转换时可以用intptr_t保证可移植性。
以下面这段代码为例:
#include <stdio.h>
#include <stdint.h>
int main()
{
int a = 12345;
int *p = &a;
int ptr = (int )p;
printf("%d\n",ptr);
printf("sizeof(ptr):%ld,sizeof(p):%ld\n",sizeof(ptr),sizeof(p));
return 0;
}
在编译时,GCC会给出warning,Wpointer-to-int-cast
表明将指针转换为整型,但是二者大小不同
test.c: In function ‘main’:
test.c:13:12: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
13 | int ptr = (int)p;
| ^
如果使用uintptr_t
改写代码则不会警告:
#include <stdio.h>
#include <stdint.h>
int main()
{
uintptr_t a = 12345;
uintptr_t *p = (uintptr_t*)a;
printf("%p\n",p)
printf("sizeof(a):%ld,sizeof(p):%ld\n",sizeof(a),sizeof(p));
return 0;
}
3 PRIu64
如下这段代码,在32位处理器和64位处理器下编译的警告会不同:
uint64_t a = 0x1234;
uint8_t str[20] = {0};
sprintf(str, "%llu", a);
使用 64 位编译器,编译报警告:uint64_t
是 long unsigned int
类型,请使用 "%ld"
。
如果改成 “%ld”
,使用 32 位机器编译时,又报警告:uint64_t
是long long unsigned int
类型,请使用 "%lld"
。
这是因为在 32 位平台上,std::uint64_t
将被定义为 unsigned long long并且格式说明符将为%llu
。而在 64 位平台上,std::uint64_t
将被定义为 unsigned long
并且格式说明符将为 %lu 。
由此可见对于uint64_t
,int64_t
这类数据类型,在sprintf
,printf
这类函数中的可移植性很差,是因为64位数据类型在不同架构的处理器中定义是不同的。
通过PRIu64
、PRId64
、PRIx64
等宏可以很好的解决这个问题。
PRIu64
的定义在 inttypes.h
头文件中,使用时需要引入该头文件
# if __WORDSIZE == 64
# define __PRI64_PREFIX "l"
# else
# define __PRI64_PREFIX "ll"
# endif
# define PRIu64 __PRI64_PREFIX "u"
对于32 位编译器,会把 "%" PRIu64 ""
扩展为 "%lld"
,对于64 位编译器,会把 "%" PRIu64 ""
扩展为 "%ld"
。
示例代码如下:
#include <stdio.h>
#include <inttypes.h>
int main(int argc, char *argv[])
{
uint64_t u64 = 100;
int64_t int64 = 100;
printf("uint64: %" PRIu64 "\n", u64);
printf("uint64 hex: %0x" PRIx64 "\n", u64);
printf("int64: %" PRId64 "\n", int64);
return 0;
}
注意: 如果是C++程序,需要定义_STDC_FORMAT_MACROS宏,可以在cmakelist.txt中加入add_compile_definitions(_STDC_FORMAT_MACROS)
添加该宏