在跨平台的 c/c++ 项目(Win32、Linux、OSX)上,我需要使用 *printf 函数来打印一些 size_t 类型的变量。在某些环境中 size_t 是 8 个字节,而在其他环境中是 4 个。在 glibc 上我有 %zd,在 Win32 上我可以使用%Id。有没有一种优雅的方法来处理这个?
10 回答
PRIuPTR
宏(来自 <inttypes.h>)为 定义了十进制格式,uintptr_t
它应该总是足够大,以至于您可以size_t
在不截断的情况下对其进行转换,例如
fprintf(stream, "Your size_t var has value %" PRIuPTR ".", (uintptr_t) your_var);
这里真的有两个问题。第一个问题是三个平台的正确 printf 说明符字符串是什么。请注意,这size_t
是一个无符号类型。
在Windows上,使用“ %Iu
”。
第二个问题是如何支持多个平台,因为每个平台上的格式字符串等内容可能不同。正如其他人指出的那样,使用#ifdef
很快就会变得丑陋。
相反,为每个目标平台编写单独的 makefile 或项目文件。然后通过源文件中的某个宏名称引用说明符,在每个 makefile 中适当地定义宏。特别是,GCC 和 Visual Studio 都接受一个“D”开关来在命令行上定义宏。
如果您的构建系统非常复杂(多个构建选项、生成的源代码等),维护 3 个单独的 makefile 可能会变得笨拙,并且您将不得不使用某种高级构建系统,例如 CMake 或 GNU 自动工具。但基本原理是相同的——使用构建系统定义特定于平台的宏,而不是将平台检测逻辑放在源文件中。
我唯一能想到的,是典型的:
#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 在 Embedded Systems Design 中写了一篇文章来讨论这个问题。根据 Dan 的说法, %zu 是标准方式,但很少有编译器支持这一点。作为替代方案,他建议使用 %lu 以及将参数显式转换为 unsigned long:
size_t n; ... printf("%lu", (unsigned long)n);
Use boost::format
. It's typesafe, so it'll print size_t
correctly with %d
, also you don't need to remember to put c_str()
on std::string
s when using it, and even if you pass a number to %s
or vice versa, it'll work.
我不知道任何令人满意的解决方案,但您可能会考虑使用专门的函数将 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 的情况。如果这对您来说太短,您总是可以转换为 unsigned long long 并将其格式化为 %llu。
不管怎样,你的琴弦永远不会尴尬。
选项1:
由于在大多数(如果不是全部?)系统上,PRIuPTR
printf 格式字符串来自也足够长来容纳一个类型,我建议对printf 格式字符串size_t
使用以下定义。size_t
但是,重要的是您要验证这是否适用于您的特定架构(编译器、硬件等),因为标准并未强制执行此操作。
#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);
选项 2:
然而,在可能的情况下,只使用%zu
“z”长度说明符,如此处所示,用于size_t
类型:
示例用法:
size_t my_variable;
printf("%zu\n", my_variable);
然而,在某些系统上,例如使用 gcc 作为编译器的 STM32 微控制器,%z
不一定实现长度说明符,并且执行类似的操作printf("%zu\n", my_size_t_num);
可能最终会打印出文字“%zu”(我亲自对此进行了测试并发现它是真的)而不是你的size_t
变量的值。
选项 3:
但是,在您需要它绝对保证工作的地方,或者在您不确定您的特定架构的地方,只需将其转换为 a 并打印即可uint64_t
,因为这可以保证工作,但需要额外的转换步骤。
示例用法:
#include <stdint.h> // for uint64_t
#include <inttypes.h> // for PRIu64
size_t my_variable;
printf("%" PRIu64 "\n", (uint64_t)my_variable);
引用来源:
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 年左右之前,它应该处理所有古老和新平台的效率有点低,那时double
可能缺乏精确的结果。
printf("%.0f\n", (double) some_size_t_object);