Bereinigen Sie den Code für printf size_t in C ++ (oder: Das nächste Äquivalent von% z von C99 in C ++)


96

Ich habe einen C ++ - Code, der Folgendes druckt size_t:

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

Ich möchte, dass dies ohne Warnungen auf 32- und 64-Bit-Architekturen kompiliert wird.

Wenn dies C99 wäre, könnte ich verwenden printf("%z", a);. AFAICT %zexistiert jedoch in keinem Standard-C ++ - Dialekt. Also muss ich stattdessen tun

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

Das ist wirklich hässlich.

Wenn es keine Möglichkeit gibt, size_ts in die Sprache zu drucken , frage ich mich, ob es möglich ist, einen printf-Wrapper oder einen solchen zu schreiben, der die entsprechenden Casts auf size_ts einfügt , um falsche Compiler-Warnungen zu beseitigen und gleichzeitig die guten beizubehalten.

Irgendwelche Ideen?


Bearbeiten Um zu verdeutlichen, warum ich printf verwende: Ich habe eine relativ große Codebasis, die ich bereinige. Es verwendet printf-Wrapper, um beispielsweise eine Warnung zu schreiben, sie in einer Datei zu protokollieren und den Code möglicherweise mit einem Fehler zu beenden. Möglicherweise kann ich genug C ++ - foo aufbringen, um dies mit einem Cout-Wrapper zu tun, aber ich möchte lieber nicht jeden warn () -Aufruf im Programm ändern, nur um einige Compiler-Warnungen zu entfernen.


4
Warum Sie printf überhaupt verwenden, sollte die Frage sein.
Ed S.

Überprüft Ihr Compiler die printf-Zeichenfolge und die Typprüfung für Sie?
Pod

Mein Compiler überprüft tatsächlich die Zeichenfolge im printf-Format und prüft sie für mich. Ich möchte diese Funktion aktiviert lassen.
Justin L.

2
% zu, z ist ein Breitenbezeichner, kein Typbezeichner. Es funktioniert für das c printf, das Sie nahtlos von C ++ aus verwenden können. Ich habe es unten kommentiert, also stimme dafür;)
Will

Wenn Sie Visual Studio verwenden, können Sie es nicht einfach verwenden "%l"? Wird das nicht immer die richtige Größe sein? Oder spielt Portabilität eine Rolle?
Mooing Duck

Antworten:


61

Die meisten Compiler haben einen eigenen Bezeichner für size_tund ptrdiff_tArgumente. Visual C ++ verwendet beispielsweise% Iu bzw.% Id. Ich denke, dass Sie mit gcc% zu und% zd verwenden können.

Sie könnten ein Makro erstellen:

#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

Verwendung:

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

5
Es ist nicht so einfach. Ob %zunterstützt wird oder nicht, hängt von der Laufzeit ab, nicht vom Compiler. Die Verwendung __GNUC__ist daher ein kleines Problem, wenn Sie GCC / mingw mit msvcrt mischen (und ohne das erweiterte printf von mingw zu verwenden).
Jørgensen


17

C ++ 11

C ++ 11 importiert C99 und std::printfsollte daher den C99- %zuFormatbezeichner unterstützen.

C ++ 98

Auf den meisten Plattformen size_tund uintptr_tgleichwertig sind, in diesem Fall können Sie die verwenden können , PRIuPTRMakro definiert in <cinttypes>:

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

Wenn Sie wirklich sicher sein wollen, werfen Sie auf uintmax_tund verwenden Sie PRIuMAX:

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

16

Unter Windows und der Visual Studio-Implementierung von printf

 %Iu

funktioniert bei mir. siehe msdn


Vielen Dank. Funktioniert auch in VS 2008. Denken Sie auch daran, dass man verwenden kann %Id, %Ixund %IXauch.
c00000fd

11

Warum nicht IOStreams verwenden, da Sie C ++ verwenden? Das sollte ohne Warnungen kompiliert werden und die richtige typbewusste Aktion ausführen, solange Sie keine hirntote C ++ - Implementierung verwenden, die kein operator <<for definiertsize_t .

Wenn die eigentliche Ausgabe erledigt werden muss printf(), können Sie sie dennoch mit IOStreams kombinieren, um ein typsicheres Verhalten zu erzielen:

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

Es ist nicht sehr effizient, aber Ihr obiger Fall befasst sich mit Datei-E / A. Das ist also Ihr Engpass, nicht dieser Code zur Formatierung von Zeichenfolgen.


Ich weiß, dass Google die Verwendung von Cout in ihrem Code verbietet. Vielleicht arbeitet Justin L. unter einer solchen Einschränkung.

In meinem Fall (siehe oben bearbeiten) könnte eine interessante Idee darin bestehen, die Funktion warn () in Bezug auf cout zu implementieren. Dies würde jedoch das manuelle Parsen von Formatzeichenfolgen beinhalten, was ... schwierig ist. :)
Justin L.

Ihre letzte Bearbeitung ist eigentlich das Gegenteil von dem, von dem ich denke, dass es für mich funktioniert. Ich möchte nicht den gesamten Code neu schreiben, der einen printf-Wrapper aufruft, aber es würde mir nichts ausmachen, die Implementierung des printf-Wrappers neu zu schreiben, um cout zu verwenden. Aber ich glaube nicht, dass das passieren wird. :)
Justin L.

Verwenden Sie std::stringstreamanstelle von IO-Streams.
Thomas Eding

1
Streams haben eine unangenehme Notation. Vergleichen: printf("x=%i, y=%i;\n", x, y);vs cout << "x=" << x << ", y=" << y << ";" << std::endl;.
Wunder. Mäuse

7

Hier ist eine mögliche Lösung, aber es ist keine schöne.

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
Nun ... das erreicht mein Ziel der Sicherheit und keine Warnungen. Aber ... ja. Ich werde die Warnungen nehmen, wenn ich das tun muss. :)
Justin L.

1
Nicht hübsch?! Kommt auf den Geschmack an. Es ermöglicht die vollständige portable Verwendung von printf mit Tieren wie uintptr_t und anderen. Toll!
Slava

@ user877329 Sie könnten diese Formatzeichenfolge als std :: string erstellen und dann GetPrintfID <size_t> :: id an der Stelle anhängen, an der Sie sie benötigen
stijn

@stijn Mit anderen Worten: Keine Verkettung zur Kompilierungszeit verfügbar
user877329

@ user877329 nein (es sei denn, Sie verwenden Makros oder ich vermisse etwas). Aber warum sollte das eine schwierige Anforderung sein?
Stijn

4

Die fmt - Bibliothek bietet eine schnelle portable (und sicher) Umsetzung printfeinschließlich der zModifikator für size_t:

#include "fmt/printf.h"

size_t a = 42;

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

Darüber hinaus unterstützt es die Python-ähnliche Format-String-Syntax und erfasst Typinformationen, sodass Sie diese nicht manuell bereitstellen müssen:

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

Es wurde mit großen Compilern getestet und bietet plattformübergreifende konsistente Ausgabe.

Haftungsausschluss : Ich bin der Autor dieser Bibliothek.


3

Der effektive Typ, der size_t zugrunde liegt, ist implementierungsabhängig . C Standard definiert es als den Typ, der vom Operator sizeof zurückgegeben wird. Abgesehen davon, dass es nicht signiert ist und eine Art integraler Typ ist, kann size_t so ziemlich alles sein, welche Größe den größten Wert aufnehmen kann, der von sizeof () erwartet wird.

Folglich kann die für eine size_t zu verwendende Formatzeichenfolge je nach Server variieren. Es sollte immer das "u" haben, kann aber l oder d oder vielleicht etwas anderes sein ...

Ein Trick könnte darin bestehen, ihn in den größten integralen Typ auf dem Computer umzuwandeln, um sicherzustellen, dass bei der Konvertierung kein Verlust auftritt, und dann die diesem bekannten Typ zugeordnete Formatzeichenfolge zu verwenden.


Ich wäre cool, wenn ich mein size_ts auf den größten integralen Typ auf der Maschine gießen und die diesem Typ zugeordnete Formatzeichenfolge verwenden würde. Meine Frage lautet: Gibt es eine Möglichkeit, dies zu tun, während sauberer Code beibehalten wird (Warnungen nur für legitime Fehler im String-Format im Printf-Format, keine hässlichen Casts usw.)? Ich könnte einen Wrapper schreiben, der die Formatzeichenfolge ändert, aber dann kann GCC mich nicht warnen, wenn ich meine Formatzeichenfolge rechtmäßig durcheinander gebracht habe.
Justin L.

Verwenden Sie CPP-Makros, um die Größe der Typen zu testen. Wählen Sie die übereinstimmende und geben Sie die Formatzeichenfolge an, die zum übereinstimmenden Typ passt.
Klarer

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

Später im Code:

my::printf("test ", 1, '\t', 2.0);

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.