cast Void pointer en c

Iniciado por prosebas, 19 Abril 2021, 06:56 AM

0 Miembros y 1 Visitante están viendo este tema.

prosebas

Buenas noches comunidad, estoy realizando un arbol general en c que cuenta con una lista génerica para guardar los descendientes(hijos) que hice  en c de forma génerica usando void pointer pero estoy teniendo problemas  :-(.

El problema que me surge ya lo tengo identificado, sin embargo, no se como solucionarlo, lo que pasa es que cuando quiero imprimir un dato del nodo general que se encuentra dentro de la lista me esta saliendo basura, el problema debe ser que estoy realizando mal el cast pero ya intente de varias formas y sigue sin funcionar.

Estructura de la lista

typedef struct node
{
   void *data;
   struct node *next;
} node, Node;
typedef struct list
{
   struct node *head;
   
} list, List;



Estructura del arbol

typedef struct NodeGeneral
{
   void *data;
   list *dec;
} GeneralNode;
typedef struct GeneralTree
{
   GeneralNode *root;
} GeneralTree;




main
En el siguiente código se encuentra el fragmento donde estoy realizando el cast(Linea 12).

int main(void)
{

 List *list;
 GeneralNode *proof;
 int x = 5;
 proof = init_GeneralNode((int *)&x);
 init_list(&list, &proof);
 Node *aux = list->head;
 while (aux != NULL)
 {
   GeneralNode *tmp =(GeneralNode *)aux->data; //Aqui estoy realizando el casteo
   printf("::%d\n", *((int *)tmp->data));
   aux = aux->next;

 }

}
//Las funciones init crean la memoria y establecen los datos

Anexó las funciones con las que inicialice la lista y establezco la cabeza(Ya la he probado con diferentes tipos de datos(int,float... datos nativos) y funciona bien la lista)

node *newNode(void *value)
{
   node *newNode = (node *)malloc(sizeof(node));
   newNode->data = value;
   newNode->next = NULL;
   return newNode;
}
void init_list(list **lista, void *value)
{
   list *aux = *lista;
   aux = (list *)malloc(sizeof(list));
   aux->head = newNode(value);
   *lista = aux;
}
//************************************
GeneralNode *init_GeneralNode(void *data)
{
   GeneralNode *newNode = (GeneralNode *)malloc(sizeof(GeneralNode));
   newNode->data = data;
   newNode->dec = NULL;
   return newNode;
}


En el NodoGeneral que tengo,en el caso de la lista de descendiente cada dato de la lista representa un NodoGeneral.El next, el puntero al siguiente hijo.

Les agradezco si me pueden ayudar.

Usuario887

#1
GeneralNode *tmp =(GeneralNode *)*((size_t*)aux->data);

Intenta recordar a que exactamente apuntan tus punteros en cada momento dado mientras programas. Si no te toparas con problemas.
Saludos.




Cita de: prosebas en 19 Abril 2021, 06:56 AM
me esta saliendo basura,

PD: Si fuera basura luego de cierta cantidad de intentos el programa se hubiese detenido. Sin embargo a medida que compilas te das cuenta de que la cantidad que esta retornando varia en un rango especifico. Esas suelen ser las direcciones que reserva Windows para con la memoria.
Lo que esta imprimiendo printf en tu programa original no es basura, era la direccion de tu variable proof en memoria, con menos precision a causa del casting que hiciste al tipo GeneralNode. size_t es adecuado para estos casos porque se identifica como palabra, es decir, puede contener una direccion a cualquier parte de la memoria (direccionable dentro de tu programa).

prosebas

#2
Cita de: marax en 19 Abril 2021, 11:33 AM
GeneralNode *tmp =(GeneralNode *)*((size_t*)aux->data);

Intenta recordar a que exactamente apuntan tus punteros en cada momento dado mientras programas. Si no te toparas con problemas.
Saludos.

Gracias ya esta solucionado, ese era el error, sin embargo, no entiendo porque es (GeneralNode *)*((size_t*)aux->data especificamente del size_t.


MinusFour

Tu variable proof es de tipo GeneralNode*. Si haces &proof, este valor es GeneralNode**. Básicamente estás poniendo GeneralNode** en node->data.

Simplemente pasa:

init_list(&list, proof);

O si no vas a tener que dereferenciar el puntero dos veces prácticamente.

Quizás sea buena idea considerar lo mismo para lista (en lugar de usar list** usar list* y no usar &list. Yo también no le daría el mismo nombre que tus tipos.

Usuario887

Cita de: prosebas en 19 Abril 2021, 16:14 PM
Gracias ya esta solucionado, ese era el error, sin embargo, no entiendo porque es (GeneralNode *)*((size_t*)aux->data especificamente del size_t.

Lo que "es" size_t no es aux->data. Lo que es de tamaño size_t es la palabra de tu computador.
https://es.wikipedia.org/wiki/Palabra_(inform%C3%A1tica)

Te equivocaste en dos cosas:

1. Accediste a la direccion de un dato, pensando que era el dato mismo. Esto te ha pasado por no mantener un registro mental o escrito de tus punteros. Aqui estas usando una variedad de punteros, innecesarios en cierta manera como añadio MinusFour, sin embargo entiendo que estas experimentando... cosa que yo veo imprescindible para hacerse habil con este tema.
2. Hiciste un casting equivocado segun la logica de tu programa, por lo que aunque hubieses accedido al dato correcto, habrias estado desalineado [https://en.wikipedia.org/wiki/Data_structure_alignment] y habrias accedido a un dato, pero incorrecto.

Con size_t se puede arreglar el problema del alineamiento. ¿Por que? Porque no size_t en si, sino (size_t *) es algo asi como un puntero (void *) pero accesible. Si tu haces esto:

int x;
void *p;
p=(void *)&x;
*p=1;


El compilador te va a dar un error:
Citarerror: 'void*' is not a pointer-to-object type

Sin embargo, puedes hacerlo con size_t:

int x;
size_t *p;
p=(size_t *)&x;
*p=1;


Y lo compila perfectamente.

size_t digamos que lo uso como un intermediario, no como un tipo.
Lee los archivos que he enlazado y te daras cuenta de que hay varias formas de llegar a eso.

Saludos.




RayR

El problema concreto que tenías se arregla como te dijo MinusFour. El cast de la línea 12 tal como lo tenías ya estaba bien. El size_t es innecesario e incorrecto. Jamás vas a tener problemas de alineación si usas punteros al mismo tipo del dato al que deseas referenciar. aux->data se refiere a proof, que apunta a un bloque reservado mediante malloc(sizeof(GeneralNode)); por lo que (GeneralNode *) es lo único que necesitas. Ese size_t no tiene ninguna razón para estar ahí.

Más aún, esto:

   int x;
   size_t *p;
   p=(size_t *)&x;
   *p=1;


viola las reglas de C. Simplificando un poco, sólo es válido acceder a un objeto o variable de tipo A mediante un puntero al mismo tipo A (o a un tipo compatible), o bien, mediante un puntero char. size_t, por supuesto, no es un tipo compatible con int, y mucho menos con GeneralNode. En un ejemplo tan sencillo, seguramente no habría problemas con ese código, pero independientemente de eso, es una clara violación a las reglas del lenguaje; en concreto, a la llamada "strict aliasing rule", por lo que, aunque compile, es un error, y el compilador es libre de realizar optimizaciones que hagan que tu programa funcione de forma incorrecta. A veces puede ser válido saltarse algunas reglas, y de hecho los compiladores a veces ofrecen opciones para estos casos, tipo -fno-strict-aliasing, pero esto sólo se debería hacer si es 100% necesario y siendo conscientes de que nos salimos del estándar y nuestro código ya no es no portable.

Las reglas que definen qué tipos son compatibles con otros pueden ser algo complejas, pero como consejo y regla general, a menos que sepamos muy bien lo que hacemos y tengamos amplia experiencia, sólo deberíamos usar punteros de exactamente el mismo tipo que la variable a referenciar (o punteros char, que es la excepción a la regla).

Usuario887

#6
RayR, creo que no entendiste el problema del hombre. El quería hacer un casting correcto de un valor que definió como (void *). Este tipo de dato no puede ser accedido directamente, así que use (size_t *) (Ojo, size_t* y no size_t) como un intermediario al dato.

¿Al menos sabes por qué la solución que le di le funcionó?:
GeneralNode *tmp =(GeneralNode *)*((size_t*)aux->data);




PD: Si el dato es void* como en este caso y sin embargo se insiste en un tipo especifico que puede variar con el tiempo (pues esa es la razon por la que existen punteros void*) se puede incluso indexar un array en un sentido general:

_t *x=(_t*)((size_t*)addr)+sizeof(_t)*i;




PD 2: Esta claro que pudo haber hecho su programa mucho mas eficientemente, pero la pregunta que tenia era como hacer el casting correctamente. Esa es la forma. Luego se dara cuenta de por que es mejor algo mas simple, mientras tanto no creo que preste atencion a eso.

MinusFour

En el dado caso que quisiera usar un puntero de ese tipo... porque no usar:

GeneralNode *tmp = *(GeneralNode **)aux->data;

En lugar de:

GeneralNode *tmp =(GeneralNode *)*((size_t*)aux->data);

RayR

#8
Claro que lo entendí. Y su problema más que con el cast, era con lo que le explicó MinusFour. Estás mezclando y confundiendo varias cosas. Por ejemplo, la alineación no tiene nada que ver en este caso. Le fallaba el programa porque aux->data tenía un puntero a puntero a GeneralNode, pero en su código lo trataba como puntero a GeneralNode. Al desreferenciarlo, estaba sólo haciéndolo con el primer puntero. En todo caso, el cast correcto debería haber sido:

GeneralNode* tmp = *(GeneralNode**)aux->data;

Pero de ninguna manera el puntero a size_t era correcto. Esta línea

   GeneralNode *tmp =(GeneralNode *)*((size_t*)aux->data);

aunque compile y funcione, no es correcta. En ningún lugar donde se dediquen a esto lo aceptarían.

Y por supuesto que entendí por qué tu solución funcionó. Llevo muchos años trabajando en esto profesionalmente, casi todos ellos en C o C++, como para no entender algo tan básico. Además, sí que estabas usando un size_t, aunque de forma implícita. Cuando haces esto

*((size_t*)aux->data)

el asterisco más a la izquierda desreferencia el puntero y convierte su valor a size_t. Ya desde ahí tu código está equivocado. Estás invocando UB (undefined behavior, comportamiento indefinido). Para empezar, size_t no necesariamente tiene el mismo tamaño que un puntero y, de hecho, en algunas plataformas es diferente, por lo que, en teoría, se podría perder precisión con esa conversión intermedia. Ahora, en realidad en todas las plataformas actuales, size_t sí que ocupa lo mismo que la mayoría de punteros, pero aún así, eso es incorrecto, es UB. No lo digo yo, lo dice el estándar del lenguaje. Sí, es un error algo común pensar que size_t (o sus punteros) es apto para punteros "genéricos". No lo es. Como ya había mencionado, a veces sí se usa, pero sólo en casos muy concretos y a  sabiendas de que no es algo estándar. El ejemplo de este tema de ninguna manera justifica su uso. En todo caso, para eso existe uintptr_t. Aún así, y esta es la verdadera cuestión del asunto, cuando tenemos un GeneralNode**, como prosebas en su código original, lo que se hace es convertirlo a GeneralNode**; nada más.

Ya perdí más tiempo del que hubiera querido, pero, una última cosa. Te recomiendo que leas más sobre punteros. En tus comentarios de este hilo noto más o menos lo mismo que en tus dudas sobre ensamblador: tienes una cierta idea del tema, pero confundes y mezclas muchas cosas. Lo peor que puede uno hacer es creerse un experto cuando apenas empieza (por el tipo de dudas que recuerdo que has planteado, y por lo que escribes en este tema, no parece que tengas mucha experiencia) porque ahí se deja de aprender. Si aún piensas que estás en lo correcto, ¿por qué no pones el código con tu línea en stackoverflow y preguntas si es correcta? Te garantizo que te van a responder que no, y sus respuestas no van a variar mucho de lo que yo te dije. No te tomaría ni 5 minutos copiar y pegar, y saldrías de dudas. O te puedes quedar así, convencido de que tienes razón porque sí. Cada quién.

Editado: veo que MinusFour ya te había respondido. Y sí, de nuevo, el código que él pone es el correcto.

Usuario887

Cita de: MinusFour en 21 Abril 2021, 15:57 PM
En el dado caso que quisiera usar un puntero de ese tipo... porque no usar:

GeneralNode *tmp = *(GeneralNode **)aux->data;

Esta si que es la forma correcta.

Cita de: RayR en 21 Abril 2021, 16:40 PM
Lo peor que puede uno hacer es creerse un experto

Perdoname hacer uso de tu precioso tiempo respondiendo esta cita:

Tienes razon pero no me creo un experto. Lo que crei fue que tu le estabas demostrando otra forma de hacerlo intentando menospreciar la mia, solo otra, y no otra correcta. Hay muchos arrogantes por aqui.

PD: Me jode mas el ego que alguien crea que me la estoy tirando de experto, a que me demuestren que no soy un experto.