我有一个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打印。这在两种情况下都适用。我看看有没有其他的办法。


当前回答

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

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

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

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

其他回答

使用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

在程序员想要输出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)大的数字。

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

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

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

在任何合理的现代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 long和unsigned 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”是由运行时库实现的。例如,部分支持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之上的扩展,因此您可能能够利用它——但我可以想象一个支持unsigned long - long但不支持“%llu”的前c99实现。无论如何,该实现支持“%zu”。

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

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