¿Cómo se puede imprimir una variable size_t de forma portátil utilizando la familia printf?

405

Tengo una variable de tipo size_ty quiero imprimirla usando printf(). ¿Qué especificador de formato utilizo para imprimirlo de forma portátil?

En la máquina de 32 bits, %uparece correcto. Compilé con g++ -g -W -Wall -Werror -ansi -pedantic, y no hubo advertencia. Pero cuando compilo ese código en una máquina de 64 bits, produce una advertencia.

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

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

La advertencia desaparece, como se esperaba, si cambio eso a %lu.

La pregunta es, ¿cómo puedo escribir el código, para que compile sin advertencia en máquinas de 32 y 64 bits?

Editar: Como solución alternativa, supongo que una respuesta podría ser "convertir" la variable en un número entero que sea lo suficientemente grande, digamos unsigned long, e imprimir usando %lu. Eso funcionaría en ambos casos. Estoy buscando si hay alguna otra idea.

Arun
fuente
44
enviar a unsigned longes la mejor opción si su implementación de libc no admite el zmodificador; el estándar C99 recomienda size_tno tener un rango de conversión de enteros mayor que long, por lo que está razonablemente seguro
Christoph
1
En la plataforma Windows, size_t puede ser más grande que largo. Por razones de compatibilidad, long siempre es de 32 bits, pero size_t puede ser de 64 bits. Por lo tanto, la conversión a tiempo sin firmar puede perder la mitad de los bits. Lo sentimos :-)
Bruce Dawson

Respuestas:

483

Usa el zmodificador:

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
fuente
77
+1. ¿Es esta una adición de C99 o esto también se aplica a C ++ (no tengo C90 a mano)?
avakar
66
es una adición de C99 y no figura en la lista de printf()modificadores de longitud del borrador C ++ 0x del 2009-11-09 (tabla 84 en la página 672)
Christoph
3
@ Christoph: Tampoco está en el último borrador, n3035.
GManNickG
11
@avakar @Adam Rosenfield @Christoph @GMan: Sin embargo, en n3035 §1.2 Referencias normativas, solo se hace referencia al estándar C99, y §17.6.1.2 / 3 de los mismos estados "Se proporcionan las instalaciones de la biblioteca del estándar C". Interpretaría que esto significa que, a menos que se especifique lo contrario, todo en la biblioteca estándar C99 es parte de la biblioteca estándar C ++ 0x, incluidos los especificadores de formato adicionales en C99.
James McNellis
99
@ArunSaha: es una característica de solo C99, no de C ++. Si desea que se compile -pedantic, deberá obtener un compilador que admita el borrador de C ++ 1x (muy poco probable), o deberá mover su código a un archivo compilado como C99. De lo contrario, su única opción es convertir sus variables unsigned long longy utilizarlas %llupara ser lo más portátil posible.
Adam Rosenfield
88

Parece que varía según el compilador que esté utilizando (blech):

... y, por supuesto, si está usando C ++, puede usarlo coutcomo lo sugiere AraK .

TJ Crowder
fuente
3
ztambién es compatible con newlib (es decir, cygwin)
Christoph
77
%zdes incorrecto para size_t; es correcto para el tipo con signo correspondiente a size_t, pero en size_tsí mismo es un tipo sin signo.
Keith Thompson
1
@KeithThompson: También mencioné %zu(y %zxen caso de que quieran hexadecimal). Es cierto que %zuprobablemente debería haber sido el primero en la lista. Fijo.
TJ Crowder
10
@TJCrowder: No creo que %zddeba estar en la lista en absoluto. No se me ocurre ninguna razón para usar en %zdlugar de %zuimprimir un size_tvalor. Ni siquiera es válido (tiene un comportamiento indefinido) si el valor excede SIZE_MAX / 2. (Para completar, puede mencionar %zopara octal.)
Keith Thompson
2
@FUZxxl: POSIX no requiere que ssize_tsea ​​el tipo firmado correspondiente size_t, por lo que no se garantiza que coincida "%zd". ( Probablemente esté en la mayoría de las implementaciones). Pubs.opengroup.org/onlinepubs/9699919799/basedefs/…
Keith Thompson
59

Para C89, use %luy emita el valor a unsigned long:

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

Para C99 y posterior, use %zu:

size_t foo;
...
printf("foo = %zu\n", foo);
John Bode
fuente
77
Considerando 2013, sugiera "Para C99 y posteriores" y "Para pre C99:". La mejor respuesta.
chux - Restablece a Mónica el
8
No hagas esto. Fallará en Windows de 64 bits donde size_t es de 64 bits y el largo es de 32 bits.
Yttrill
1
@Yttrill: ¿Cuál es la respuesta para las ventanas de 64 bits, entonces?
John Bode
1
@JohnBode unsigned long long¿ Quizás ?
James Ko
2
O bien: puede convertir a ay uint64_tluego usar la PRIu64macro de inttypes.h, que contiene el especificador de formato.
James Ko
9

Ampliando la respuesta de Adam Rosenfield para Windows.

Probé este código con VS2013 Update 4 y 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;
}

VS2015 generaron salidas binarias:

1
1
2

mientras que el generado por VS2013 dice:

zu
zx
zd

Nota: ssize_tes una extensión POSIX y SSIZE_Tes algo similar en los tipos de datos de Windows , por lo tanto, agregué <BaseTsd.h>referencia.

Además, a excepción de los siguientes encabezados C99 / C11, todos los encabezados C99 están disponibles en la vista previa de VS2015:

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

Además, C11 <uchar.h>ahora se incluye en la última vista previa.

Para obtener más detalles, consulte esta lista antigua y la nueva para conocer la conformidad estándar.

cuervo vulcano
fuente
La actualización 5 de VS2013 produce los mismos resultados que la actualización 4.
Nathan Kidd
6

Para aquellos que hablan de hacer esto en C ++, que no necesariamente admite las extensiones C99, les recomiendo encarecidamente el formato boost ::. Esto hace que la pregunta size_t type size sea discutible:

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

Como no necesita especificadores de tamaño en formato boost ::, puede preocuparse por cómo desea mostrar el valor.

swestrup
fuente
44
Probablemente quiera %uentonces.
GManNickG
5
std::size_t s = 1024;
std::cout << s; // or any other kind of stream like stringstream!
AraK
fuente
8
Sí, pero el interlocutor pregunta específicamente por un printfespecificador. Supongo que tienen algunas otras restricciones no establecidas que hacen que el uso de std::coutun problema.
Donal Fellows
1
@Donal ¡Me pregunto qué tipo de problema podrían crear las transmisiones de C ++ en un proyecto de C ++!
AraK
99
@AraK. Son muy lentos? Agregan MUCHOS bytes por muy poco motivo. ¿ArunSaha solo quiere saber por su propio conocimiento personal? Preferencia personal (prefiero stdio a fstream yo mismo). Hay muchas razones.
KitsuneYMG
1
@TKCrowder: Bueno, la solicitud original decía que se quería una solución C (a través del etiquetado) y hay buenas razones para no usar secuencias en C ++, por ejemplo, si el descriptor de formato de salida se extrae de un catálogo de mensajes. (Podrías escribir un analizador de mensajes y usar transmisiones si lo deseas, pero eso es mucho trabajo cuando puedes aprovechar el código existente).
Donal Fellows
1
@Donal: las etiquetas eran C y C ++. No estoy de ninguna manera abogando por el flujo de E / S de C ++ (no soy un fanático de él), solo señalo que la pregunta originalmente no * "... solicite especificación para un printfespecificador".
TJ Crowder
5
printf("size = %zu\n", sizeof(thing) );
nategoose
fuente
2

Como dijo AraK, la interfaz de transmisión de c ++ siempre funcionará de forma portátil.

std :: size_t s = 1024; std :: cout << s; // o cualquier otro tipo de flujo como stringstream!

Si desea C stdio, no hay una respuesta portátil a esto para ciertos casos de "portátil". Y se pone feo ya que, como has visto, elegir los indicadores de formato incorrectos puede generar una advertencia del compilador o dar una salida incorrecta.

C99 intentó resolver este problema con formatos inttypes.h como "%" PRIdMAX "\ n". Pero al igual que con "% zu", no todos admiten c99 (como MSVS antes de 2013). Hay archivos "msinttypes.h" flotando para lidiar con esto.

Si realiza una conversión a un tipo diferente, dependiendo de los indicadores, puede recibir una advertencia del compilador para el truncamiento o un cambio de signo. Si sigue esta ruta, elija un tipo de tamaño fijo relevante más grande. Uno sin signo largo largo y "% llu" o sin signo largo "% lu" debería funcionar, pero llu también puede ralentizar las cosas en un mundo de 32 bits como demasiado grande. (Editar: mi mac emite una advertencia en 64 bits para que% llu no coincida con size_t, aunque% lu,% llu y size_t son todos del mismo tamaño. Y% lu y% llu no son del mismo tamaño en mi MSVS2012. Entonces es posible que deba emitir + usar un formato que coincida).

Para el caso, puede ir con tipos de tamaño fijo, como int64_t. ¡Pero espera! Ahora volvemos a c99 / c ++ 11, y MSVS anterior falla nuevamente. ¡Además, también tiene moldes (por ejemplo, map.size () no es un tipo de tamaño fijo)!

Puede usar un encabezado o biblioteca de terceros, como boost. Si aún no está usando uno, es posible que no quiera inflar su proyecto de esa manera. Si está dispuesto a agregar uno solo para este problema, ¿por qué no usar transmisiones de c ++ o compilación condicional?

Por lo tanto, tiene que recurrir a transmisiones en c ++, compilación condicional, marcos de trabajo de terceros o algo portátil que funcione para usted.

Rick Berge
fuente
-1

¿Te avisará si pasas un entero sin signo de 32 bits a un formato% lu? Debería estar bien ya que la conversión está bien definida y no pierde ninguna información.

He oído que algunas plataformas definen macros en las <inttypes.h>que puede insertar en el literal de cadena de formato, pero no veo ese encabezado en mi compilador de Windows C ++, lo que implica que puede no ser multiplataforma.

Kylotan
fuente
1
La mayoría de los compiladores no le avisarán si pasa algo del tamaño incorrecto a printf. GCC es una excepción. inttypes.h se definió en C99, por lo que cualquier compilador de C que cumpla con C99 lo tendrá, que ya debería ser todo. Aún así, es posible que deba activar C99 con un indicador de compilación. En cualquier caso, intttypes.h no define un formato específico para size_t o ptrdiff_t, ya que se decidió que eran lo suficientemente importantes como para obtener sus propios especificadores de tamaño de 'z' y 't' respectivamente.
swestrup
Si usa %lu, debe convertir el size_tvalor a unsigned long. No hay conversión implícita (aparte de las promociones) para argumentos printf.
Keith Thompson
-2

C99 define "% zd", etc. para eso. (gracias a los comentaristas) No hay un especificador de formato portátil para eso en C ++: podría usar %p, lo que sería una palabra en estos dos escenarios, pero tampoco es una opción portátil, y da el valor en hexadecimal.

Alternativamente, use algo de transmisión (por ejemplo, secuencia de cadena) o un reemplazo seguro de printf como Boost Format . Entiendo que este consejo es de uso limitado (y requiere C ++). (Hemos utilizado un enfoque similar para nuestras necesidades al implementar el soporte Unicode).

El problema fundamental para C es que printf usando puntos suspensivos no es seguro por diseño: necesita determinar el tamaño del argumento adicional a partir de los argumentos conocidos, por lo que no se puede arreglar para admitir "lo que sea que tenga". Entonces, a menos que su compilador implemente algunas extensiones propietarias, no tiene suerte.

Peterchen
fuente
2
el zmodificador de tamaño es C estándar, pero algunas implementaciones de libc están estancadas en 1990 por varias razones (por ejemplo, Microsoft básicamente abandonó C en favor de C ++ y - más recientemente - C #)
Christoph
3
C99 definió el especificador de tamaño 'z' como el tamaño de un valor size_t, y 't' como el tamaño de un valor ptrdiff_t.
swestrup
2
%zdestá mal, no está firmado, por lo que debería estarlo %zu.
David Conrad
-3

En algunas plataformas y para algunos tipos, hay disponibles especificadores de conversión de printf específicos, pero a veces hay que recurrir a la conversión a tipos más grandes.

He documentado este problema complicado aquí, con un código de ejemplo: http://www.pixelbeat.org/programming/gcc/int_types/ y lo actualizo periódicamente con información sobre nuevas plataformas y tipos.

pixelbeat
fuente
1
Tenga en cuenta que se desaconsejan las respuestas de solo enlace, las respuestas SO deben ser el punto final de una búsqueda de una solución (frente a otra escala de referencias, que tienden a quedarse obsoletas con el tiempo). Considere agregar una sinopsis independiente aquí, manteniendo el enlace como referencia.
kleopatra
-5

si desea imprimir el valor de un size_t como una cadena, puede hacer esto:

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

el resultado es:

número: 2337200120702199116

texto: ¡Vamos a pescar en lugar de sentarnos en nuestro pero!

Editar: releyendo la pregunta debido a los votos negativos, noté que su problema no es% llu o% I64d, pero el tipo size_t en diferentes máquinas ve esta pregunta https://stackoverflow.com/a/918909/1755797
http: // www. cplusplus.com/reference/cstdio/printf/

size_t es unsigned int en una máquina de 32 bits y unsigned long long int en 64bit
pero% ll siempre espera un unsigned long long int.

size_t varía en longitud en diferentes sistemas operativos, mientras que% llu es el mismo

Andre
fuente
44
¿Qué tontería es esta?
Antti Haapala
lanzar los primeros 8 bytes de la matriz de caracteres a un 64bit largo largo sin signo a través del puntero size_t e imprimirlos como número con printf% I64d no es realmente espectacular, lo sé, por supuesto que no lo hice en el código para evitar el desbordamiento de tipo, pero eso no está en el alcance de la pregunta.
Andre