Retorna um `struct` de uma função em C

171

Hoje eu estava ensinando alguns amigos a usar C structs. Um deles perguntou se você pudesse voltar a structpartir de uma função, ao que eu respondi: "Não! Você iria retornar ponteiros para dinamicamente malloced struct. S em vez"

Vindo de alguém que faz principalmente C ++, eu esperava não conseguir retornar structs por valores. No C ++, você pode sobrecarregar os operator =objetos e faz todo sentido ter uma função para retornar seu objeto por valor. Em C, no entanto, você não tem essa opção e, por 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;

    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;
}    

Entendo por struct b = a;que não deve funcionar - você não pode sobrecarregar operator =seu tipo de dados. Como é que a = foo();compila bem? Isso significa algo diferente struct b = a;? Talvez a pergunta a fazer seja: O que exatamente faz a returndeclaração em conjunto para =assinar?

[edit]: Ok, acabei de apontar que struct b = aé um erro de sintaxe - isso é correto e eu sou um idiota! Mas isso torna ainda mais complicado! Usar struct MyObj b = arealmente funciona! O que estou perdendo aqui?

mmirzadeh
fonte
24
struct b = a;é um erro de sintaxe. E se você tentar struct MyObj b = a;?
Greg Hewgill
2
@ GregHewgill: Você está absolutamente certo. Muito interessante, no entanto, struct MyObj b = a;parece funcionar :)
mmirzadeh

Respostas:

200

Você pode retornar uma estrutura de uma função (ou usar o =operador) sem problemas. É uma parte bem definida da linguagem. O único problema struct b = aé que você não forneceu um tipo completo. struct MyObj b = avai funcionar muito 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 executa todos os três - passa uma estrutura como parâmetro, retorna uma estrutura de uma função e usa estruturas nas instruções de atribuição:

#include <stdio.h>

struct a {
   int i;
};

struct a f(struct a x)
{
   struct a r = x;
   return r;
}

int main(void)
{
   struct a x = { 12 };
   struct a y = f(x);
   printf("%d\n", y.i);
   return 0;
}

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

#include <stdio.h>

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;
}
Carl Norum
fonte
14
Isso é bem interessante. Eu sempre tive a impressão de que você precisa de indicadores para isso. Eu estava errado :) #
3100 mmirzadeh
8
Você certamente não precisa de ponteiros. Dito isto, na maioria das vezes você gostaria de usá-las - as cópias implícitas de memória que ocorrem lançando estruturas por valor podem ser um desperdício real de ciclos da CPU, sem mencionar a largura de banda da memória.
Carl Norum
10
@CarlNorum, qual o tamanho de uma estrutura para obter uma cópia que custa mais do que malloc + free?
josefx
7
@josefx, uma única cópia? Provavelmente enorme. O problema é que, normalmente, se você passa estruturas por valor, as copia bastante . Enfim, não é tão simples assim. Você pode estar passando por estruturas locais ou globais; nesse caso, seu custo de alocação é praticamente gratuito.
Carl Norum
7
Você precisa de ponteiros e alocação de memória para o valor retornado fora do corpo da função assim que a quantidade de memória alocada para um valor não for conhecida no momento da compilação. É para estruturas, portanto, as funções C não têm nenhum problema em devolvê-las.
Reinierpost
33

Ao fazer uma chamada como a = foo();, o compilador pode enviar o endereço da estrutura de resultados para a pilha e transmiti-lo como um ponteiro "oculto" para a foo()função. Efetivamente, pode 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 plataforma. Como observa Carl Norum, se a estrutura é pequena o suficiente, ela pode até ser devolvida completamente em um registro.

Greg Hewgill
fonte
11
Isso depende totalmente da implementação. Por exemplo, o armcc passará estruturas pequenas o suficiente nos registros regulares de passagem de parâmetro (ou valor de retorno).
Carl Norum
Isso não retornaria um ponteiro para uma variável local? A memória da estrutura retornada não pode fazer parte do fooquadro da pilha. Tem que estar em um lugar que sobreviva após o retorno de foo.
Anders Abel
@AndersAbel: Acho que o que Greg quer dizer é que o compilador pega um ponteiro para a variável na função principal e o passa para a função foo. Dentro da função foo, você acabou de fazer a atribuição
mmirzadeh
4
@AndersAbel: O *r = afinal faria (efetivamente) uma cópia da variável local na variável do chamador. Eu digo "efetivamente" porque o compilador pode implementar o RVO e eliminar acompletamente a variável local .
Greg Hewgill 11/03/12
3
Embora isso não responda diretamente à pergunta, essa é a razão pela qual muitas pessoas caem aqui via google c return struct: elas sabem que no cdecl eaxé retornado por valor e que as estruturas em geral não se encaixam eax. Era isso que eu estava procurando.
Ciro Santilli publicou 22/08/13
14

A struct blinha não funciona porque é um erro de sintaxe. Se você expandi-lo para incluir o tipo, ele funcionará bem

struct MyObj b = a;  // Runs fine

O que C está fazendo aqui é essencialmente memcpyda estrutura de origem até o destino. Isso é verdade para a atribuição e o retorno de structvalores (e realmente para todos os outros valores em C)

JaredPar
fonte
+1, de fato, muitos compiladores realmente emitem uma chamada literal memcpynesse caso - pelo menos, se a estrutura for razoavelmente grande.
Carl Norum
Então, durante a inicialização de um tipo de dados, a função memcpy funciona?
bhuwansahni
1
@bhuwansahni Não sei bem o que você está perguntando aqui. Você poderia elaborar um pouco?
JaredPar
4
@JaredPar - os compiladores costumam chamar literalmente a memcpyfunção para as situações de estrutura. Você pode fazer um programa de teste rápido e ver o GCC fazer isso, por exemplo. Para tipos internos que não acontecem - eles não são grandes o suficiente para acionar esse tipo de otimização.
Carl Norum
3
Definitivamente, é possível que isso aconteça - o projeto no qual estou trabalhando não tem um memcpysímbolo definido, por isso, muitas vezes, encontramos erros de vinculador de "símbolo indefinido" quando o compilador decide cuspir um por conta própria.
Carl Norum
9

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

Na verdade, também fiquei sabendo quando estava tentando encontrar uma solução melhor para retornar mais de um valor para a função sem usar ponteiro ou variável global.

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

#include<stdio.h>
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;
}
Um homem
fonte
5

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

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

As matrizes, no entanto, ainda podem ser passadas e retornadas apenas como ponteiros.

Giorgio
fonte
Você pode retornar uma matriz por valor se a colocar dentro de uma estrutura. O que você não pode retornar por valor é uma matriz de comprimento variável.
han
1
Sim, eu posso colocar uma matriz dentro de uma estrutura, mas não posso, por exemplo, escrever typedef char arr [100]; arr foo () {...} Uma matriz não pode ser retornada, mesmo que o tamanho seja conhecido.
Giorgio
O voto negativo poderia explicar o motivo do voto negativo? Se minha resposta contiver informações incorretas, eu ficaria feliz em corrigi-las.
Giorgio
4

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

Você simplesmente deixou de lado parte do tipo - a tag struct - na sua linha que não funciona.

DigitalRoss
fonte
4

Não há problema em devolver uma estrutura. Será passado por valor

Mas, e se a estrutura contiver algum membro que tenha o 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 memória local para a função get (). Quando get () retornar, o endereço local do nome teria sido liberado. Portanto, no chamador, se tentarmos acessar esse endereço, isso poderá causar uma falha de segmentação, pois estamos tentando um endereço liberado. Isso é mau..

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

Portanto, devemos sempre tentar evitar o retorno de endereços de memória local de uma função.

Qualquer coisa alocada pode ser devolvida como e quando desejado

Jagan
fonte
2
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 versões mais recentes de compiladores. Assim como o id, o conteúdo do nome é copiado para a variável de estrutura atribuída.

saroj panda
fonte
1
Ainda mais simples: struct emp get () {return {100, "john"}; }
Chris Reid
1

struct var e2 endereço enviado como arg para chamar a pilha e os valores são atribuídos lá. De fato, get () retorna o endereço de e2 em eax reg. Isso funciona como chamada por referência.

Bala
fonte