在C++中如何清晰地输出size_t(或:C99中%z的最近等效物)

108

我有一些 C++ 代码,它打印了一个 size_t

size_t a;
printf("%lu", a);

我希望在32位和64位架构上编译此代码时不会收到任何警告。

如果这是C99,我可以使用printf(“%z”,a);。 但据我了解,在任何标准的C ++语言中都不存在%z。所以,我必须执行以下操作:

printf("%lu", (unsigned long) a);

这看起来真的很丑陋。

如果语言中没有打印 size_t 的功能,我想知道是否有可能编写 printf 包装器或类似方法,以便在 size_t 上插入适当的转换,从而消除不必要的编译器警告,同时仍然保持良好的警告。

有任何想法吗?


编辑 为了澄清为什么我使用 printf:我正在清理一个相对较大的代码库。它使用 printf 包装器来执行诸如“发出警告、将其记录到文件并可能使用错误退出代码”的操作。我也许能够动手编写一个 cout 包装器来实现同样的功能,但我不想改变程序中的每个 warn() 调用,只是为了消除一些编译器警告。


4
为什么你要使用printf,这才是问题所在。 - Ed S.
你的编译器是否会检查 printf 字符串并为你进行类型检查? - Pod
我的编译器确实会检查printf格式字符串并为我进行类型检查。我想保持这个功能开启。 - Justin L.
2
%zu是一个宽度说明符而不是类型说明符。它适用于c printf,你可以从C++中无缝使用它。我在下面进行了评论,所以请为它投票 ;) - Will
如果您正在使用Visual Studio,难道不能只使用“%l”吗?那不是总是正确的大小吗?或者可移植性很重要吗? - Mooing Duck
C语言的强制类型转换看起来很丑陋。如果你有一个适当的typedef,你可以使用构造函数语法,例如int(a) - Wolf
9个回答

75

%zu是在C++系统中可行的printf格式说明符,没有必要使它变得更加复杂。


9
@ChrisMarkle,一个快速的测试表明在MinGW中不起作用。此外,微软网站上也没有列出它(http://msdn.microsoft.com/en-us/library/tcxf1dw6%28v=vs.100%29.aspx)。我想答案是否定的。 - wump

62

大多数编译器都有自己的 size_tptrdiff_t 参数说明符,例如Visual C++使用 %Iu 和 %Id,我认为gcc会允许你使用 %zu 和 %zd。

你可以创建一个宏:

#if defined(_MSC_VER) || defined(__MINGW32__) //__MINGW32__ should goes before __GNUC__
  #define JL_SIZE_T_SPECIFIER    "%Iu"
  #define JL_SSIZE_T_SPECIFIER   "%Id"
  #define JL_PTRDIFF_T_SPECIFIER "%Id"
#elif defined(__GNUC__)
  #define JL_SIZE_T_SPECIFIER    "%zu"
  #define JL_SSIZE_T_SPECIFIER   "%zd"
  #define JL_PTRDIFF_T_SPECIFIER "%zd"
#else
  // TODO figure out which to use.
  #if NUMBITS == 32
    #define JL_SIZE_T_SPECIFIER    something_unsigned
    #define JL_SSIZE_T_SPECIFIER   something_signed
    #define JL_PTRDIFF_T_SPECIFIER something_signed
  #else
    #define JL_SIZE_T_SPECIFIER    something_bigger_unsigned
    #define JL_SSIZE_T_SPECIFIER   something_bigger_signed
    #define JL_PTRDIFF_T_SPECIFIER something-bigger_signed
  #endif
#endif

用法:

size_t a;
printf(JL_SIZE_T_SPECIFIER, a);
printf("The size of a is " JL_SIZE_T_SPECIFIER " bytes", a);

5
这并不是那么简单。是否支持%z取决于运行时,而不是编译器。因此,如果您同时使用GCC/mingw和msvcrt(且未使用mingw的增强printf),则使用__GNUC__会有些问题。 - jørgensen

24

C++11

C++11引入了C99,因此std::printf应支持C99的%zu格式说明符。

C++98

在大多数平台上,size_tuintptr_t是等效的,因此您可以使用定义在<cinttypes>中的PRIuPTR宏:

size_t a = 42;
printf("If the answer is %" PRIuPTR " then what is the question?\n", a);

如果你真的想要安全,将变量转换为uintmax_t并使用PRIuMAX

printf("If the answer is %" PRIuMAX " then what is the question?\n", static_cast<uintmax_t>(a));

16

关于在Windows和Visual Studio中使用printf函数的实现

 %Iu

对我有效。 请参见msdn


谢谢。这也适用于VS 2008。还要记住,也可以使用%Id%Ix%IX - c00000fd

12

既然您正在使用C ++,为什么不使用IOStreams呢? 只要您没有使用一个没有为 size_t 定义 operator << 的愚蠢C ++实现,那么这样应该可以编译而不会出现警告并且做正确的类型安全的操作。

当必须使用printf()进行实际输出时,您仍然可以将其与IOStreams组合以获得类型安全的行为:

size_t foo = bar;
ostringstream os;
os << foo;
printf("%s", os.str().c_str());

虽然这种方法并不是特别高效,但是你上面的情况涉及文件I/O,因此瓶颈在于文件I/O,而不是这个字符串格式化代码。


我知道谷歌禁止在他们的代码中使用cout。也许Justin L.正在遵守这样的限制。 - user181548
在我的情况下(请参见上面的编辑),一个有趣的想法可能是尝试使用cout来实现warn()函数。但这将涉及手动解析格式字符串,这有点棘手。 :) - Justin L.
使用std::stringstream代替IO流。 - Thomas Eding
1
流的符号表示很别扭。比较一下:printf("x=%i, y=%i;\n", x, y);cout << "x=" << x << ", y=" << y << ";" << std::endl; - wonder.mice
@wonder.mice 我不认为在格式字符串中运行时检测错误比较"好"。是的,那个库可能有一些优点,但这样的错误可以和应该在编译时被检测到,就像iostream一样。异常或其他——那只是一个支撑。 - Ruslan
显示剩余3条评论

9

fmt库提供了一个快速可移植(且安全)的printf实现,其中包括z修饰符用于size_t:

#include "fmt/printf.h"

size_t a = 42;

int main() {
  fmt::printf("%zu", a);
}

除此以外,它还支持类似于Python的格式化字符串语法,并捕获类型信息,使您不必手动提供它:
fmt::print("{}", a);

这个库已经通过主要编译器的测试,并在各个平台上提供了一致的输出。

免责声明:我是这个库的作者。


这现在是在C++20/23中吗? - underscore_d
1
@underscore_d,std::format 在 C++20 中可用,但不幸的是 print 不在其中。 - vitaut

7
这里有一个可能的解决方案,但它并不完美。
template< class T >
struct GetPrintfID
{
  static const char* id;
};

template< class T >
const char* GetPrintfID< T >::id = "%u";


template<>
struct GetPrintfID< unsigned long long > //or whatever the 64bit unsigned is called..
{
  static const char* id;
};

const char* GetPrintfID< unsigned long long >::id = "%lu";

//should be repeated for any type size_t can ever have


printf( GetPrintfID< size_t >::id, sizeof( x ) );

2
好吧...这确实达到了我的安全目标,没有警告。但是...天啊。如果那是我必须做的,我会接受警告的。 :) - Justin L.
1
不够美观?这取决于个人口味。它允许在像uintptr_t等野兽般的数据类型上完全可移植地使用printf。太棒了! - Slava
@user877329 你可以将该格式字符串构建为std :: string,然后在需要的位置附加GetPrintfID <size_t> :: id。 - stijn
@stijn 换句话说:没有可用的编译时连接。 - user877329
@user877329 不行(除非使用宏,或者我漏掉了什么)。但是为什么这会成为一个严格的要求呢? - stijn

3
size_t 的有效类型取决于实现。 C标准将其定义为sizeof运算符返回的类型; 除了是无符号和某种整数类型之外,size_t可以是任何大小都能容纳sizeof()返回的最大值的东西。
因此,用于size_t的格式字符串可能会因服务器而异。 它应始终具有“u”,但可能是l或d或其他内容...
一个技巧是将其强制转换为机器上最大的整数类型,确保在转换中没有损失,然后使用与此已知类型相关联的格式字符串。

我认为将我的 size_t 强制转换为机器上最大的整数类型并使用与该类型相关联的格式字符串是很酷的。我的问题是:是否有一种方法可以在保持清晰代码的同时实现这一点(仅对合法的 printf 格式字符串错误发出警告,没有丑陋的强制转换等)?我可以编写一个包装器来更改格式字符串,但是当我真正搞砸了格式字符串时,GCC就无法给我警告了。 - Justin L.
使用 CPP 宏测试类型大小;选择匹配的类型并指定与匹配类型相对应的格式字符串。 - Clearer

0
#include <cstdio>
#include <string>
#include <type_traits>

namespace my{
    template<typename ty>
    auto get_string(ty&& arg){
        using rty=typename::std::decay_t<::std::add_const_t<ty>>;
        if constexpr(::std::is_same_v<char, rty>)
            return ::std::string{1,arg};
        else if constexpr(::std::is_same_v<bool, rty>)
            return ::std::string(arg?"true":"false");
        else if constexpr(::std::is_same_v<char const*, rty>)
            return ::std::string{arg};
        else if constexpr(::std::is_same_v<::std::string, rty>)
            return ::std::forward<ty&&>(arg);
        else
            return ::std::to_string(arg);
    };

    template<typename T1, typename ... Args>
    auto printf(T1&& a1, Args&&...arg){
        auto str{(get_string(a1)+ ... + get_string(arg))};
        return ::std::printf(str.c_str());
    };
};

代码中稍后出现:
my::printf("test ", 1, '\t', 2.0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接