puntero - ¿Cómo funciona REALMENTE devolver la variable struct en C?
puntero a puntero (3)
¿Cómo es que un valor de retorno de función es claro para mí solo para comenzar de nuevo?
int f()
{
int a = 2;
return a;
}
Ahora a
obtiene la memoria en la pila y su lapso de vida está dentro de f()
para devolver el valor que copia el valor a un registro especial que es leído por la persona que llama, ya que sabe que el destinatario ha colocado el valor para él . (Dado que el tamaño del tamaño de registro especial del titular de valor de retorno es limitado es por eso que no podemos devolver objetos grandes. En caso de idiomas avanzados cuando queremos devolver la función de objeto, en realidad copia la dirección del objeto en el montón en ese registro especial)
Volvamos a C para una situación en la que quiero devolver una variable de estructura, no un puntero:
struct inventory
{
char name[20];
int number;
};
struct inventory function();
int main()
{
struct inventory items;
items=function();
printf("/nam in main/n");
printf("/n%s/t",items.name);
printf(" %d/t",items.number);
getch();
return 0;
}
struct inventory function()
{
struct inventory items;
printf(" enter the item name/n ");
scanf(" %s ",&items.name );
printf(" enter the number of items/n ");
scanf("%d",&items.number );
return items;
}
Código bifurcado desde: https://stackoverflow.com/a/22952975/962545
Aquí está el trato,
Comencemos con la variable main, items
declarada pero no inicializada y luego se llama a la función que devuelve la variable de estructura inicializada que se copia a la que está en main. Ahora estoy un poco borroso para entender cómo function()
devolvió items
variable struct que no se crean dinámicamente (técnicamente no en heap), así que la vida útil de esta variable está dentro del cuerpo de la function()
, también el tamaño del item
variable puede ser bastante grande para que quepa en un registro especial, ¿por qué funcionó? (Sé que podemos asignar dinámicamente el elemento dentro de la función y devolver la dirección, pero no quiero otra alternativa, estoy buscando una explicación)
Pregunta: Aunque funciona, pero ¿cómo function()
realmente devuelve la variable struct y se copia a la variable de items
en main cuando se supone que muere con function()
return?
Seguramente me estoy perdiendo algo importante, la explicación detallada ayudaría. :)
EDITAR: Otras referencias de respuesta:
- https://stackoverflow.com/a/2155742/962545
- Nombrado como optimización del valor de retorno
Los detalles varían ampliamente llamando a la convención. Algunos ABI no tienen convenciones de llamadas para pasar estructuras completas, en cuyo caso el compilador puede hacer lo que crea que tenga sentido.
Ejemplos incluyen:
- Pasar y regresar la estructura completa como una serie de registros consecutivos (a menudo se usa con estructuras "pequeñas")
- Colocando la estructura completa como un bloque de argumentos en la pila
- Asignando un argumento vacío lo suficientemente grande como para contener la estructura, para ser llenado con un valor de retorno
- Pasar la dirección (pila) de la estructura como un argumento (como si la función fuera declarada
void function(struct inventory *)
)
Cualquiera de estas implementaciones podría cumplir con la especificación C aquí. Pero, veamos una implementación específica: la salida de mi compilador cruzado GCC ARM.
La compilación del código que me diste me da esto:
main:
stmfd sp!, {fp, lr}
add fp, sp, #4
sub sp, sp, #48
sub r3, fp, #52
mov r0, r3
bl function(PLT)
Los operandos de destino están siempre a la izquierda. Puede ver que el programa reserva espacio en la pila, luego pasa la dirección del espacio de la pila como r0
(el primer argumento en la convención de llamadas de ARM EABI). function
no toma argumentos, por lo que este argumento es claramente un argumento artificial agregado por nuestro compilador.
function
ve así:
function:
stmfd sp!, {r4, fp, lr}
add fp, sp, #8
sub sp, sp, #36
str r0, [fp, #-40]
ldr r3, .L6
...
add r2, pc, r2
mov r0, r2
mov r1, r3
bl scanf(PLT)
ldr r3, [fp, #-40]
mov ip, r3
sub r4, fp, #36
ldmia r4!, {r0, r1, r2, r3}
stmia ip!, {r0, r1, r2, r3}
ldmia r4, {r0, r1}
stmia ip, {r0, r1}
ldr r0, [fp, #-40]
sub sp, fp, #8
ldmfd sp!, {r4, fp, pc}
Este código básicamente oculta el único argumento en [fp, #-40]
, luego lo carga y comienza a guardar datos en la dirección a la que apunta. Al final, devuelve este valor de puntero en r0
nuevamente. Efectivamente, el compilador ha convertido la firma de la función en
struct inventory *function(struct inventory *)
donde la persona que llama asigna la estructura devuelta en la pila, la pasa y luego la devuelve.
Te estás perdiendo la cosa más obvia que hay en la forma en que C pasa / devuelve cosas: todo se pasa arounbd por valor , o al menos: se comporta de esa manera.
Es decir:
struct foo some_f( void )
{
struct foo local = {
.member = 123,
.bar = 2.0
};
//some awsome code
return local;
}
Funcionará, bien. Si la estructura es pequeña, es posible que este código cree una variable struct local y devuelva una copia de esa estructura a la persona que llama.
En otros casos, sin embargo, este código se traducirá aproximadamente a:
void caller()
{
struct foo hidden_stack_space;
struct foo your_var = *(some_f(&hidden_stack_space));
}
//and the some_f function will behave as:
struct foo * some_f(struct foo * local)
{
//works on local and
return local;
}
Bueno, esto no es Exactamente lo que sucede todo el tiempo , pero se reduce a esto, más o menos. El resultado será el mismo, pero los compiladores pueden comportarse de manera diferente en este caso.
La última línea es: C regresa por valor, por lo que su código funciona bien. Sin embargo, hay trampas:
struct foo
{
int member1;
char *str;
};
struct foo some_f()
{
char bar[] = "foobar";
struct foo local = {
.member1 = 123,
.str = &bar[0]
};
return local;
}
Es peligroso: el puntero asignado a local.str
apunta a la memoria que se liberará una vez que se devuelva la estructura. En ese caso, los problemas que esperaba con este código son ciertos: esa memoria ya no existe (o ya no es válida).
Simplemente porque un puntero es una variable cuyo valor es la dirección de la memoria, y ese valor se devuelve / asigna.
Una estructura, al menos una grande, será asignada y devuelta en la pila, y será extraída de la pila (si es que lo hace) por la persona que llama. El compilador intentará asignarlo en el mismo lugar donde la persona que llama espera encontrarlo, pero hará una copia si eso no es posible. Es posible, pero no necesario, que también haya un puntero a la estructura, devuelto a través de registros.
Por supuesto, los detalles variarán según la arquitectura.