Confundido sobre el acceso a miembros de estructura a través de un puntero

Soy nuevo en C y estoy confundido por los resultados que obtengo al hacer referencia a un miembro de una estructura mediante un puntero. Vea el siguiente código para un ejemplo. ¿Qué sucede cuando hago referencia a tst-> número la primera vez? ¿Qué cosa fundamental me falta aquí?

#include  #include  typedef struct { int number; } Test; Test* Test_New(Test t,int number) { t.number = number; return &t; } int main(int argc, char** argv) { Test test; Test *tst = Test_New(test,10); printf("Test.number = %d\n",tst->number); printf("Test.number = %d\n",tst->number); printf("Test.number = %d\n",tst->number); } 

La salida es:

 Test.number = 10 Test.number = 4206602 Test.number = 4206602 

Cuando pasa la prueba a su función Test_New, la pasa por valor y, por lo tanto, se realiza una copia local en la stack para el scope de la función de su función Test_New. Ya que devuelve la dirección de la variable, una vez que la función devuelve la stack es inútil, ¡pero ha devuelto un puntero a una estructura en la stack antigua! De modo que puede ver que su primera llamada devuelve el valor correcto, ya que nada ha sobrescrito su valor de stack, pero las llamadas subsiguientes (que usan la stack) sobrescriben su valor y le dan resultados erróneos.

Para hacer esto correctamente, vuelva a escribir su función Test_New para tomar un puntero y pasar el puntero a la estructura en la función.

 Test* Test_New(Test * t,int number) { t->number = number; return t; } int main(int argc, char ** argv) { Test test; Test * tst = Test_New(&test,10); printf("Test.number = %d\n",tst->number); printf("Test.number = %d\n",tst->number); printf("Test.number = %d\n",tst->number); } 

Independientemente de la struct , siempre es incorrecto devolver la dirección de una variable local . Por lo general, también es incorrecto colocar la dirección de una variable local en una variable global o almacenarla en un objeto asignado en el montón con malloc . Generalmente, si necesita devolver un puntero a un objeto, necesitará que alguien más le proporcione el puntero, o bien, deberá asignar espacio con malloc , que devolverá un puntero. En ese caso, parte de la API para su función debe especificar quién es el responsable de llamar free cuando el objeto ya no es necesario.

Usted está devolviendo la dirección de t según lo declarado en el método Test_New , no la dirección de test que pasó al método. Es decir, la test se pasa por valor y, en su lugar, debe pasarle un puntero.

Entonces, esto es lo que sucede cuando se llama Test_New . Se crea una nueva estructura de Test llamada t y el número t.number se establece para que sea igual al valor de test.number (que no se había inicializado). Luego establece t.number igual al number parámetro que pasó al método, y luego devuelve la dirección de t . Pero t es una variable local y queda fuera del scope tan pronto como finaliza el método. Por lo tanto, está devolviendo un puntero a datos que ya no existen y es por eso que está terminando con basura.

Cambia la statement de Test_New a

 Test* Test_New(Test* t,int number) { t->number = number; return t; } 

y llamalo via

 Test *tst = Test_New(&test,10); 

y todo irá como esperas.

Solo para extender la respuesta de BlodBath, piensa en lo que sucede en la memoria cuando haces esto.

A medida que ingresa a su rutina principal, se crea una nueva estructura de prueba automática, en la stack, ya que es automática. Así que tu stack parece algo así como

     |  direccion de retorno para main |  será utilizado en la parte inferior
     |  argc |  Copiado en la stack desde el entorno
     |  dirección argv |  Copiado en la stack desde el entorno
 -> |  número de prueba |  creado por definición prueba de prueba;

con -> indica el puntero de stack al último elemento utilizado de la stack.

Ahora llama a Test_new() , y actualiza la stack de esta manera:

     |  direccion de retorno para main |  será utilizado en la parte inferior
     |  argc |  Copiado en la stack desde el entorno
     |  dirección argv |  Copiado en la stack desde el entorno
     |  número de prueba |  creado por definición prueba de prueba;
     |  return addr para Test_new |  solía volver al fondo
     |  copia de test.number |  Copiado en la stack porque C SIEMPRE usa llamada por valor
 -> |  10 |  copiado en la stack

Cuando regresas &t , ¿qué dirección estás recibiendo? Respuesta: la dirección de los datos EN LA PILA. PERO ENTONCES regresas, el puntero de stack se decrementa. Cuando llamas a printf , esas palabras en la stack se reutilizan, pero tu dirección aún es importante para ellas. Sucede que lo que el número en esa ubicación en la stack, interpretado como una dirección, apunta a tiene el valor 4206602, pero eso es pura casualidad; de hecho, fue una especie de mala suerte, ya que la buena suerte hubiera sido algo que causó una falla de segmentación, lo que le permite saber que algo estaba realmente roto.

El problema es que no está pasando una referencia a Test_New , está pasando un valor. Entonces, estás devolviendo la ubicación de memoria de la variable local . Considere este código que demuestra su problema:

 #include  typedef struct { } Test; void print_pass_by_value_memory(Test t) { printf("%p\n", &t); } int main(int argc, char** argv) { Test test; printf("%p\n", &test); print_pass_by_value_memory(test); return 0; } 

La salida de este progtwig en mi máquina es:

 0xbfffe970 0xbfffe950 

La prueba t declarada en Test_New () es una variable local. Está intentando devolver la dirección de una variable local. A medida que la variable local se destruye una vez que existe la función, se liberará el significado de la memoria, el comstackdor es libre de poner algún otro valor en la ubicación donde se guardó su variable local.

En su progtwig, cuando intenta acceder al valor la segunda vez, la ubicación de la memoria podría haberse asignado a una variable o proceso diferente. Por lo tanto, está recibiendo la salida incorrecta.

Una mejor opción para usted será pasar la estructura de main () por referencia en lugar de por valor.

Has pasado el contenido de prueba por valor a Test_New. IOW se ha asignado una nueva copia de una estructura de prueba en la stack cuando llamó a Test_New. Es la dirección de esta Prueba que regresa de la función.

Cuando usa tst-> number la primera vez, se recupera el valor de 10 porque aunque esa stack se ha desenrollado, no se ha hecho ningún otro uso de esa memoria. Sin embargo, tan pronto como se llama a ese primer printf, la memoria de stack se reutiliza para lo que necesite, pero tst sigue apuntando a esa memoria. Por lo tanto, los usos subsiguientes de tst-> number recuperan cualquier printf que haya quedado allí en esa memoria.

Utilice Test & t en la firma de función en su lugar.

Podrías hacer algo como esto para hacerlo un poco más fácil:

 typedef struct test { int number; } test_t; test_t * Test_New(int num) { struct test *ptr; ptr = (void *) malloc(sizeof(struct test)); if (! ptr) { printf("Out of memory!\n"); return (void *) NULL; } ptr->number = num; return ptr; } void cleanup(test_t *ptr) { if (ptr) free(ptr); } .... int main(void) { test_t *test, *test1, *test2; test = Test_New(10); test1 = Test_New(20); test2 = Test_new(30); printf( "Test (number) = %d\n" "Test1 (number) = %d\n" "Test2 (number) = %d\n", test->number, test1->number, test2->number); .... cleanup(test1); cleanup(test2); cleanup(test3); return 0; } 

… Como puede ver, es fácil asignar espacio para varias instancias completamente diferentes de test_t, por ejemplo, si necesita guardar el estado existente de una para poder revertir más tarde … o por cualquier motivo.

A menos que, por supuesto, haya alguna razón por la que debas mantenerlo local … pero realmente no puedo pensar en una.