ChatGPT解决这个技术问题 Extra ChatGPT

如何使用 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 打印。这在这两种情况下都有效。我正在寻找是否有任何其他想法。

如果您的 libc 实现不支持 z 修饰符,则强制转换为 unsigned long 是最佳选择; C99 标准建议 size_t 的整数转换等级不要大于 long,因此您是相当安全的
在 Windows 平台上,size_t 可以大于 long。出于兼容性原因,long 始终是 32 位,但 size_t 可以是 64 位。因此,转换为 unsigned long 可能会丢失一半的位。对不起 :-)

A
Adam Rosenfield

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

+1。这是 C99 的补充还是也适用于 C++(我手边没有 C90)?
它是 C99 的新增内容,未出现在 2009-11-09 的 C++0x 草案的 printf() 长度修饰符列表中(第 672 页上的表 84)
@Christoph:也不是最新的草案,n3035。
@avakar @Adam Rosenfield @Christoph @GMan:但是,在 n3035 §1.2 规范性参考资料中,仅参考了 C99 标准,并且相同的 §17.6.1.2/3 规定“提供了 C 标准库的设施”。我将此解释为,除非另有说明,否则 C99 标准库中的所有内容都是 C++0x 标准库的一部分,包括 C99 中的附加格式说明符。
@ArunSaha:这只是 C99 的功能,而不是 C++。如果您希望它使用 -pedantic 进行编译,您需要获得支持 C++1x 草案的编译器(极不可能),或者您需要将代码移动到编译为 C99 的文件中。否则,您唯一的选择是将变量转换为 unsigned long long 并使用 %llu 以最大限度地实现可移植性。
C
Community

看起来它取决于您使用的编译器(blech):

gnu 说 %zu (或 %zx 或 %zd 但显示它好像它已签名等)

Microsoft 说 %Iu(或 %Ix,或 %Id,但又是已签名的,等等)——但从 cl v19(在 Visual Studio 2015 中)开始,Microsoft 支持 %zu(请参阅此评论的回复)

...当然,如果您使用 C++,您可以使用 cout 代替 suggested by AraK


newlib(即 cygwin)也支持 z
size_t%zd 不正确; size_t 对应的有符号类型是正确的,但 size_t 本身是无符号类型。
@KeithThompson:我确实也提到了 %zu (以及 %zx 以防他们想要十六进制)。确实如此,%zu 可能应该是列表中的第一个。固定的。
@TJCrowder:我认为 %zd 根本不应该在列表中。我想不出任何理由使用 %zd 而不是 %zu 来打印 size_t 值。如果值超过 SIZE_MAX / 2,它甚至无效(具有未定义的行为)。 (为了完整起见,您可能会提到 %zo 表示八进制。)
@FUZxxl:POSIX 不要求 ssize_t 是对应于 size_t 的签名类型,因此不能保证匹配 "%zd"。 (它可能在大多数实现中。)pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
J
John Bode

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

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

对于 C99 及更高版本,请使用 %zu

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

考虑到 2013 年,建议“For C99 and onward”和“For pre C99:”。最佳答案。
不要这样做。它将在 size_t 为 64 位且 long 为 32 位的 64 位 Windows 上失败。
@Yttrill:那么 64 位窗口的答案是什么?
或者:您可以转换为 uint64_t,然后使用 inttypes.h 中的 PRIu64 宏,其中包含格式说明符。
@JamesKo 那有什么意义? uint64_t 是 C99,所以如果它可用,那么 "%zu" 也是如此(这是正确的做法)。
v
vulcan raven

扩展 Adam Rosenfield 对 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 生成的二进制输出:

1 1 2

而VS2013生成的那个说:

zu zx zd

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

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

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

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

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


VS2013 Update 5 产生的结果与 Update 4 给您的结果相同。
h
haccks
printf("size = %zu\n", sizeof(thing) );

s
swestrup

对于那些谈论在不一定支持 C99 扩展的 C++ 中执行此操作的人,我衷心推荐 boost::format。这使得 size_t 类型大小问题变得毫无意义:

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

由于您不需要 boost::format 中的大小说明符,因此您只需担心要如何显示该值。


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

是的,但提问者专门要求 printf 说明符。我猜他们还有其他一些未说明的限制,这使得使用 std::cout 成为问题。
@Donal 我想知道 C++ 流在 C++ 项目中会产生什么样的问题!
@AraK。他们很慢?他们无缘无故地添加了很多字节。 ArunSaha 只是想知道他/她自己的个人知识?个人喜好(我更喜欢 stdio 而非 fstream 自己)。有很多原因。
@TKCrowder:嗯,最初的请求确实说需要一个 C 解决方案(通过标记),并且有充分的理由不在 C++ 中使用流,例如,如果输出格式描述符是从消息目录中提取的。 (如果需要,您可以为消息编写解析器并使用流,但是当您可以利用现有代码时,这是很多工作。)
@Donal:标签是 C 和 C++。我绝不是在提倡 C++ 的 I/O 流的东西(我不是它的粉丝),只是指出问题 没有 最初 *"...ask specification for printf 说明符。”
K
Keith Thompson

在任何合理的现代 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" 由运行时库实现。例如,具有部分 C99 支持的实现可能会实现 long long 并使 size_t 成为 unsigned long long 的 typedef,但不支持 "%zu"。 (这样的实现可能不会定义 __STDC_VERSION__。)

有人指出,微软的实现可以有 32 位 unsigned long 和 64 位 size_t。 Microsoft 确实支持 "%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;
}

一种打印“尺寸太大而无法打印”的实现(Windows/Cygwin 上的 x86_64-w64-mingw32-gcc.exe -std=c90)实际上支持 unsigned long long 作为 C90 之上的扩展,因此您也许可以利用它——但我可以想象支持 unsigned long long 但不支持 "%llu" 的 C99 之前的实现。无论如何,该实现都支持 "%zu"

根据我的经验,当我在探索实现而不是在生产代码中时,我只想在快速一次性代码中打印 size_t 值。在这种情况下,做任何有效的事情可能就足够了。

(问题是关于 C 的,但我会提到在 C++ 中 std::cout << sizeof (int) 可以在任何版本的语言中正常工作。)


R
Rick Berge

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

std::size_t s = 1024; std::cout << s; // 或任何其他类型的流,如 stringstream!

如果你想要 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() 不是固定大小的类型)!

您可以使用 3rd 方标头或库,例如 boost。如果您还没有使用一个,您可能不想以这种方式夸大您的项目。如果您愿意为这个问题添加一个,为什么不使用 c++ 流或条件编译呢?

因此,您可以使用 c++ 流、条件编译、第 3 方框架,或者碰巧适合您的某种可移植的东西。


s
supercat

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

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

出于所有实际目的,与以下一样便携,但可能更快更小:

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

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


K
Kylotan

如果您将 32 位无符号整数传递给 %lu 格式,它会警告您吗?应该没问题,因为转换定义明确并且不会丢失任何信息。

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


如果您将错误大小的内容传递给 printf,大多数编译器不会警告您。海合会是个例外。 inttypes.h 是在 C99 中定义的,因此任何符合 C99 的 C 编译器都将拥有它,现在应该是所有的。不过,您可能必须使用编译器标志打开 C99。在任何情况下,intttypes.h 都没有为 size_t 或 ptrdiff_t 定义特定格式,因为它们被认为足够重要,可以分别获得自己的大小说明符“z”和“t”。
如果您使用 %lu,则应将 size_t 值强制转换为 unsigned longprintf 的参数没有隐式转换(促销除外)。
p
peterchen

C99 为此定义了“%zd”等。 (感谢评论者)C++ 中没有可移植的格式说明符 - 您可以使用 %p,这在这两种情况下都可以使用,但也不是可移植的选择,并给出十六进制值。

或者,使用一些流式传输(例如 stringstream)或安全的 printf 替换,例如 Boost Format。我了解此建议的用途有限(并且确实需要 C++)。 (在实现 unicode 支持时,我们使用了适合我们需求的类似方法。)

C 的基本问题是使用省略号的 printf 在设计上是不安全的——它需要从已知参数中确定附加参数的大小,因此无法修复它以支持“无论你得到什么”。所以除非你的编译器实现了一些专有的扩展,否则你就不走运了。


z 大小修饰符是标准 C,但由于各种原因,一些 libc 实现在 1990 年停滞不前(例如,Microsoft 基本上放弃了 C 以支持 C++ 和 - 最近 - C#)
C99 将大小说明符“z”定义为 size_t 值的大小,将“t”定义为 ptrdiff_t 值的大小。
%zd 是错误的,它是无符号的,所以它应该是 %zu
p
pixelbeat

在某些平台和某些类型上,有特定的 printf 转换说明符可用,但有时不得不求助于转换为更大的类型。

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


请注意,不鼓励仅链接的答案,因此答案应该是搜索解决方案的终点(相对于另一个参考中途停留,随着时间的推移往往会变得陈旧)。请考虑在此处添加独立的概要,并保留链接作为参考。
A
Andre

如果要将 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/

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

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


通过 size_t 指针将 char 数组的前 8 个字节转换为 unsigned long long 64 位,并使用 printf %I64d 将它们打印为数字并不是很壮观我知道,当然我没有编写代码来防止类型溢出但是这不在问题的范围内。

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅