¿Especificador de formato correcto para imprimir el puntero o la dirección?

194

¿Qué especificador de formato debo usar para imprimir la dirección de una variable? Estoy confundido entre el lote a continuación.

% u - entero sin signo

% x - valor hexadecimal

% p - puntero nulo

¿Cuál sería el formato óptimo para imprimir una dirección?

San
fuente

Respuestas:

240

La respuesta más simple, suponiendo que no le importen los caprichos y las variaciones de formato entre las diferentes plataformas, es la %pnotación estándar .

El estándar C99 (ISO / IEC 9899: 1999) dice en §7.19.6.1 ¶8:

pEl argumento será un puntero a void. El valor del puntero se convierte en una secuencia de caracteres de impresión, de una manera definida por la implementación.

(En C11 - ISO / IEC 9899: 2011 - la información se encuentra en §7.21.6.1 ¶8.)

En algunas plataformas, eso incluirá un inicio 0xy en otros no, y las letras podrían estar en minúsculas o mayúsculas, y el estándar C ni siquiera define que será una salida hexadecimal, aunque sé de Sin implementación donde no está.

Está algo abierto a debatir si debe convertir explícitamente los punteros con un (void *)elenco. Está siendo explícito, lo que generalmente es bueno (por lo que es lo que hago), y el estándar dice 'el argumento será un puntero a void'. En la mayoría de las máquinas, se saldría con la suya omitiendo un reparto explícito. Sin embargo, sería importante en una máquina donde la representación de bits de una char *dirección para una ubicación de memoria dada es diferente de la dirección de ' cualquier otra cosa ' para la misma ubicación de memoria. Esta sería una máquina con dirección de palabra, en lugar de con dirección de byte. Estas máquinas no son comunes (probablemente no disponibles) en estos días, pero la primera máquina en la que trabajé después de la universidad fue una de ellas (ICL Perq).

Si no está satisfecho con el comportamiento definido por la implementación de %p, use C99 <inttypes.h>y en su uintptr_tlugar:

printf("0x%" PRIXPTR "\n", (uintptr_t)your_pointer);

Esto le permite ajustar la representación a su gusto. Elegí tener los dígitos hexadecimales en mayúsculas para que el número sea uniformemente de la misma altura y el descenso característico al comienzo de 0xA1B2CDEFaparece de esta manera, no como el 0xa1b2cdefque sube y baja a lo largo del número también. Sin embargo, su elección, dentro de límites muy amplios. El (uintptr_t)reparto está inequívocamente recomendado por GCC cuando se puede leer el formato de cadena en tiempo de compilación. Creo que es correcto solicitar el reparto, aunque estoy seguro de que algunos ignorarían la advertencia y se saldrían con la suya la mayor parte del tiempo.


Kerrek pregunta en los comentarios:

Estoy un poco confundido sobre las promociones estándar y los argumentos variados. ¿Todos los punteros son promovidos a nulo *? De lo contrario, si int*fueran, digamos, dos bytes y void*fueran 4 bytes, entonces claramente sería un error leer cuatro bytes del argumento, ¿no?

Yo tenía la ilusión de que el estándar de C dice que todos los punteros a objetos deben ser del mismo tamaño, de modo void *y int *no pueden ser de diferentes tamaños. Sin embargo, lo que creo que es la sección relevante del estándar C99 no es tan enfático (aunque no sé de una implementación donde lo que sugerí que es verdadero es realmente falso):

§6.2.5 Tipos

¶26 Un puntero a anular tendrá los mismos requisitos de representación y alineación que un puntero a un tipo de carácter. 39) Del mismo modo, los punteros a versiones calificadas o no calificadas de tipos compatibles tendrán los mismos requisitos de representación y alineación. Todos los punteros a los tipos de estructura deben tener los mismos requisitos de representación y alineación que los demás. Todos los punteros a tipos de unión tendrán los mismos requisitos de representación y alineación que los demás. Los punteros a otros tipos no necesitan tener los mismos requisitos de representación o alineación.

39) Los mismos requisitos de representación y alineación están destinados a implicar intercambiabilidad como argumentos de funciones, valores de retorno de funciones y miembros de sindicatos.

(C11 dice exactamente lo mismo en la sección §6.2.5, ¶28 y la nota al pie 48.)

Por lo tanto, todos los punteros a las estructuras deben ser del mismo tamaño entre sí y deben compartir los mismos requisitos de alineación, aunque las estructuras a las que apuntan los punteros pueden tener requisitos de alineación diferentes. Del mismo modo para los sindicatos. Los punteros de caracteres y los punteros vacíos deben tener el mismo tamaño y requisitos de alineación. Los punteros a las variaciones de int(significado unsigned inty signed int) deben tener los mismos requisitos de tamaño y alineación entre sí; de manera similar para otros tipos. Pero el estándar C no dice eso formalmente sizeof(int *) == sizeof(void *). Oh, bueno, SO es bueno para hacerte inspeccionar tus suposiciones.

El estándar C definitivamente no requiere que los punteros de función sean del mismo tamaño que los punteros de objeto. Eso era necesario para no romper los diferentes modelos de memoria en sistemas tipo DOS. Allí podría tener punteros de datos de 16 bits pero punteros de función de 32 bits, o viceversa. Esta es la razón por la cual el estándar C no exige que los punteros de función se puedan convertir en punteros de objeto y viceversa.

Afortunadamente (para los programadores que apuntan a POSIX), POSIX entra en la brecha y exige que los punteros de función y los punteros de datos sean del mismo tamaño:

§2.12.3 Tipos de puntero

Todos los tipos de puntero de función tendrán la misma representación que el puntero de tipo a anular. La conversión de un puntero de función a void *no alterará la representación. Un void *valor resultante de dicha conversión se puede volver a convertir al tipo de puntero de función original, utilizando una conversión explícita, sin pérdida de información.

Nota: El estándar ISO C no requiere esto, pero se requiere para la conformidad POSIX.

Por lo tanto, parece que las conversiones explícitas void *son muy recomendables para la máxima confiabilidad en el código al pasar un puntero a una función variable como printf(). En los sistemas POSIX, es seguro emitir un puntero de función a un puntero vacío para imprimir. En otros sistemas, no es necesariamente seguro hacer eso, ni es necesariamente seguro pasar punteros que void *no sean un yeso.

Jonathan Leffler
fuente
3
Estoy un poco confundido acerca de las promociones estándar y los argumentos variados. ¿Todos los punteros reciben promoción estándar void*? De lo contrario, si int*fueran, digamos, dos bytes y void*fueran 4 bytes, entonces claramente sería un error leer cuatro bytes del argumento, ¿no?
Kerrek SB
Tenga en cuenta que una actualización de POSIX (POSIX 2013) ha eliminado la sección 2.12.3, moviendo la mayoría de los requisitos a la dlsym()función. Algún día escribiré el cambio ... pero 'un día' no es 'hoy'.
Jonathan Leffler
¿Esta respuesta también se aplica a los punteros a las funciones? ¿Se pueden convertir a void *? Hmm, veo tu comentario aquí . Dado que solo se necesita una conversión de un wat (puntero de función a void *), ¿funciona entonces?
chux
@chux: Estrictamente, la respuesta es 'no', pero en la práctica la respuesta es 'sí'. El estándar C no garantiza que los punteros de función se puedan convertir a void *ay sin pérdida de información. Pragmáticamente, hay muy pocas máquinas en las que el tamaño de un puntero de función no es el mismo que el de un puntero de objeto. No creo que el estándar proporcione un método para imprimir un puntero de función en máquinas donde la conversión es problemática.
Jonathan Leffler
"y viceversa sin pérdida de información" no es relevante para la impresión. ¿Eso ayuda?
chux - Restablece a Mónica el
50

pes el especificador de conversión para imprimir punteros. Utilizar este.

int a = 42;

printf("%p\n", (void *) &a);

Recuerde que omitir la pconversión es un comportamiento indefinido y que la impresión con el especificador de conversión se realiza de una manera definida por la implementación.

ouah
fuente
2
Perdón, ¿por qué omitir el elenco es "comportamiento indefinido"? ¿Importa esto la dirección de qué variable es, si todo lo que necesita es la dirección, no el valor?
valdo
9
@valdo porque C lo dice (C99, 7.19.6.1p8) "p El argumento será un puntero al vacío".
ouah
12
@valdo: No es necesariamente el caso de que todos los punteros tengan el mismo tamaño / representación.
caf
32

Use %p, para "puntero", y no use nada más *. El estándar no le garantiza que se le permita tratar un puntero como cualquier tipo particular de entero, por lo que en realidad obtendría un comportamiento indefinido con los formatos integrales. (Por ejemplo, %uespera un unsigned int, pero ¿qué pasa si void*tiene un tamaño diferente o un requisito de alineación diferente unsigned int?)

*) [¡Vea la excelente respuesta de Jonathan!] Alternativamente %p, puede usar macros específicas de puntero de <inttypes.h>, agregadas en C99.

Todos los punteros de objeto son convertibles implícitamente void*en C, pero para pasar el puntero como un argumento variado, debe lanzarlo explícitamente (ya que los punteros de objeto arbitrarios solo son convertibles , pero no idénticos a los punteros vacíos):

printf("x lives at %p.\n", (void*)&x);
Kerrek SB
fuente
2
Todos los punteros de objetos son convertibles a void *(aunque printf()técnicamente necesita la conversión explícita, ya que es una función variada). Los punteros de función no son necesariamente convertibles a void *.
caf
@caf: ¡Oh, no sabía sobre los argumentos variadic - arreglado! ¡Gracias!
Kerrek SB
2
El estándar C no requiere que los punteros de función sean convertibles void *y vuelvan al puntero de función sin pérdida; afortunadamente, sin embargo, POSIX lo requiere explícitamente (teniendo en cuenta que no es parte del estándar C). Así, en la práctica, puede salirse con la suya (la conversión void (*function)(void)de void *ida y vuelta a void (*function)(void)), pero estrictamente no es un mandato de la norma C.
Jonathan Leffler
2
Jonathan y R .: Todo esto es muy interesante, pero estoy bastante seguro de que no estamos tratando de imprimir punteros de funciones aquí, por lo que quizás este no sea el lugar adecuado para discutir esto. ¡Prefiero ver algo de apoyo aquí para mi insistencia en no usar %u!
Kerrek SB
2
%uy %luestán mal en todas las máquinas , no en algunas máquinas. La especificación de printfes muy clara que cuando el tipo pasado no coincide con el tipo requerido por el especificador de formato, el comportamiento es indefinido. Si el tamaño de los tipos coincide (que podría ser verdadero o falso, dependiendo de la máquina) es irrelevante; son los tipos que deben coincidir, y nunca lo harán.
R .. GitHub DEJA DE AYUDAR AL HIELO
9

Como alternativa a las otras respuestas (muy buenas), puede convertir uintptr_tao intptr_t(desde stdint.h/ inttypes.h) y usar los correspondientes especificadores de conversión de enteros. Esto permitiría una mayor flexibilidad en la forma en que se formatea el puntero, pero estrictamente hablando no se requiere una implementación para proporcionar estos typedefs.

R .. GitHub DEJA DE AYUDAR AL HIELO
fuente
¿Considerar #include <stdio.h> int main(void) { int p=9; int* m=&s; printf("%u",m); } es un comportamiento indefinido imprimir la dirección de la variable usando el %uespecificador de formato? La dirección de la variable en la mayoría de los casos es positiva, ¿puedo usarla en %ulugar de %p?
Destructor
1
@Destructor: No, %ues un formato de unsigned inttipo y no se puede usar con un argumento puntero a printf.
R .. GitHub DEJA DE AYUDAR A ICE
-1

Puedes usar %xo %Xo %p; Todos ellos son correctos.

  • Si usa %x, la dirección se da en minúsculas, por ejemplo:a3bfbc4
  • Si usa %X, la dirección se da en mayúscula, por ejemplo:A3BFBC4

Ambos son correctos.

Si usa %xo %Xestá considerando seis posiciones para la dirección, y si usa %pestá considerando ocho posiciones para la dirección. Por ejemplo:

Bajá
fuente
1
Bienvenido a SO. Tómese un tiempo para revisar las otras respuestas, están explicando claramente una serie de detalles que está pasando por alto.
AntoineL