Nettoyer le code pour printf size_t en C ++ (ou: équivalent le plus proche de% z de C99 en C ++)


96

J'ai du code C ++ qui imprime un size_t:

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

J'aimerais que cela compile sans avertissements sur les architectures 32 et 64 bits.

Si c'était C99, je pourrais utiliser printf("%z", a);. Mais AFAICT %zn'existe dans aucun dialecte C ++ standard. Alors à la place, je dois faire

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

ce qui est vraiment moche.

S'il n'y a pas de fonction d'impression size_tintégrée dans le langage, je me demande s'il est possible d'écrire un wrapper printf ou quelque chose de ce genre qui insérera les moulages appropriés sursize_t s afin d'éliminer les faux avertissements du compilateur tout en conservant les bons.

Des idées?


Edit Pour clarifier pourquoi j'utilise printf: j'ai une base de code relativement grande que je nettoie. Il utilise des wrappers printf pour faire des choses comme "écrire un avertissement, le consigner dans un fichier et éventuellement quitter le code avec une erreur". Je pourrais peut-être rassembler suffisamment de C ++ - foo pour le faire avec un wrapper cout, mais je préfère ne pas changer chaque appel warn () dans le programme juste pour me débarrasser de certains avertissements du compilateur.


4
Pourquoi utilisez-vous printf du tout devrait être la question.
Ed S.

votre compilateur inspecte-t-il la chaîne printf et vérifie le type pour vous?
Pod

Mon compilateur inspecte en effet la chaîne de format printf et saisit-le pour moi. J'aimerais garder cette fonctionnalité activée.
Justin L.

2
% zu, z est un spécificateur de largeur et non un spécificateur de type. Cela fonctionne pour le c printf que vous pouvez utiliser de manière transparente à partir de C ++. Je l'ai commenté ci-dessous, alors votez pour;)
Will

Si vous utilisez Visual Studio, ne pouvez-vous pas simplement utiliser "%l"? Est-ce que ce ne sera pas toujours la bonne taille? Ou la portabilité est-elle importante?
Mooing Duck le

Réponses:


61

La plupart des compilateurs ont leur propre spécificateur size_tet ptrdiff_targuments, Visual C ++ par exemple utilise respectivement% Iu et% Id, je pense que gcc vous permettra d'utiliser% zu et% zd.

Vous pouvez créer une 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

Usage:

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

5
Ce n’est pas si simple. La %zprise en charge ou non dépend du runtime, pas du compilateur. L'utilisation __GNUC__est donc un peu problématique, si vous mélangez GCC / mingw avec msvcrt (et sans utiliser le printf augmenté de mingw).
jørgensen


17

C ++ 11

C ++ 11 importe C99 et std::printfdoit donc prendre en charge le %zuspécificateur de format C99 .

C ++ 98

Sur la plupart des plates-formes, size_tet uintptr_tsont équivalents, auquel cas vous pouvez utiliser la PRIuPTRmacro définie dans <cinttypes>:

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

Si vous voulez vraiment être en sécurité, lancez uintmax_tet utilisez PRIuMAX:

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

16

Sur Windows et l'implémentation Visual Studio de printf

 %Iu

travaille pour moi. voir msdn


Merci. Fonctionne VS 2008aussi. Gardez également à l'esprit que l'on peut utiliser %Id, %Ixet %IXaussi.
c00000fd

11

Puisque vous utilisez C ++, pourquoi ne pas utiliser IOStreams? Cela devrait compiler sans avertissement et faire la bonne chose sensible au type, tant que vous n'utilisez pas une implémentation C ++ insensée qui ne définit pas un operator <<for size_t.

Lorsque la sortie réelle doit être terminée printf(), vous pouvez toujours la combiner avec IOStreams pour obtenir un comportement de type sécurisé:

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

Ce n'est pas très efficace, mais votre cas ci-dessus concerne les E / S de fichier, c'est donc votre goulot d'étranglement, pas ce code de formatage de chaîne.


Je sais que Google interdit l'utilisation de cout dans leur code. Peut-être que Justin L. travaille sous une telle restriction.

Dans mon cas (voir la modification ci-dessus), une idée intéressante pourrait être d'essayer d'implémenter la fonction warn () en termes de cout. Mais cela impliquerait d'analyser manuellement les chaînes de format, ce qui est ... délicat. :)
Justin L.

Votre dernière modification est en fait le contraire de ce que je pense pourrait fonctionner pour moi. Je ne veux pas réécrire tout le code qui appelle un wrapper printf, mais cela ne me dérangerait pas de réécrire l'implémentation du wrapper printf pour utiliser cout. Mais je ne pense pas que cela arrivera. :)
Justin L.

Utilisez à la std::stringstreamplace des flux IO.
Thomas Eding

1
Les flux ont une notation maladroite. Comparez: printf("x=%i, y=%i;\n", x, y);vs cout << "x=" << x << ", y=" << y << ";" << std::endl;.
wonder.mice

7

voici une solution possible, mais ce n'est pas tout à fait jolie.

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
Eh bien ... cela atteint mon objectif de sécurité et pas d'avertissement. Mais ... ouais. Je prendrai les avertissements si c'est ce que je dois faire. :)
Justin L.

1
Pas beau?! Dépend du goût. Il permet une utilisation entièrement portable de printf avec des bêtes comme uintptr_t et autres. Génial!
Slava

@ user877329 vous pouvez créer cette chaîne de format en tant que std :: string, puis ajouter GetPrintfID <size_t> :: id à l'endroit où vous en avez besoin
stijn

@stijn En d'autres termes: pas de concaténation au moment de la compilation disponible
user877329

@ user877329 non (à moins d'utiliser des macros, ou il me manque quelque chose). Mais pourquoi serait-ce une exigence difficile?
stijn

4

La bibliothèque fmt fournit une implémentation portable (et sûre) rapide de l' printfinclusion du zmodificateur pour size_t:

#include "fmt/printf.h"

size_t a = 42;

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

En plus de cela, il prend en charge la syntaxe de chaîne de format de type Python et capture les informations de type afin que vous n'ayez pas à les fournir manuellement:

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

Il a été testé avec les principaux compilateurs et fournit une sortie cohérente sur toutes les plates-formes.

Avertissement : je suis l'auteur de cette bibliothèque.


3

Le type effectif sous-jacent size_t dépend de l'implémentation . C Standard le définit comme le type renvoyé par l'opérateur sizeof; mis à part le fait d'être non signé et une sorte de type intégral, size_t peut être à peu près tout ce qui peut accueillir la plus grande valeur attendue d'être retournée par sizeof ().

Par conséquent, la chaîne de format à utiliser pour size_t peut varier en fonction du serveur. Il devrait toujours avoir le "u", mais peut être l ou d ou peut-être autre chose ...

Une astuce pourrait être de le convertir en le plus grand type intégral sur la machine, en garantissant aucune perte dans la conversion, puis en utilisant la chaîne de format associée à ce type connu.


Je serais cool de lancer mes size_ts sur le plus grand type intégral de la machine et d'utiliser la chaîne de format associée à ce type. Ma question est la suivante: y a-t-il un moyen de le faire tout en conservant un code propre (avertissements uniquement pour les erreurs de chaîne de format printf légitimes, pas de casts laids, etc.)? Je pourrais écrire un wrapper qui change la chaîne de format, mais GCC ne serait pas en mesure de me donner des avertissements lorsque j'ai légitimement gâché ma chaîne de format.
Justin L.

Utilisez les macros CPP pour tester la taille des types; choisissez celui qui correspond et spécifiez la chaîne de format qui correspond au type correspondant.
Plus clair

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

Plus tard dans le code:

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.