Uno de mis amigos señaló de "Comprender y usar los punteros C - Richard Reese, publicaciones de O'Reilly" el segundo punto y no pude explicar la primera oración. ¿Qué me estoy perdiendo?
Puntero para anular
Un puntero a void es un puntero de uso general que se utiliza para contener referencias a cualquier tipo de datos. A continuación se muestra un ejemplo de un puntero a void:
void *pv;
Tiene dos propiedades interesantes:
- Un puntero a void tendrá la misma representación y alineación de memoria que un puntero a
char
.- Un puntero a void nunca será igual a otro puntero. Sin embargo, dos punteros vacíos asignados a un valor
NULL
serán iguales.
Este es mi código, no del libro y todos los punteros tienen el mismo valor y son iguales.
#include <stdio.h> int main() { int a = 10; int *p = &a; void *p1 = (void*)&a; void *p2 = (void*)&a; printf("%p %p\n",p1,p2); printf("%p\n",p); if(p == p1) printf("Equal\n"); if(p1 == p2) printf("Equal\n"); }
Producción:
0x7ffe1fbecfec 0x7ffe1fbecfec 0x7ffe1fbecfec Equal Equal
La siguiente sección de este Borrador de la Norma C11 refuta completamente la afirmación realizada (incluso con la aclaración mencionada en la 'errata', en el comentario de GSerg ).
6.3.2.3 Punteros
1 Un puntero a
void
puede convertirse en o desde un puntero a cualquier tipo de objeto. Un puntero a cualquier tipo de objeto se puede convertir en un puntero paravoid
y viceversa; el resultado se comparará igual al puntero original.
O bien, esta sección del mismo proyecto de Norma:
7.20.1.4 Tipos enteros capaces de contener punteros a objetos
1 El siguiente tipo designa un tipo de entero con signo con la propiedad de que cualquier puntero válido a
void
puede convertirse a este tipo, luego volver a convertirse en puntero avoid
y el resultado será igual al puntero original:
intptr_t
C 2018 6.5.9 6 dice:
Dos punteros se comparan igual si y solo si ambos son punteros nulos, ambos son punteros al mismo objeto (incluido un puntero a un objeto y un subobjeto al principio) o función, ambos son punteros a uno más allá del último elemento de la misma matriz o uno es un puntero a uno más allá del final de un objeto de matriz y el otro es un puntero al inicio de un objeto de matriz diferente que sigue inmediatamente al primer objeto de matriz en el espacio de direcciones.
Entonces, supongamos que tenemos:
int a; void *p0 = &a; void *p1 = &a;
Entonces, si p0
y p1
“apuntan al mismo objeto”, p0 == p1
debe evaluarse como verdadero. Sin embargo, uno podría interpretar el estándar en el sentido de que un void *
no apunta a nada mientras sea un void *
; solo contiene la información necesaria para volver a convertirlo a su tipo original. Pero podemos probar esta interpretación.
Considere la especificación de que dos punteros son iguales si apuntan a un objeto y un subobjeto al principio. Eso significa que dado int a[1];
, &a == &a[0]
debería evaluarse como verdadero. Sin embargo, no podemos usar correctamente &a == &a[0]
, porque las restricciones para ==
para punteros requieren que los operandos apunten a tipos compatibles o que uno o ambos sean un void *
(con calificadores como const
permitidos). Pero a
y a[0]
no tienen tipos compatibles ni son void
.
La única forma de que surja una situación totalmente definida en la que comparemos punteros con este objeto y su subobjeto es que al menos uno de los punteros se haya convertido en un void *
o en un puntero a un tipo de carácter (porque estos son dado un trato especial en las conversiones). Podríamos interpretar que el estándar significa solo lo último, pero considero que la interpretación más razonable es que se incluye void *
. La intención es que (void *) &a == (void *) &a[0]
se interprete como una comparación de un puntero al objeto a
con un puntero al objeto a[0]
aunque esos punteros estén en el formulario void *
. Por lo tanto, estos dos void *
deben compararse como iguales.
TL/DR : el libro está mal.
¿Qué me estoy perdiendo?
Nada, por lo que puedo ver. Incluso la versión de la errata presentada en los comentarios...
Un puntero a void nunca será igual a otro puntero a void.
... simplemente no es compatible con la especificación del lenguaje C. En la medida en que el autor confíe en la especificación del lenguaje, el texto relevante sería el párrafo 6.5.9/6:
Dos punteros se comparan iguales si y solo si ambos son punteros nulos, ambos son punteros al mismo objeto (incluido un puntero a un objeto y un subobjeto al principio) o función, ambos son punteros a uno más allá del último elemento de la misma matriz o uno es un puntero a uno más allá del final de un objeto de matriz y el otro es un puntero al inicio de un objeto de matriz diferente que sigue inmediatamente al primer objeto de matriz en el espacio de direcciones.
void
es un tipo de objeto, aunque "incompleto". Los punteros a void
que son válidos y no nulos son punteros a objetos, y se comparan como iguales entre sí bajo las condiciones expresadas por la especificación. La forma habitual de obtener dichos punteros es convirtiendo un puntero de objeto de un tipo diferente (puntero) en void *
. El resultado de tal conversión aún apunta al mismo objeto que el puntero original.
Mi mejor conjetura es que el libro malinterpreta la especificación para indicar que los punteros a void no deben interpretarse como punteros a objetos. Aunque hay casos especiales que se aplican solo a los punteros a void
, eso no implica que las disposiciones generales que se aplican a los punteros a objetos no se apliquen también a los punteros a void.
Un puntero es solo una dirección en la memoria. Dos punteros cualesquiera son iguales si son NULL o si apuntan a la misma dirección. Puede seguir y seguir sobre cómo puede suceder eso con el lenguaje de estructuras, uniones, etc. Pero al final, es simplemente álgebra con ubicaciones de memoria.
- Un puntero a void nunca será igual a otro puntero. Sin embargo, dos punteros vacíos asignados a un valor
NULL
serán iguales.
Dado que NULL
se menciona en esa declaración, creo que es un error de tipeo. La declaración debe ser algo como
NULL
. Sin embargo, dos punteros vacíos asignados a un valor NULL
serán iguales. Eso significa que cualquier puntero válido para anular nunca es igual a un puntero NULL
.