static_cast - reinterpret_cast c++ ejemplos
¿Es% p especificador sólo para punteros válidos? (4)
% p es solo una especificación de formato de salida para printf. No es necesario eliminar la referencia ni validar el puntero de ninguna manera, aunque algunos compiladores emiten una advertencia si el tipo no es un puntero:
int main(void)
{
int t = 5;
printf("%p/n", t);
}
Advertencia de compilación:
warning: format ‘%p’ expects argument of type ‘void*’, but argument 2 has type ‘int’ [-Wformat]
Salidas:
0x5
Supongamos que en mi plataforma sizeof(int)==sizeof(void*)
y tengo este código:
printf( "%p", rand() );
¿Será este un comportamiento indefinido debido a que pasa un valor que no es un puntero válido en lugar de %p
?
C estándar, 7.21.6.1, la función fprintf
, indica solo
p
El argumento será un puntero avoid
.
Por el Apéndice J.2, esto es una restricción , y violar una restricción causa UB.
(Abajo está mi razonamiento anterior de por qué esto debería ser UB, que era demasiado complicado).
Ese párrafo no describe cómo se recupera el void*
de ...
, pero la única forma en que el estándar C ofrece para este propósito es 7.16.1.1, la macro va_arg
, que nos advierte que
Si el tipo no es compatible con el tipo del siguiente argumento real (según se promueve de acuerdo con las promociones de argumentos predeterminados), el comportamiento no está definido.
Si lees 6.2.7, Tipo compatible y tipo compuesto, no hay indicios de que void*
e int
deban ser compatibles, independientemente de su tamaño. Entonces, yo diría que dado que va_arg
es la única forma de implementar printf
en el estándar C , el comportamiento no está definido.
Para ampliar la respuesta de @larsman (que dice que dado que violaste una restricción, el comportamiento no está definido), aquí hay una implementación real de C donde sizeof(int) == sizeof(void*)
, sin embargo, el código no es equivalente a printf( "%p", (void*)rand() );
El procesador Motorola 68000 tiene 16 registros que se utilizan para el cálculo general, pero no son equivalentes. Ocho de ellos (denominados a0
a a7
) se usan para acceder a la memoria (registros de direcciones) y los otros ocho ( d0
a d7
) se usan para aritmética (registros de datos). Una convención de llamada válida para esta arquitectura sería
- Pase los dos primeros parámetros de enteros en
d0
yd1
; Pasar el resto en la pila. - Pase los dos primeros parámetros de puntero en
a0
ya1
; Pasar el resto en la pila. - Pase todos los otros tipos en la pila, independientemente del tamaño.
- Los parámetros pasados en la pila se empujan de derecha a izquierda sin importar el tipo.
- Los parámetros basados en la pila se alinean en los límites de 4 bytes.
Esta es una convención de llamadas perfectamente legal, similar a las convenciones de llamadas utilizadas por muchos procesadores modernos.
Por ejemplo, para llamar a la función void foo(int i, void *p)
, debería pasar i
en d0
y p
en a0
.
Tenga en cuenta que para llamar a la void bar(void *p, int i)
la función void bar(void *p, int i)
, también pasaría i
en d0
p
en a0
.
Bajo estas reglas, printf("%p", rand())
pasaría la cadena de formato en a0
y el parámetro de número aleatorio en d0
. Por otro lado, printf("%p", (void*)rand())
pasaría la cadena de formato en a0
y el parámetro de puntero aleatorio en a1
.
La estructura va_list
se vería así:
struct va_list {
int d0;
int d1;
int a0;
int a1;
char *stackParameters;
int intsUsed;
int pointersUsed;
};
Los primeros cuatro miembros se inicializan con los valores de entrada correspondientes de los registros. El stackParameters
apunta a los primeros parámetros basados en la pila pasados a través de ...
, y los intsUsed
y los pointersUsed
intsUsed
se inicializan al número de parámetros nombrados que son enteros y punteros, respectivamente.
La macro va_arg
es un compilador intrínseco que genera un código diferente según el tipo de parámetro esperado.
- Si el tipo de parámetro es un puntero, entonces
va_arg(ap, T)
expande a(T*)get_pointer_arg(&ap)
. - Si el tipo de parámetro es un entero, entonces
va_arg(ap, T)
expande a(T)get_integer_arg(&ap)
. - Si el tipo de parámetro es otra cosa, entonces
va_arg(ap, T)
expande a*(T*)get_other_arg(&ap, sizeof(T))
.
La función get_pointer_arg
es así:
void *get_pointer_arg(va_list *ap)
{
void *p;
switch (ap->pointersUsed++) {
case 0: p = ap->a0; break;
case 1: p = ap->a1; break;
case 2: p = *(void**)get_other_arg(ap, sizeof(p)); break;
}
return p;
}
La función get_integer_arg
es así:
int get_integer_arg(va_list *ap)
{
int i;
switch (ap->intsUsed++) {
case 0: i = ap->d0; break;
case 1: i = ap->d1; break;
case 2: i = *(int*)get_other_arg(ap, sizeof(i)); break;
}
return i;
}
Y la función get_other_arg
es así:
void *get_other_arg(va_list *ap, size_t size)
{
void *p = ap->stackParameters;
ap->stackParameters += ((size + 3) & ~3);
return p;
}
Como se señaló anteriormente, al llamar a printf("%p", rand())
pasaría la cadena de formato en a0
y el entero aleatorio en d0
. Pero cuando la función printf
ejecute, verá el formato %p
y ejecutará un va_arg(ap, void*)
, que usará get_pointer_arg
y leerá el parámetro de a1
lugar de d0
. Como a1
no se inicializó, contiene basura. El número aleatorio que generaste se ignora.
Continuando con el ejemplo, si tenía printf("%p %i %s", rand(), 0, "hello");
Esto se llamaría de la siguiente manera:
-
a0
= dirección de la cadena de formato (primer parámetro de puntero) -
a1
= dirección de la cadena"hello"
(segundo parámetro de puntero) -
d0
= número aleatorio (primer parámetro entero) -
d1
= 0 (segundo parámetro entero)
Cuando se ejecuta la función printf
, lee la cadena de formato de a0
como se esperaba. Cuando ve el %p
, recuperará el puntero de a1
y lo imprimirá, de modo que obtendrá la dirección de la cadena "hello"
. Luego verá el %i
y recuperará el parámetro de d0
, por lo que imprime un número aleatorio. Finalmente, ve el %s
y recupera el parámetro de la pila. ¡Pero no pasaste ningún parámetro en la pila! Esto leerá la pila de basura no definida, que probablemente bloqueará su programa cuando intente imprimirlo como si fuera un puntero de cadena.
Sí, está indefinido. De C ++ 11, 3.7.4.2/4:
El efecto de usar un valor de puntero no válido (incluido el pasarlo a una función de desasignación) no está definido.
con una nota a pie de página:
En algunas implementaciones, causa un error de tiempo de ejecución generado por el sistema.