在C ++中将代码清除为printf size_t(或:在C ++中与C99的%z最接近)


96

我有一些C ++代码可以显示size_t

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

我希望在32位和64位体系结构上进行编译而不会发出警告。

如果这是C99,则可以使用printf("%z", a);。但是AFAICT %z在任何标准C ++方言中都不存在。所以我要做

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

这真的很丑。

如果size_t语言中没有内置的用于打印s的功能,我想知道是否有可能编写一个printf包装器或诸如此类的东西,以便在size_ts 上插入适当的强制转换,从而消除虚假的编译器警告,同时仍保持良好的警告。

有任何想法吗?


编辑以弄清为什么我使用printf:我有一个相对较大的代码库正在清理。它使用printf包装器执行“写警告,将其记录到文件中,并可能由于错误而退出代码”之类的操作。我也许能够使用cout包装器来聚集足够的C ++-foo,但是我不想改变程序中的每个warn()调用只是为了摆脱一些编译器警告。


4
为什么要使用printf才是问题。
Ed S.

编译器是否检查printf字符串并为您检查类型?

我的编译器确实检查了printf格式字符串,并为我键入了检查它。我想保持此功能打开。
贾斯汀·

2
%zu,z是宽度说明符,而不是类型说明符。它适用于可以从C ++无缝使用的c printf。我下面的评论,所以选它;)
威尔

如果您使用的是Visual Studio,就不能使用"%l"吗?那不是总是合适的尺寸吗?还是可移植性重要?
Mooing Duck 2013年

Answers:


61

大多数编译器都有自己的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支持取决于运行时,而不取决于编译器。__GNUC__因此,如果将GCC / mingw与msvcrt混合使用(并且不使用mingw的增强printf),则使用会有点问题。
约根森


17

C ++ 11

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

C ++ 98

在大多数平台上,size_t并且uintptr_t是等效的,在这种情况下,您可以使用在中PRIuPTR定义的宏<cinttypes>

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和printf的Visual Studio实现中

 %Iu

为我工作。参见 msdn


谢谢。工程在VS 2008太。也请记住,一个可以使用%Id%Ix并且%IX也。
c00000fd

11

由于您使用的是C ++,为什么不使用IOStreams?只要您没有使用没有定义operator <<for 的健忘的C ++实现,那它就应该在没有警告的情况下进行编译并执行正确的类型感知的事情size_t

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

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

它不是超级高效,但是上面的案例处理文件I / O,因此这是您的瓶颈,而不是此字符串格式代码。


我知道Google禁止在其代码中使用cout。贾斯汀·L(Justin L.)也许正在这种限制下工作。

在我的情况下(请参见上面的编辑),一个有趣的想法可能是尝试并按照cout实现warn()函数。但这将涉及手动解析格式字符串,这很麻烦。:)
贾斯汀·

您的最新编辑实际上与我认为可能对我有用的相反。我不想重写所有调用printf包装器的代码,但我不介意重写printf包装器的实现以使用cout。但是我认为这不会发生。:)
贾斯汀·

使用std::stringstream而不是IO流。
Thomas Eding 2013年

1
流具有笨拙的表示法。比较:printf("x=%i, y=%i;\n", x, y);cout << "x=" << x << ", y=" << y << ";" << std::endl;
wonder.mice 2015年

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
好吧……确实达到了我的安全目标,并且没有警告。但是... 如果那是我必须要做的,我会警告。:)
贾斯汀·

1
不漂亮?!取决于口味。它允许完全将printf与uintptr_t之类的野兽一起使用。大!
Slava 2013年

@ user877329,您可以将该格式字符串构建为std :: string,然后在所需位置附加GetPrintfID <size_t> :: id
stijn 2014年

@stijn换句话说:没有编译时串联可用
user877329 2014年

@ user877329否(除非使用宏,否则我会丢失某些东西)。但是,为什么这是一个困难的要求?
stijn 2014年

4

FMT库提供了快速便携式(安全)实现的printf,包括z了修改size_t

#include "fmt/printf.h"

size_t a = 42;

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

除此之外,它还支持类似Python的格式字符串语法并捕获类型信息,因此您无需手动提供它:

fmt::print("{}", a);

它已经在主要编译器上进行了测试,并提供跨平台的一致输出。

免责声明:我是这个图书馆的作者。


3

基于size_t的有效类型取决于实现。C标准将其定义为sizeof运算符返回的类型;除了无符号和某种整数类型之外,size_t几乎可以是任何大小可以容纳由sizeof()返回的最大值的东西。

因此,根据服务器的不同,用于size_t的格式字符串可能会有所不同。它应该始终带有“ u”,但可以是l或d或其他名称。

一个技巧是将其转换为计算机上最大的整数类型,以确保转换不会丢失,然后使用与此已知类型关联的格式字符串。


我会很酷地将size_ts 强制转换为计算机上最大的整数类型,并使用与此类型关联的格式字符串。我的问题是:有没有办法在保持干净代码的同时做到这一点(仅针对合法的printf格式字符串错误,没有难看的强制转换等警告)?我可以编写一个包装程序来更改格式字符串,但是当我合理地弄乱了格式字符串时,GCC将无法向我发出警告。
贾斯汀·

使用CPP宏测试类型的大小;选择一个匹配的,并指定与匹配类型匹配的格式字符串。
2014年

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);

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.