Retorna um `struct` de uma function em C

Hoje eu estava ensinando alguns amigos a usar C struct s. Um deles perguntou se você poderia retornar uma struct de uma function, à qual eu respondi: “Não! Você retornaria pointers para malloc dynamics em vez disso.”

Vindo de alguém que faz principalmente C ++, eu esperava não ser capaz de retornar struct s por valores. Em C ++ você pode sobrecarregar o operator = para seus objects e faz todo o sentido ter uma function para retornar seu object por valor. Em C, no entanto, você não tem essa opção e isso me fez pensar no que o compilador está realmente fazendo. Considere o seguinte:

 struct MyObj{ double x, y; }; struct MyObj foo(){ struct MyObj a; ax = 10; ay = 10; return a; } int main () { struct MyObj a; a = foo(); // This DOES work struct b = a; // This does not work return 0; } 

Eu entendo porque struct b = a; não deve funcionar – você não pode sobrecarregar operator = para seu tipo de dados. Como é que a = foo(); compila bem? Significa algo diferente de struct b = a; ? Talvez a pergunta a fazer seja: O que exatamente a declaração de return em conjunto com o sinal = faz?

[edit]: Ok, eu estava apenas apontado struct b = a é um erro de syntax – isso é correto e eu sou um idiota! Mas isso torna tudo ainda mais complicado! Usando struct MyObj b = a realmente funciona! O que estou perdendo aqui?

Você pode retornar uma estrutura de uma function (ou usar o operador = ) sem problemas. É uma parte bem definida da linguagem. O único problema com a struct b = a é que você não forneceu um tipo completo. struct MyObj b = a funcionará bem. Você também pode passar estruturas para funções – uma estrutura é exatamente igual a qualquer tipo interno para fins de passagem de parâmetros, valores de retorno e atribuição.

Aqui está um programa de demonstração simples que faz todos os três – passa uma estrutura como um parâmetro, retorna uma estrutura de uma function e usa estruturas em instruções de atribuição:

 #include  struct a { int i; }; struct af(struct ax) { struct ar = x; return r; } int main(void) { struct ax = { 12 }; struct ay = f(x); printf("%d\n", yi); return 0; } 

O próximo exemplo é exatamente o mesmo, mas usa o tipo int interno para fins de demonstração. Os dois programas têm o mesmo comportamento em relação a passagem por valor para passagem de parâmetros, atribuição, etc .:

 #include  int f(int x) { int r = x; return r; } int main(void) { int x = 12; int y = f(x); printf("%d\n", y); return 0; } 

Ao fazer uma chamada como a = foo(); , o compilador pode enviar o endereço da estrutura de resultados na pilha e passá-lo como um ponteiro “oculto” para a function foo() . Efetivamente, poderia se tornar algo como:

 void foo(MyObj *r) { struct MyObj a; // ... *r = a; } foo(&a); 

No entanto, a implementação exata disso depende do compilador e / ou da plataforma. Como Carl Norum observa, se a estrutura for pequena o suficiente, ela pode até ser passada completamente de volta em um registrador.

A linha struct b não funciona porque é um erro de syntax. Se você expandi-lo para include o tipo, ele funcionará bem

 struct MyObj b = a; // Runs fine 

O que C está fazendo aqui é essencialmente um memcpy da estrutura de origem para o destino. Isto é verdade tanto para a atribuição quanto para o retorno de valores de struct (e realmente todos os outros valores em C)

sim, é possível que possamos passar estrutura e estrutura de retorno também. Você estava certo, mas na verdade você não passou o tipo de dados que deveria ser como este struct MyObj b = a.

Na verdade eu também vim a saber quando estava tentando descobrir uma solução melhor para retornar mais de um valor para a function sem usar o ponteiro ou a variável global.

Agora abaixo está o exemplo para o mesmo, que calcula o desvio de um aluno marca sobre a média.

 #include struct marks{ int maths; int physics; int chem; }; struct marks deviation(struct marks student1 , struct marks student2 ); int main(){ struct marks student; student.maths= 87; student.chem = 67; student.physics=96; struct marks avg; avg.maths= 55; avg.chem = 45; avg.physics=34; //struct marks dev; struct marks dev= deviation(student, avg ); printf("%d %d %d" ,dev.maths,dev.chem,dev.physics); return 0; } struct marks deviation(struct marks student , struct marks student2 ){ struct marks dev; dev.maths = student.maths-student2.maths; dev.chem = student.chem-student2.chem; dev.physics = student.physics-student2.physics; return dev; } 

Você pode atribuir estruturas em C. a = b; é uma syntax válida.

Você simplesmente deixou de fora parte do tipo – a tag struct – em sua linha que não funciona.

Tanto quanto me lembro, as primeiras versões de C só permitiam retornar um valor que poderia caber em um registrador de processador, o que significa que você só poderia retornar um ponteiro para uma estrutura. A mesma restrição aplicada aos argumentos da function.

Versões mais recentes permitem passar objects de dados maiores, como structs. Eu acho que esse recurso já era comum durante os anos oitenta ou início dos anos noventa.

Matrizes, no entanto, ainda podem ser passadas e retornadas apenas como pointers.

Não há problema em passar de volta uma estrutura. Será passado por valor

Mas, e se a estrutura contiver algum membro que tenha um endereço de uma variável local

 struct emp { int id; char *name; }; struct emp get() { char *name = "John"; struct emp e1 = {100, name}; return (e1); } int main() { struct emp e2 = get(); printf("%s\n", e2.name); } 

Agora, aqui e1.name contém um endereço de memory local para a function get (). Uma vez que get () retorna, o endereço local para o nome teria sido liberado. Portanto, no chamador, se tentarmos acessar esse endereço, isso pode causar falha de segmentação, pois estamos tentando um endereço liberado. Isso é mau..

Onde como o e1.id será perfeitamente válido como seu valor será copiado para e2.id

Portanto, devemos sempre tentar evitar o retorno de endereços de memory locais de uma function.

Qualquer coisa maltratada pode ser devolvida como e quando quiser

 struct emp { int id; char *name; }; struct emp get() { char *name = "John"; struct emp e1 = {100, name}; return (e1); } int main() { struct emp e2 = get(); printf("%s\n", e2.name); } 

funciona bem com novas versões de compiladores. Assim como id, o conteúdo do nome é copiado para a variável de estrutura atribuída.