Pulisci il codice per printf size_t in C ++ (o: equivalente più vicino a% z di C99 in C ++)


96

Ho del codice C ++ che stampa un size_t:

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

Mi piacerebbe che questo si compilasse senza avvisi su entrambe le architetture a 32 e 64 bit.

Se questo fosse C99, potrei usare printf("%z", a);. Ma AFAICT %znon esiste in nessun dialetto C ++ standard. Quindi, invece, devo fare

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

che è davvero brutto.

Se non ci sono funzionalità per la stampa di size_ts incorporate nel linguaggio, mi chiedo se sia possibile scrivere un wrapper printf o qualcosa di simile che inserirà i cast appropriati su size_ts in modo da eliminare gli avvisi del compilatore spuri pur mantenendo quelli buoni.

Qualche idea?


Modifica Per chiarire perché sto usando printf: ho una base di codice relativamente grande che sto ripulendo. Usa i wrapper printf per fare cose come "scrivere un avviso, registrarlo in un file e possibilmente uscire dal codice con un errore". Potrei essere in grado di raccogliere abbastanza C ++ - pippo per farlo con un wrapper cout, ma preferirei non cambiare ogni chiamata warn () nel programma solo per eliminare alcuni avvertimenti del compilatore.


4
Perché stai usando printf dovrebbe essere la domanda.
Ed S.

il tuo compilatore controlla la stringa printf e controlla il tipo?
Pod

Il mio compilatore controlla effettivamente la stringa del formato printf e digita il controllo per me. Vorrei mantenere attiva questa funzione.
Justin L.

2
% zu, z è uno specificatore di larghezza non un identificatore di tipo. Funziona per c printf che puoi usare senza problemi da C ++. L'ho commentato di seguito, quindi votatelo;)
Il

Se stai usando Visual Studio, non puoi semplicemente usarlo "%l"? Non sarà sempre della taglia giusta? O la portabilità è importante?
Mooing Duck

Risposte:


61

La maggior parte dei compilatori ha i propri specificatori size_te ptrdiff_targomenti, Visual C ++ ad esempio usa rispettivamente% Iu e% Id, penso che gcc ti permetterà di usare% zu e% zd.

Potresti creare una macro:

#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

Utilizzo:

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

5
Non è così facile. Che %zsia supportato o meno dipende dal runtime, non dal compilatore. L'uso __GNUC__è quindi un po 'un problema, se mescoli GCC / mingw con msvcrt (e senza usare printf aumentato di mingw).
jørgensen


17

C ++ 11

C ++ 11 importa C99 quindi std::printfdovrebbe supportare l'identificatore di %zuformato C99 .

C ++ 98

Sulla maggior parte delle piattaforme size_te uintptr_tsono equivalenti, nel qual caso è possibile utilizzare la PRIuPTRmacro definita in <cinttypes>:

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

Se vuoi davvero essere al sicuro, trasmetti uintmax_te usa PRIuMAX:

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

16

In Windows e nell'implementazione di Visual Studio di printf

 %Iu

per me va bene. vedi msdn


Grazie. Funziona VS 2008anche dentro . Anche tenere a mente che si può usare %Id, %Ixe %IXanche.
c00000fd

11

Dato che stai usando C ++, perché non usare IOStreams? Dovrebbe compilarsi senza avvertimenti e fare la cosa giusta in grado di riconoscere i tipi, purché non si utilizzi un'implementazione C ++ cerebrale che non definisce un file operator <<for size_t.

Quando l'output effettivo deve essere eseguito printf(), puoi comunque combinarlo con IOStreams per ottenere un comportamento indipendente dai tipi:

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

Non è super efficiente, ma il tuo caso sopra riguarda l'I / O di file, quindi questo è il tuo collo di bottiglia, non questo codice di formattazione della stringa.


So che Google vieta l'uso di cout nel loro codice. Forse Justin L. sta lavorando con una tale restrizione.

Nel mio caso (vedi modifica sopra), un'idea interessante potrebbe essere quella di provare a implementare la funzione warn () in termini di cout. Ma ciò comporterebbe l'analisi manuale delle stringhe di formato, il che è ... complicato. :)
Justin L.

La tua ultima modifica è in realtà l'opposto di quello che penso potrebbe funzionare per me. Non voglio riscrivere tutto il codice che invoca un wrapper printf, ma non mi dispiacerebbe riscrivere l'implementazione del wrapper printf per usare cout. Ma non credo che succederà. :)
Justin L.

Utilizzare std::stringstreaminvece di flussi di I / O.
Thomas Eding

1
I flussi hanno una notazione scomoda. Confronta: printf("x=%i, y=%i;\n", x, y);vs cout << "x=" << x << ", y=" << y << ";" << std::endl;.
wonder.mice

7

ecco una possibile soluzione, ma non è proprio carina ..

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
Bene ... questo raggiunge il mio obiettivo di sicurezza e nessun avvertimento. Ma ... sì. Prenderò gli avvertimenti se è quello che devo fare. :)
Justin L.

1
Non carino?! Dipende dal gusto. Permette l'uso completamente portabile di printf con bestie come uintptr_t e simili. Grande!
Slava

@ user877329 puoi creare quella stringa di formato come std :: string e quindi aggiungere GetPrintfID <size_t> :: id nel punto in cui ti serve
stijn

@stijn In altre parole: nessuna concatenazione in fase di compilazione disponibile
user877329

@ user877329 no (a meno che non si utilizzino le macro o mi manchi qualcosa). Ma perché dovrebbe essere un requisito difficile?
stijn

4

La libreria fmt fornisce un'implementazione veloce e portabile (e sicura) che printfinclude il zmodificatore per size_t:

#include "fmt/printf.h"

size_t a = 42;

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

In aggiunta a ciò, supporta la sintassi della stringa di formato simile a Python e acquisisce le informazioni sul tipo in modo che non sia necessario fornirle manualmente:

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

È stato testato con i principali compilatori e fornisce un output coerente su tutte le piattaforme.

Disclaimer : sono l'autore di questa libreria.


3

Il tipo effettivo sottostante size_t dipende dall'implementazione . Lo standard C lo definisce come il tipo restituito dall'operatore sizeof; oltre ad essere senza segno e una sorta di tipo integrale, size_t può essere praticamente qualsiasi cosa la cui dimensione possa contenere il valore più grande che ci si aspetta venga restituito da sizeof ().

Di conseguenza, la stringa di formato da utilizzare per size_t può variare a seconda del server. Dovrebbe sempre avere la "u", ma potrebbe essere l o d o forse qualcos'altro ...

Un trucco potrebbe essere quello di eseguire il cast sul tipo integrale più grande sulla macchina, assicurando che non vi siano perdite nella conversione, e quindi utilizzare la stringa di formato associata a questo tipo noto.


Sarebbe fantastico trasmettere i miei messaggi size_tal tipo integrale più grande sulla macchina e utilizzare la stringa di formato associata a questo tipo. La mia domanda è: esiste un modo per farlo mantenendo un codice pulito (avvisi solo per errori di stringa di formato printf legittimi, nessun cast brutto, ecc.)? Potrei scrivere un wrapper che cambia la stringa di formato, ma poi GCC non sarebbe in grado di darmi avvisi quando ho legittimamente incasinato la mia stringa di formato.
Justin L.

Utilizza le macro CPP per verificare la dimensione dei tipi; scegli quello che corrisponde e specifica la stringa di formato che va con il tipo di corrispondenza.
Più sereno

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

Più avanti nel codice:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.