Physical Address:
ChongQing,China.
WebSite:
在我们日常开发中,经常会涉及到一些Debug日志的打印,此时可能需要打印各种数据类型的变量值,从而进行诊断。
此时我们一般有两种选择,一种是使用stdout流输出的方式进行打印;另外一种则是使用printf结合占位符的形式进行打印。这里简单举例如下:
int a = 1;
int64_t b = 2;
//占位符打印
printf("%lld,%d\n", a, b);
//流输出打印
std::cout << a <<","<< b <<std::endl;
其中stdout流失输出的方式写起来不够简洁,个人不喜欢使用;相反更喜欢使用printf结合占位符的方式来进行打印。
常规情况下,我们使用C/C++打印变量时都会使用占位符,而不同的数据类型需要搭配不同的占位符才能正常进行打印,以下是一些常用数据类型对应的占位符清单:
数据类型 | 占位符 | 备注 |
char | %c | 用于打印字符 |
char*,char[] | %s | 用于打印以\0结尾的字符串 |
int | %d | 十进制有符号整数 |
long | %ld | 十进制有符号长整型 |
long long | %lld | |
unsigned int | %u | 十进制无符号整数 |
unsigned int | %o | 八进制无符号整数 |
unsigned int | %x | 十六进制无符号整数,小写 |
unsigned int | %X | 十六进制无符号整数,大写 |
unsigned long | %lu | 十进制无符号长整型 |
unsigned long long | %llu | |
float | %f | 浮点数 |
double | %lf | 双精度浮点数 |
pointer | %p | 任何指针类型 |
但在某些时候,我们使用占位符打印可能会存在问题,比如此种场景:
uint64_t product_seq=45845454;
printf(“product sequence is %lu”, product_seq);
如果我们在64位平台上进行编译运行,不会有任何问题;但如果我们在32位平台上运行,则会提示报错,报错的信息提示此处应当使用的占位符为%llu而非%lu。
这是因为uint64_t 在64位平台上对应的数据类型是unsigned long,在32位平台上则是unsigned long long,如果我们再使用%lu就会报错。
如何解决一定位长的数据在不同平台上的数据类型不一致而导致占位符打印失败呢,其实C/C++语言标准规范制定者已经考虑到了这种问题,并给出了相应的解决方案。我们在C中可以引用inttypes.h,而在C++中引用cinttypes头文件,再结合具体的宏即可实现优雅的跨平台打印。如下示例:
#C example
include <inttypes.h>
uint64_t product_seq=45845454;
printf(“product sequence is %” PRIu64, product_seq);
#C++ example
include <cinttypes>
uint64_t product_seq=45845454;
printf(“product sequence is %” PRIu64, product_seq);
所以当我们需要跨平台打印不同位长的数据类型时,我们最好摒弃直接使用占位符进行打印的方式,转而利用官方标准定义中的宏来帮助我们进行打印,这里是一些对应表:
数据类型 | 十进制 | 十六进制 | 备注 |
int8_t | PRId8/ PRIi8 | ||
int16_t | PRId16/PRIi16 | ||
int32_t | PRId32/PRIi32 | ||
int64_t | PRId64/PRIi64 | ||
uint8_t | PRIu8 | PRIx8 | 如需要大写则将x变为X |
uint16_t | PRIu16 | PRIx16 | 如需要大写则将x变为X |
uint32_t | PRIu32 | PRIx32 | 如需要大写则将x变为X |
uint64_t | PRIu64 | PRIx64 | 如需要大写则将x变为X |
这里我们可以再进一步,探究其实现的原理,这里我们可以找到inttypes.h头文件,看看关于PRIu64的定义:
#define PRIu8 "u" /* uint8_t */
#define PRIu16 "u" /* uint16_t */
#define PRIu32 "u" /* uint32_t */
#define PRIu64 __PRI_64_prefix"u" /* uint64_t */
这里我们可以看到PRIu64会被替换为__PRI_64_prefix”u”,而__PRI_64_prefix的定义在该头文件中:
#ifdef __LP64__
#define __PRI_64_prefix "l"
#define __PRI_PTR_prefix "l"
#else
#define __PRI_64_prefix "ll"
#define __PRI_PTR_prefix
#endif
这段宏定义的作用其实就是根据宏__LP64__来设置__PRI_64_prefix,在不同的平台中其会被不同的值取代。由此,__PRI_64_prefix”u”在64位平台上就是lu,在32位平台上就是llu,在预处理阶段就完成了替换,从而实现了跨平台的类型适配。