Comment imprimer une variable size_t de manière portable en utilisant la famille printf?

405

J'ai une variable de type size_tet je veux l'imprimer en utilisant printf(). Quel spécificateur de format dois-je utiliser pour l'imprimer de manière portable?

Dans une machine 32 bits, %usemble juste. J'ai compilé avec g++ -g -W -Wall -Werror -ansi -pedantic, et il n'y a eu aucun avertissement. Mais quand je compile ce code sur une machine 64 bits, il produit un avertissement.

size_t x = <something>;
printf("size = %u\n", x);

warning: format '%u' expects type 'unsigned int', 
    but argument 2 has type 'long unsigned int'

L'avertissement disparaît, comme prévu, si je change cela en %lu.

La question est, comment puis-je écrire le code, afin qu'il compile sans avertissement sur les machines 32 et 64 bits?

Edit: Comme solution de contournement, je suppose qu'une réponse pourrait être de "convertir" la variable en un entier suffisamment grand, par exemple unsigned long, et d'imprimer à l'aide %lu. Cela fonctionnerait dans les deux cas. Je cherche s'il y a une autre idée.

Arun
la source
4
la conversion en unsigned longest la meilleure option si votre implémentation libc ne prend pas en charge le zmodificateur; la norme C99 recommande de size_tne pas avoir un rang de conversion entier supérieur à long, donc vous êtes raisonnablement en sécurité
Christoph
1
Sur la plate-forme Windows, size_t peut être plus long que long. Pour des raisons de compatibilité, long est toujours 32 bits mais size_t peut être 64 bits. Ainsi, la conversion en long non signé peut perdre la moitié des bits. Désolé :-)
Bruce Dawson
2
Copie
phuclv

Réponses:

484

Utilisez le zmodificateur:

size_t x = ...;
ssize_t y = ...;
printf("%zu\n", x);  // prints as unsigned decimal
printf("%zx\n", x);  // prints as hex
printf("%zd\n", y);  // prints as signed decimal
Adam Rosenfield
la source
7
+1. S'agit-il d'un ajout C99 ou cela s'applique-t-il également au C ++ (je n'ai pas le C90 à portée de main)?
avakar
6
c'est un ajout C99 et ne figure pas dans la liste des printf()modificateurs de longueur du brouillon C ++ 0x du 2009-11-09 (tableau 84 à la page 672)
Christoph
3
@Christoph: Ce n'est pas non plus dans le dernier projet, n3035.
GManNickG
11
@avakar @Adam Rosenfield @Christoph @GMan: Cependant, dans n3035 §1.2 Références normatives, seule la norme C99 est référencée, et §17.6.1.2 / 3 du même état "Les installations de la bibliothèque de normes C sont fournies." J'interpréterais cela comme signifiant que, sauf indication contraire, tout dans la bibliothèque standard C99 fait partie de la bibliothèque standard C ++ 0x, y compris les spécificateurs de format supplémentaires dans C99.
James McNellis
9
@ArunSaha: C'est une fonctionnalité de C99 uniquement, pas de C ++. Si vous voulez qu'il compile avec -pedantic, vous devrez soit obtenir un compilateur prenant en charge le brouillon C ++ 1x (très peu probable), soit vous devrez déplacer votre code dans un fichier compilé en C99. Sinon, votre seule option est de convertir vos variables unsigned long longet de les utiliser %llupour être portable au maximum.
Adam Rosenfield
88

Il semble que cela varie en fonction du compilateur que vous utilisez (blech):

... et bien sûr, si vous utilisez C ++, vous pouvez utiliser à la coutplace comme suggéré par AraK .

TJ Crowder
la source
3
zest également supporté par newlib (ie cygwin)
Christoph
7
%zdest incorrect pour size_t; il est correct pour le type signé correspondant à size_t, mais size_tlui-même est un type non signé.
Keith Thompson
1
@KeithThompson: J'ai également mentionné %zu(et %zxau cas où ils voudraient de l'hexagone). C'est vrai, cela %zuaurait probablement dû être le premier dans la liste. Fixé.
TJ Crowder
10
@TJCrowder: Je ne pense pas que cela %zddevrait être du tout dans la liste. Je ne vois aucune raison d'utiliser %zdplutôt que %zud'imprimer une size_tvaleur. Ce n'est même pas valide (a un comportement indéfini) si la valeur dépasse SIZE_MAX / 2. (Pour être complet, vous pourriez mentionner %zopour octal.)
Keith Thompson
2
@FUZxxl: POSIX ne nécessite pas que ssize_tle type signé corresponde à size_t, il n'est donc pas garanti de correspondre "%zd". (Il se trouve probablement sur la plupart des implémentations.) Pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Keith Thompson
59

Pour C89, utilisez %luet convertissez la valeur en unsigned long:

size_t foo;
...
printf("foo = %lu\n", (unsigned long) foo);

Pour C99 et versions ultérieures, utilisez %zu:

size_t foo;
...
printf("foo = %zu\n", foo);
John Bode
la source
7
Pour 2013, proposez "Pour C99 et suivants" et "Pour pré C99:". Meilleure réponse.
chux
8
Ne faites pas cela. Il échouera sur Windows 64 bits où size_t est 64 bits et long est 32 bits.
Yttrill
1
@Yttrill: Quelle est donc la réponse pour les fenêtres 64 bits?
John Bode
1
@JohnBode Peut unsigned long long- être ?
James Ko
2
Ou: vous pouvez convertir en a uint64_tpuis utiliser la PRIu64macro de inttypes.h, qui contient le spécificateur de format.
James Ko
9

Prolongeant la réponse d'Adam Rosenfield pour Windows.

J'ai testé ce code avec à la fois sur VS2013 Update 4 et VS2015 preview:

// test.c

#include <stdio.h>
#include <BaseTsd.h> // see the note below

int main()
{
    size_t x = 1;
    SSIZE_T y = 2;
    printf("%zu\n", x);  // prints as unsigned decimal
    printf("%zx\n", x);  // prints as hex
    printf("%zd\n", y);  // prints as signed decimal
    return 0;
}

Sorties binaires générées par VS2015:

1
1
2

tandis que celui généré par VS2013 dit:

zu
zx
zd

Remarque: ssize_test une extension POSIX et SSIZE_Test une chose similaire dans les types de données Windows , d'où j'ai ajouté une <BaseTsd.h>référence.

De plus, à l'exception des en-têtes C99 / C11 suivants, tous les en-têtes C99 sont disponibles dans l'aperçu VS2015:

C11 - <stdalign.h>
C11 - <stdatomic.h>
C11 - <stdnoreturn.h>
C99 - <tgmath.h>
C11 - <threads.h>

De plus, les C11 <uchar.h>sont désormais inclus dans le dernier aperçu.

Pour plus de détails, consultez cette ancienne et la nouvelle liste pour la conformité standard.

corbeau vulcan
la source
VS2013 Update 5 produit les mêmes résultats que la mise à jour 4 vous a donné.
Nathan Kidd
6

Pour ceux qui parlent de faire cela en C ++ qui ne prend pas nécessairement en charge les extensions C99, je recommande vivement boost :: format. Cela rend la question de taille de type size_t théorique:

std::cout << boost::format("Sizeof(Var) is %d\n") % sizeof(Var);

Comme vous n'avez pas besoin de spécificateurs de taille au format boost ::, vous pouvez simplement vous soucier de la façon dont vous souhaitez afficher la valeur.

swestrup
la source
4
Je veux probablement %ualors.
GManNickG
5
std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!
AraK
la source
8
Oui, mais le questionneur demande spécifiquement un printfspécificateur. Je suppose qu'ils ont d'autres contraintes non déclarées qui rendent l'utilisation d' std::coutun problème.
Donal Fellows
1
@Donal Je me demande quel genre de problème les flux C ++ pourraient-ils créer dans un projet C ++!
AraK
9
@AraK. Ils sont très lents? Ils ajoutent BEAUCOUP d'octets pour peu de raisons. ArunSaha veut juste savoir pour ses connaissances personnelles? Préférence personnelle (je préfère stdio à moi-même). Il y a plusieurs raisons.
KitsuneYMG
1
@TKCrowder: Eh bien, la demande d'origine indiquait qu'une solution C était souhaitée (via le balisage) et il y a de bonnes raisons de ne pas utiliser de flux en C ++, par exemple, si le descripteur de format de sortie est extrait d'un catalogue de messages. (Vous pouvez écrire un analyseur pour les messages et utiliser des flux si vous le souhaitez, mais c'est beaucoup de travail lorsque vous pouvez simplement exploiter le code existant.)
Donal Fellows
1
@Donal: Les balises étaient C et C ++. Je ne suis pas en aucune façon préconiser des trucs de flux E / S de C ++ (je ne suis pas fan de celui - ci), tout en soulignant que la question n'a pas à l' origine * « ... demander la spécification d'un printfprescripteur. »
TJ Crowder
5
printf("size = %zu\n", sizeof(thing) );
nategoose
la source
2

Comme l'a dit AraK, l'interface des flux c ++ fonctionnera toujours de manière portable.

std :: size_t s = 1024; std :: cout << s; // ou tout autre type de flux comme stringstream!

Si vous voulez C stdio, il n'y a pas de réponse portable à cela pour certains cas de "portable". Et cela devient moche car, comme vous l'avez vu, choisir les mauvais indicateurs de format peut générer un avertissement du compilateur ou donner une sortie incorrecte.

C99 a essayé de résoudre ce problème avec des formats inttypes.h comme "%" PRIdMAX "\ n". Mais tout comme avec "% zu", tout le monde ne prend pas en charge c99 (comme MSVS avant 2013). Il y a des fichiers "msinttypes.h" qui flottent pour gérer cela.

Si vous effectuez un cast vers un type différent, selon les indicateurs, vous pouvez obtenir un avertissement du compilateur pour la troncature ou un changement de signe. Si vous suivez cette route, choisissez un type de taille fixe pertinent plus grand. L'un des longs non signés et "% llu" ou des longs "% lu" non signés devrait fonctionner, mais llu peut également ralentir les choses dans un monde 32 bits comme excessivement grand. (Modifier - mon mac émet un avertissement en 64 bits pour% llu ne correspondant pas à size_t, même si% lu,% llu et size_t sont tous de la même taille. Et% lu et% llu ne sont pas de la même taille sur mon MSVS2012. Donc vous devrez peut-être caster + utiliser un format qui correspond.)

Pour cette question, vous pouvez aller avec des types de taille fixe, tels que int64_t. Mais attendez! Maintenant, nous sommes de retour à c99 / c ++ 11, et les anciens MSVS échouent à nouveau. De plus, vous avez également des moulages (par exemple map.size () n'est pas un type de taille fixe)!

Vous pouvez utiliser un en-tête ou une bibliothèque tiers tel que boost. Si vous n'en utilisez pas déjà un, vous ne voudrez peut-être pas gonfler votre projet de cette façon. Si vous êtes prêt à en ajouter un juste pour ce problème, pourquoi ne pas utiliser des flux c ++ ou une compilation conditionnelle?

Donc, vous en êtes aux flux c ++, à la compilation conditionnelle, aux frameworks tiers ou à quelque chose de portable qui vous convient.

Rick Berge
la source
-1

Vous avertira-t-il si vous passez un entier non signé 32 bits au format% lu? Cela devrait être bien car la conversion est bien définie et ne perd aucune information.

J'ai entendu dire que certaines plates-formes définissent des macros en ce <inttypes.h>que vous pouvez insérer dans le littéral de chaîne de format, mais je ne vois pas cet en-tête sur mon compilateur Windows C ++, ce qui implique qu'il ne peut pas être multiplateforme.

Kylotan
la source
1
La plupart des compilateurs ne vous avertiront pas si vous passez quelque chose de mauvaise taille dans printf. GCC est une exception. inttypes.h a été défini en C99, donc tout compilateur C qui est conforme à C99 l'aura, ce qui devrait être tous maintenant. Néanmoins, vous devrez peut-être activer C99 avec un indicateur de compilation. Dans tous les cas, intttypes.h ne définit pas de format spécifique pour size_t ou ptrdiff_t, car ils ont été jugés suffisamment importants pour obtenir leurs propres spécificateurs de taille de 'z' et 't' respectivement.
swestrup
Si vous utilisez %lu, vous devez convertir la size_tvaleur en unsigned long. Il n'y a pas de conversion implicite (autre que les promotions) pour les arguments vers printf.
Keith Thompson
-2

C99 définit "% zd" etc. pour cela. (Merci aux commentateurs) Il n'y a pas de spécificateur de format portable pour cela en C ++ - vous pouvez utiliser %pce mot qui dans les deux scénarios, mais ce n'est pas un choix portable non plus, et donne la valeur en hexadécimal.

Alternativement, utilisez un streaming (par exemple stringstream) ou un remplacement de printf sûr tel que Boost Format . Je comprends que ce conseil n'est que d'une utilité limitée (et nécessite C ++). (Nous avons utilisé une approche similaire adaptée à nos besoins lors de l'implémentation de la prise en charge unicode.)

Le problème fondamental pour C est que printf utilisant des points de suspension est dangereux par conception - il doit déterminer la taille de l'argument supplémentaire à partir des arguments connus, il ne peut donc pas être corrigé pour prendre en charge "tout ce que vous avez". Donc, à moins que votre compilateur implémente des extensions propriétaires, vous n'avez pas de chance.

Peterchen
la source
2
le zmodidfier de taille est C standard, mais certaines implémentations de libc sont bloquées en 1990 pour diverses raisons (par exemple, Microsoft a essentiellement abandonné C en faveur de C ++ et - plus récemment - C #)
Christoph
3
C99 a défini le spécificateur de taille «z» comme la taille d'une valeur size_t et «t» comme la taille d'une valeur ptrdiff_t.
swestrup
2
%zdest faux, il n'est pas signé donc il devrait l'être %zu.
David Conrad
-3

Sur certaines plates-formes et pour certains types, des spécificateurs de conversion printf spécifiques sont disponibles, mais il faut parfois recourir à la conversion vers des types plus grands.

J'ai documenté ce problème délicat ici, avec un exemple de code: http://www.pixelbeat.org/programming/gcc/int_types/ et le mettre à jour périodiquement avec des informations sur de nouvelles plates-formes et types.

pixelbeat
la source
1
Notez que les réponses de lien uniquement sont déconseillées, les réponses SO devraient être le point final d'une recherche de solution (par rapport à une autre escale de références, qui ont tendance à devenir obsolète au fil du temps). Veuillez envisager d'ajouter un synopsis autonome ici, en conservant le lien comme référence.
kleopatra
-5

si vous souhaitez imprimer la valeur d'un size_t sous forme de chaîne, vous pouvez le faire:

char text[] = "Lets go fishing in stead of sitting on our but !!";
size_t line = 2337200120702199116;

/* on windows I64x or I64d others %lld or %llx if it works %zd or %zx */
printf("number: %I64d\n",*(size_t*)&text);
printf("text: %s\n",*(char(*)[])&line);

le résultat est:

numéro: 2337200120702199116

texte: Allons pêcher au lieu de s'asseoir sur notre mais !!

Edit: relisant la question en raison des votes négatifs, j'ai remarqué que son problème n'est pas% llu ou% I64d mais le type size_t sur différentes machines voir cette question https://stackoverflow.com/a/918909/1755797
http: // www. cplusplus.com/reference/cstdio/printf/

size_t est entier non signé sur une machine 32 bits et entier long long non signé sur 64 bits
mais% ll attend toujours un entier long long non signé.

size_t varie en longueur sur différents systèmes d'exploitation tandis que% llu est le même

André
la source
4
Quelle absurdité est-ce?!
Antti Haapala
convertir les 8 premiers octets du tableau de caractères en un 64 bits long et long non signé via le pointeur size_t et les imprimer sous forme de nombre avec le printf% I64d n'est pas vraiment spectaculaire, je sais, bien sûr, je n'ai pas de code pour empêcher le débordement de type mais ce n'est pas dans la portée de la question.
Andre