在跨平台的c / c ++项目(Win32,Linux,OSX)中,我需要使用* printf函数来打印一些size_t类型的变量。在某些环境中,size_t是8个字节,而在其他环境中它们是4.在glibc上我有%zd,在Win32上我可以使用%Id。有一种优雅的方式来处理这个问题吗?
PRIuPTR
宏(来自
)为。定义十进制格式
uintptr_t
,它应该总是足够大,你可以在没有截断的情况下向它施放size_t
,例如:
fprintf(stream, "Your size_t var has value %" PRIuPTR ".", (uintptr_t) your_var);
size_t
是一个至少16位的无符号类型。经常可以看到32和64的宽度。
printf("%zu\n", some_size_t_object); // Standard since C99
以上是未来的最佳方式,但如果代码也需要移植到C99之前的平台,则将值转换为某种宽泛的类型。 unsigned long
是合理的候选人,但可能缺乏。
// OK, yet insufficient with large sizes > ULONG_MAX
printf("%lu\n", (unsigned long) some_size_t_object);
或使用条件代码
#ifdef ULLONG_MAX
printf("%llu\n", (unsigned long long) some_size_t_object);
#else
printf("%lu\n", (unsigned long) some_size_t_object);
#endif
最后考虑double
。这是一个有点低效但应该处理所有古老和新的平台,直到2030年至2040年左右考虑Moore's law时,double
可能缺乏精确的结果。
printf("%.0f\n", (double) some_size_t_object);
这里真的有两个问题。第一个问题是三个平台的正确printf说明符字符串是什么。请注意,size_t
是无符号类型。
在Windows,使用“%Iu
”。
第二个问题是如何支持多个平台,因为格式字符串之类的东西可能在每个平台上都不同。正如其他人所指出的那样,使用#ifdef
会很快变得难看。
而是为每个目标平台编写单独的makefile或项目文件。然后通过源文件中的某个宏名称引用说明符,在每个makefile中相应地定义宏。特别是,GCC和Visual Studio都接受“D”开关来在命令行上定义宏。
如果您的构建系统非常复杂(多个构建选项,生成的源等),维护3个单独的makefile可能会变得难以处理,并且您将不得不使用某种高级构建系统,如CMake或GNU autotools。但基本原理是相同的 - 使用构建系统来定义特定于平台的宏,而不是将平台检测逻辑放在源文件中。
我唯一能想到的是典型的:
#ifdef __WIN32__ // or whatever
#define SSIZET_FMT "%ld"
#else
#define SSIZET_FMT "%zd"
#endif
然后利用恒定折叠:
fprintf(stream, "Your size_t var has value " SSIZET_FMT ".", your_var);
Dan Saks在嵌入式系统设计中写了一篇文章,covered这件事。根据Dan的说法,%zu是标准方式,但很少有编译器支持这一点。作为替代方案,他建议使用%lu以及对unsigned long的显式转换:
size_t n; ... printf("%lu", (unsigned long)n);
使用boost::format
。它是类型安全的,所以它会用size_t
正确打印%d
,你也不需要记得在使用它时将c_str()
放在std::string
s上,即使你将数字传递给%s
,反之亦然,它也会起作用。
我不知道任何令人满意的解决方案,但你可能会考虑一个专门的函数来将size_t项格式化为字符串,并打印字符串。
(或者,如果你能逃脱它,boost :: format可以轻松处理这类事情。)
您只需要找到具有最大存储类的整数类型,将值转换为它,然后对较大的类型使用适当的格式字符串。请注意,此解决方案适用于任何类型(ptrdiff_t等),而不仅仅是size_t。
您要使用的是uintmax_t和格式宏PRIuMAX。对于Visual C ++,您将需要下载与c99兼容的stdint.h和inttypes.h头文件,因为Microsoft不提供它们。
另见
http://www.embedded.com/columns/technicalinsights/204700432
本文纠正了Frederico引用的文章中的错误。
我对这个问题的选择是简单地将size_t参数转换为unsigned long并在任何地方使用%lu - 当然这只是在值不会超过2 ^ 32-1的情况下。如果这对你来说太短,你总是可以转换为无符号的long long并将其格式化为%llu。
无论哪种方式,你的字符串永远不会尴尬。
由于在大多数(如果不是全部?)系统中,来自PRIuPTR
的printf format string 也足以容纳size_t
类型,我建议使用size_t
printf格式字符串的以下定义。
但是,重要的是要验证这将适用于您的特定体系结构(编译器,硬件等),因为标准不强制执行此操作。
#include <inttypes.h>
// Printf format strings for `size_t` variable types.
#define PRIdSZT PRIdPTR
#define PRIiSZT PRIiPTR
#define PRIoSZT PRIoPTR
#define PRIuSZT PRIuPTR
#define PRIxSZT PRIxPTR
#define PRIXSZT PRIXPTR
用法示例:
size_t my_variable;
printf("%" PRIuSZT "\n", my_variable);
但是,如果可能,只需使用%zu
“z”长度说明符as shown here,用于size_t
类型:
用法示例:
size_t my_variable;
printf("%zu\n", my_variable);
然而,在某些系统上,例如使用gcc作为编译器的STM32微控制器,%z
长度说明符不一定实现,并且像printf("%zu\n", my_size_t_num);
这样的操作可能最终打印出文字“%zu”(我亲自测试了这个并找到了这是真的)而不是你的size_t
变量的值。
但是,如果您需要它绝对保证可以工作,或者您不确定您的特定架构,只需投射和打印为uint64_t
并完成,因为这可以保证工作,但需要额外的步骤。
用法示例:
#include <stdint.h> // for uint64_t
#include <inttypes.h> // for PRIu64
size_t my_variable;
printf("%" PRIu64 "\n", (uint64_t)my_variable);