2012-01-29 11 views
19

Duplicar posible:
Correct format specifier to print pointer (address)?printf y punteros

Al imprimir usando un puntero printf, es necesario que a emitir el puntero a void *? En otras palabras, en un código como

#include <stdio.h> 
int main() { 
    int a; 
    printf("address of a = %p\n", &a); 
} 

debe ser realmente el argumento (void *) &a? gcc no parece dar ninguna advertencia cuando no se realiza un lanzamiento explícito.

+0

por qué debería dar una advertencia cuando está dando un parámetro a printf que acepta cualquier cosa y recupera los datos especificados por% p, que es la dirección que apunta a un . – TigOldBitties

+0

'printf' no es seguro de tipos - ¿por qué deberían sus variables dar una sacudida de jammy? Depende de usted. Welcomen a OOP. –

+0

@TigOldBitties: Porque gcc realiza análisis estáticos basados ​​en cadenas de formato 'printf'. –

Respuesta

13

Sí, se requiere el molde a void*.

int a; 
printf("address of a = %p\n", &a); 

&a es de tipo int*; El formato "%p" de printf requiere un argumento del tipo void*. El argumento int* es no convertido implícitamente a void*, porque la declaración de printf no proporciona información de tipo para otros parámetros que no sean el primero (la cadena de formato). Todos los argumentos posteriores a la cadena de formato tienen aplicadas las promociones de argumento por defecto; estas promociones no convierten int* en void*.

El resultado probable es que printf ve un argumento que es realmente del tipo int* y lo interpreta como si se tratara de tipo void*. Esto es tipo-juego de palabras, no conversión, y tiene un comportamiento indefinido. Es probable que funcione si int* y void* tienen la misma representación, pero el estándar de idioma no garantiza eso, ni siquiera por implicación. Y el tipo de juego de palabras que describí es solo un comportamiento posible; el estándar dice literalmente nada sobre lo que puede suceder.

(Si lo hace lo mismo con una función no variadic con un prototipo visible, por lo que el compilador sabe en el momento de la llamada que el parámetro es de tipo void*, entonces se va a generar código para hacer una implícita int* -to- void* conversión. Ese no es el caso aquí.)

2

Creo que podría ser necesario para emitir. ¿Estamos seguros de que el tamaño de los punteros es siempre el mismo? Estoy seguro de que he leído recientemente en stackoverflow que el tamaño (o tal vez solo la alineación?) De un struct* puede ser diferente al de un union*. Esto sugeriría que uno o ambos pueden ser diferentes del tamaño de un void*.

Así que incluso si el valor no cambia mucho, o en absoluto, en la conversión, tal vez el molde es necesario para garantizar que el tamaño del puntero sea el correcto.

En print, %p espera un void* por lo que debe explicitarlo. Si no lo hace, y si tiene suerte, el tamaño del puntero y la representación del puntero podrían salvar el día. Pero debe explicitarlo para que sea cierto; cualquier otra cosa es un comportamiento técnicamente indefinido.

+1

Su respuesta aborda los mecanismos por los cuales podría ocurrir una falla en una implementación, pero el punto clave es que el contacto de interfaz requiere que pase los tipos correctos a 'printf', y el incumplimiento de este requisito da como resultado UB. –

+0

@ H2CO3, no soy yo quien está haciendo el downvoting en ninguna parte de esta pregunta. Al menos otra persona está presentando teorías alternativas, y tal vez haya muchos otros que están acechando y vindicando. –

+0

No perdoné, pero pude ver cómo una respuesta de la forma "No estoy seguro, pero podría ser de esta manera ..." podría ser visto por otros como alguien digno de votos a favor. En cualquier caso, lamentablemente esta es la mejor respuesta hasta ahora, y la única que no está del todo mal. –

4

¿Es esta una C o C++ pregunta? Para C++, parece que de acuerdo con 5.2.2 [expr.call] párrafo 7 no hay ninguna conversión implícita a void*. Parece que el párrafo 6 del 6.5.2.2 de C99 tampoco implica ninguna promoción explícita de los tipos de punteros. Esto significaría que se requiere un lanzamiento explícito a void* ya que los tipos de puntero pueden tener diferentes tamaños (al menos en C++): si el diseño de los diferentes tipos de apuntadores no es idéntico, terminaría con un comportamiento indefinido. ¿Puede alguien señalar dónde está garantizado que se pase un puntero con el tamaño apropiado al usar listas de argumentos variables?

Por supuesto, al ser un programador de C++ esto no es un gran problema: simplemente no use funciones con un número variable de argumentos. Sin embargo, ese no es un enfoque viable en C.

+0

No es un problema de tamaño, todos los punteros a objetos son del mismo tamaño (dado que el estándar sí los requiere para el viaje de ida y vuelta). Pero la representación no es necesariamente la misma. –

+2

@BenVoigt: No, no se requiere que todos los punteros de los objetos sean del mismo tamaño. 'void *' debe ser lo suficientemente grande como para contener el valor convertido de cualquier puntero de objeto sin pérdida de información, pero no es necesario convertir 'char *' a 'int *' y volver a generar el valor original. El único requisito de ida y vuelta es 'anything *' to 'void *' y viceversa. ('char *' también está cubierto por esto, ya que debe tener la misma representación que 'void *'.) –

+0

@KeithThompson: En una lectura más cercana, depende de la alineación. Si 'int' no tiene un requisito de alineación más fuerte que' char', entonces 'char *' to 'int *' y viceversa es necesario para generar el valor original. 5.2.10p7 "Convertir un prvalue del tipo " al 'T1'" al tipo "puntero a' T2' "(donde' T1' y 'T2' son tipos de objetos y donde los requisitos de alineación de' T2' no son más estricto que los de 'T1') y volver a su tipo original produce el valor del puntero original. –