如何使用printf家族可移植地打印size_t变量?

我有一个类型为size_t的变量,我想使用printf()来打印它。我使用什么格式说明符来可移植地打印它?

在32位机器中,%u似乎是正确的。我使用g++ -g -W -Wall -Werror -ansi -pedantic编译,并且没有警告。但是当我在64位机器上编译该代码时,它会产生警告。

size_t x = <something>;
printf("size = %u\n", x);


warning: format '%u' expects type 'unsigned int',
but argument 2 has type 'long unsigned int'

如预期的那样,如果我将其更改为%lu,警告就会消失。

问题是,我如何编写代码,使它在32位和64位机器上编译警告免费?

编辑:作为一种变通方法,我猜一个答案可能是将变量“强制转换”为一个足够大的整数,比如unsigned long,然后使用%lu打印。这在两种情况下都适用。我看看有没有其他的办法。

376825 次浏览
printf("size = %zu\n", sizeof(thing) );

如果您传递一个32位无符号整数为%lu格式,它会警告您吗?这应该没问题,因为转换是定义良好的,不会丢失任何信息。

我听说一些平台在<inttypes.h>中定义宏,你可以插入到格式字符串文字中,但我在我的Windows c++编译器上没有看到这个头,这意味着它可能不是跨平台的。

std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!

看起来它取决于你使用的编译器(漂白):

  • gnu说%zu(或%zx%zd,但会显示它,好像它是有符号的,等等)
  • 微软说%Iu(或%Ix,或%Id,但同样是有符号的,等等)但是从cl v19(在Visual Studio 2015中)开始,微软支持%zu (见this reply to this comment)

...当然,如果你使用c++,你可以用cout代替AraK建议

使用z修饰符:

size_t x = ...;
ssize_t y = ...;
printf("%zu\n", x);  // prints as unsigned decimal
printf("%zx\n", x);  // prints as hex
printf("%zd\n", y);  // prints as signed decimal

C99为此定义了“%zd”等。在c++中没有可移植的格式说明符——你可以使用%p,这在这两种情况下都是word,但也不是一个可移植的选择,并以十六进制给出值。

或者,使用一些流(例如stringstream)或安全的printf替代品,如提升格式。我知道这个建议只有有限的用途(并且确实需要c++)。(在实现unicode支持时,我们使用了适合我们需求的类似方法。)

C语言的基本问题是printf使用省省号在设计上是不安全的——它需要从已知参数中确定额外参数的大小,所以它不能被修复为支持“你得到的任何东西”。所以除非你的编译器实现了一些专有的扩展,否则你就不走运了。

对于C89,使用%lu并将值转换为unsigned long:

size_t foo;
...
printf("foo = %lu\n", (unsigned long) foo);

对于C99及以后版本,使用%zu:

size_t foo;
...
printf("foo = %zu\n", foo);

对于那些在c++中不一定支持C99扩展的人来说,我衷心推荐boost::format。这使得size_t类型的大小问题没有意义:

std::cout << boost::format("Sizeof(Var) is %d\n") % sizeof(Var);

由于在boost::格式中不需要大小说明符,所以只需考虑如何显示值即可。

在某些平台上,对于某些类型,有特定的printf转换说明符可用,但有时必须求助于更大的类型强制转换。

我在这里用示例代码记录了这个棘手的问题: http://www.pixelbeat.org/programming/gcc/int_types/ 并定期更新有关新平台和类型的信息

扩展亚当·罗森菲尔德对Windows的回答。

我在VS2013 Update 4和VS2015预览版上测试了这段代码:

// test.c


#include <stdio.h>
#include <BaseTsd.h> // see the note below


int main()
{
size_t x = 1;
SSIZE_T y = 2;
printf("%zu\n", x);  // prints as unsigned decimal
printf("%zx\n", x);  // prints as hex
printf("%zd\n", y);  // prints as signed decimal
return 0;
}

VS2015生成二进制输出:

< p > 1
1
2 < / p >

而VS2013生成的则是:

< p >祖茂堂
zx
zd < / p >

注意:ssize_t是POSIX扩展,而SSIZE_TWindows数据类型中是类似的东西,因此我添加了<BaseTsd.h>引用。

此外,除了以下C99/C11头文件,所有C99头文件都可以在VS2015预览版中使用:

C11 - <stdalign.h>
C11 - <stdatomic.h>
C11 - <stdnoreturn.h>
C99 - <tgmath.h>
C11 - <threads.h>

此外,C11的<uchar.h>现在包含在最新预览中。

有关更多详细信息,请参阅此列表以获得标准一致性。

正如AraK所说,c++流接口将始终可移植地工作。

std::size_t = 1024; std:: cout & lt; & lt;年代;//或任何其他类型的流,如stringstream!< / p >

如果你想要C stdio,对于某些“可移植”的情况,没有可移植的答案。正如您所看到的,选择错误的格式标志可能会产生编译器警告或给出不正确的输出,因此情况变得很糟糕。

C99试图用“%”PRIdMAX“\n”这样的inttypes.h格式来解决这个问题。但就像“%zu”一样,并不是所有人都支持c99(就像2013年之前的MSVS)。有“msinttypes.h”文件来处理这个问题。

如果转换为不同的类型,则根据标志的不同,可能会收到编译器关于截断或符号更改的警告。如果你走这条路,选择一个更大的相关固定大小的类型。unsigned long long和“%llu”或unsigned long“%lu”中的一个应该可以工作,但llu在32位的世界中也可能会因为太大而减慢速度。(编辑-我的mac发出了一个64位的警告,因为%llu不匹配size_t,即使%lu, %llu和size_t都是相同的大小。在我的MSVS2012上,%lu和%llu的大小不一样。所以你可能需要强制转换+使用匹配的格式。)

因此,您可以使用固定大小的类型,例如int64_t。但是等等!现在我们回到了c99/c++11,旧的MSVS又失败了。另外,你也有类型转换(例如map.size()不是一个固定大小的类型)!

您可以使用第三方头文件或库,如boost。如果您还没有使用它,您可能不希望以这种方式扩展项目。如果您愿意为这个问题添加一个,为什么不使用c++流或条件编译呢?

所以你只能使用c++流、条件编译、第三方框架,或者恰好适合你的某种可移植的东西。

如果你想打印size_t的值作为字符串,你可以这样做:

char text[] = "Lets go fishing in stead of sitting on our but !!";
size_t line = 2337200120702199116;


/* on windows I64x or I64d others %lld or %llx if it works %zd or %zx */
printf("number: %I64d\n",*(size_t*)&text);
printf("text: %s\n",*(char(*)[])&line);

结果是:

号码:2337200120702199116

我们去钓鱼吧,别坐在椅子上了!

编辑:重新阅读问题,因为向下投票,我注意到他的问题不是%llu或%I64d,而是不同机器上的size_t类型,看到这个问题https://stackoverflow.com/a/918909/1755797
http://www.cplusplus.com/reference/cstdio/printf/ < / p >

size_t在32位机器上是unsigned long long int,在64位机器上是unsigned long long int 但是%ll总是期望unsigned long long int.

Size_t在不同的操作系统上的长度不同,而%llu是相同的

在任何合理的现代C实现中,"%zu"是打印类型为size_t的值的正确方法:

printf("sizeof (int) = %zu\n", sizeof (int));

"%zu"格式说明符是在1999年ISO C标准中添加的(并被2011年ISO c++标准所采用)。如果您不需要关心更老的实现,那么现在可以停止阅读。

如果你的代码需要移植到c99之前的实现,你可以将值转换为unsigned long并使用"%lu":

printf("sizeof (int) = %lu\n", (unsigned long)sizeof (int));

这不能移植到C99或更高版本,因为C99引入了long longunsigned long long,因此size_t可能比unsigned long更宽。

抵制使用没有强制转换的"%lu""%llu"的诱惑。用于实现size_t的类型是实现定义的,如果类型不匹配,则行为是未定义的。像printf("%lu\n", sizeof (int));这样的东西可能“工作”,但它根本不是可移植的。

原则上,下面的应该涵盖了所有可能的情况:

#if __STDC_VERSION__ < 199901L
printf("sizeof (int) = %lu\n", (unsigned long)sizeof (int));
#else
printf("sizeof (int) = %zu\n", sizeof (int));
#endif

在实践中,它可能并不总是正确工作。__STD_VERSION__ >= 199901L 应该保证支持"%zu",但并非所有实现都是正确的,特别是因为__STD_VERSION__是由编译器设置的,而"%zu"是由运行时库实现的。例如,支持"%zu"0 C99的实现可能实现long long,并使size_t成为unsigned long long的类型定义,但不支持"%zu"。(这样的实现可能不会定义__STDC_VERSION__。)

已经指出微软的实现可以有32位的unsigned long和64位的size_t。微软确实支持"%zu",但该支持是相对较晚添加的。另一方面,只有当特定的size_t值恰好超过ULONG_MAX时,转换到unsigned long才会有问题,而这在实践中不太可能发生。

如果你能够假设合理的现代实现,只需使用"%zu"。如果你需要考虑旧的实现,这里有一个荒谬的可移植程序,它适应各种配置:

#include <stdio.h>
#include <limits.h>
int main(void) {
const size_t size = -1; /* largest value of type size_t */
#if __STDC_VERSION__ < 199901L
if (size > ULONG_MAX) {
printf("size is too big to print\n");
}
else {
printf("old: size = %lu\n", (unsigned long)size);
}
#else
printf("new: size = %zu\n", size);
#endif
return 0;
}

一个实现打印“size is too big to print”;(x86_64-w64-mingw32-gcc.exe -std=c90 on Windows/Cygwin)实际上支持unsigned long long作为C90之上的扩展,所以你可能能够利用这一点——但我可以想象一个前c99实现支持unsigned long long但不支持"%llu"。无论如何,该实现支持"%zu"

根据我的经验,我只想在研究实现时在快速丢弃代码中打印size_t值,而不是在生产代码中。在这种情况下,只要可行就足够了。

(这个问题是关于C语言的,但我要提一下,在c++中,std::cout << sizeof (int)将在任何版本的语言中正确工作。)

在大多数程序员想要输出size_t的上下文中,程序员会对所输出的数值有一个合理的上限。例如,如果程序员输出一条消息,说int有多大,使用:

printf("int is %u bytes", (unsigned)sizeof (int) );

从所有的实际目的来看,它与以下内容一样便携,但可能更快更小:

printf("int is %zu bytes", sizeof (int) );

这种构造可能失败的唯一情况是,在一个平台上,int上的填充字节数相对于unsigned int所能表示的最大值的大小大得离谱(sizeof (int)可能大于65535有点令人难以置信,但更令人难以置信的是,它可能那么大,而unsigned没有足够的值位来表示一个大于sizeof (int)的数字。