Hoy les estaba enseñando a un par de amigos cómo usar C struct
s. Uno de ellos preguntó si podía devolver un struct
de una función, a lo que respondí: "¡No! En su lugar, devolvería los punteros a malloc
ed dinámicamente struct
".
Viniendo de alguien que principalmente hace C ++, esperaba no poder devolver struct
s por valores. En C ++ puede sobrecargar los operator =
objetos y tiene mucho sentido tener una función para devolver su objeto por valor. En C, sin embargo, no tienes esa opción, por lo que me hizo pensar qué está haciendo realmente el compilador. Considera lo siguiente:
struct MyObj{
double x, y;
};
struct MyObj foo(){
struct MyObj a;
a.x = 10;
a.y = 10;
return a;
}
int main () {
struct MyObj a;
a = foo(); // This DOES work
struct b = a; // This does not work
return 0;
}
Entiendo por struct b = a;
qué no debería funcionar: no puede sobrecargar operator =
su tipo de datos. ¿Cómo es que se a = foo();
compila bien? ¿Significa algo más que struct b = a;
? Tal vez la pregunta que return
debe =
hacerse es: ¿Qué hace exactamente la declaración en conjunto para firmar?
[editar]: Ok, me acaban de señalar que struct b = a
es un error de sintaxis, ¡eso es correcto y soy un idiota! ¡Pero eso lo hace aún más complicado! ¡Usar struct MyObj b = a
sí funciona! ¿Que me estoy perdiendo aqui?
struct b = a;
es un error de sintaxis ¿Y si lo intentasstruct MyObj b = a;
?struct MyObj b = a;
parece funcionar :)Respuestas:
Puede devolver una estructura desde una función (o usar el
=
operador) sin ningún problema. Es una parte bien definida del lenguaje. El único problemastruct b = a
es que no proporcionó un tipo completo.struct MyObj b = a
funcionará bien. También puede pasar estructuras a funciones: una estructura es exactamente la misma que cualquier tipo incorporado para fines de paso de parámetros, valores de retorno y asignación.Aquí hay un programa de demostración simple que hace las tres cosas: pasa una estructura como parámetro, devuelve una estructura de una función y usa estructuras en las declaraciones de asignación:
El siguiente ejemplo es casi exactamente el mismo, pero utiliza el tipo incorporado
int
para fines de demostración. Los dos programas tienen el mismo comportamiento con respecto al paso por valor para el paso de parámetros, asignación, etc.fuente
Al realizar una llamada como
a = foo();
, el compilador puede insertar la dirección de la estructura de resultados en la pila y pasarla como un puntero "oculto" a lafoo()
función. Efectivamente, podría convertirse en algo como:Sin embargo, la implementación exacta de esto depende del compilador y / o plataforma. Como señala Carl Norum, si la estructura es lo suficientemente pequeña, incluso podría devolverse completamente en un registro.
fuente
foo
marco de la pila. Tiene que estar en un lugar que sobreviva más allá del regreso defoo
.foo
. Dentro de la funciónfoo
, solo haces la tarea*r = a
final (efectivamente) haría una copia de la variable local a la variable del llamante. Digo "efectivamente" porque el compilador podría implementar RVO y eliminar la variable local pora
completo.c return struct
: saben que en cdecleax
se devuelve por valor y que las estructuras en general no encajan dentroeax
. Esto es lo que estaba buscando.La
struct b
línea no funciona porque es un error de sintaxis. Si lo expande para incluir el tipo, funcionará bienLo que C está haciendo aquí es esencialmente una
memcpy
estructura de origen al destino. Esto es cierto tanto para la asignación como para el retorno destruct
valores (y realmente cualquier otro valor en C)fuente
memcpy
en este caso, al menos, si la estructura es razonablemente grande.memcpy
función para las situaciones de estructura. Puede hacer un programa de prueba rápida y ver GCC hacerlo, por ejemplo. Para los tipos integrados que no sucederán, no son lo suficientemente grandes como para activar ese tipo de optimización.memcpy
símbolo definido, por lo que a menudo nos encontramos con errores de enlace de "símbolo indefinido" cuando el compilador decide escupir uno solo.Sí, es posible que podamos pasar la estructura y devolver la estructura también. Tenía razón, pero en realidad no pasó el tipo de datos que debería ser como esta estructura MyObj b = a.
En realidad, también llegué a saber cuándo estaba tratando de encontrar una mejor solución para devolver más de un valor para la función sin usar puntero o variable global.
Ahora a continuación se muestra el ejemplo de lo mismo, que calcula la desviación de las calificaciones de un estudiante sobre el promedio.
fuente
Hasta donde puedo recordar, las primeras versiones de C solo permitían devolver un valor que podría caber en un registro de procesador, lo que significa que solo podía devolver un puntero a una estructura. La misma restricción aplicada a los argumentos de la función.
Las versiones más recientes permiten pasar objetos de datos más grandes como estructuras. Creo que esta característica ya era común durante los años ochenta o principios de los noventa.
Sin embargo, las matrices todavía se pueden pasar y devolver solo como punteros.
fuente
Usted puede asignar estructuras en C
a = b;
es una sintaxis válida.Simplemente dejó parte del tipo, la etiqueta de estructura, en su línea que no funciona.
fuente
No hay problema en devolver una estructura. Será pasado por valor
Pero, ¿qué pasa si la estructura contiene algún miembro que tenga una dirección de una variable local?
Ahora, aquí e1.name contiene una dirección de memoria local para la función get (). Una vez que get () regresa, la dirección local para el nombre se habría liberado. Por lo tanto, en la persona que llama si intentamos acceder a esa dirección, puede causar un error de segmentación, ya que estamos intentando una dirección liberada. Eso es malo..
Donde como e1.id será perfectamente válido ya que su valor se copiará a e2.id
Por lo tanto, siempre debemos tratar de evitar devolver las direcciones de memoria local de una función.
Cualquier cosa mal colocada se puede devolver cuando lo desee
fuente
funciona bien con versiones más recientes de compiladores. Al igual que id, el contenido del nombre se copia en la variable de estructura asignada.
fuente
la dirección struct var e2 se empuja como arg a la pila llamada y los valores se asignan allí. De hecho, get () devuelve la dirección de e2 en eax reg. Esto funciona como llamada por referencia.
fuente