Вернуть `struct` из функции в C

171

Сегодня я учил пару друзей, как использовать C structs. Один из них спросил, можете ли вы вернуть a structиз функции, на что я ответил: «Нет! Вместо этого вы бы возвращали указатели для динамически mallocсоздаваемых structs».

Исходя из того, кто в основном работает на C ++, я ожидал, что не смогу вернуть structs по значениям. В C ++ вы можете перегружать operator =свои объекты и имеет полный смысл иметь функцию, которая возвращает ваш объект по значению. В C, однако, у вас нет такой опции, и поэтому я подумал, что на самом деле делает компилятор. Учтите следующее:

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

Я понимаю, почему struct b = a;не должно работать - вы не можете перегрузить operator =для вашего типа данных. Как это хорошо, что a = foo();компилируется? Это означает что-то кроме struct b = a;? Может быть, вопрос, который нужно задать, состоит в следующем: что именно делает returnзаявление в сочетании со =знаком?

[править]: Хорошо, я только что указал struct b = aна синтаксическую ошибку - это правильно, и я идиот! Но это делает это еще более сложным! Использование struct MyObj b = aдействительно работает! Что мне здесь не хватает?

mmirzadeh
источник
24
struct b = a;это синтаксическая ошибка Что если ты попробуешь struct MyObj b = a;?
Грег Хьюгилл
2
@GregHewgill: Вы абсолютно правы. Довольно интересно, однако, struct MyObj b = a;кажется, работает :)
mmirzadeh

Ответы:

200

Вы можете вернуть структуру из функции (или использовать =оператор) без каких-либо проблем. Это четко определенная часть языка. Единственная проблема в struct b = aтом, что вы не указали полный тип. struct MyObj b = aбудет работать просто отлично. Вы также можете передавать структуры в функции - структура точно такая же, как и любой встроенный тип для целей передачи параметров, возвращаемых значений и назначения.

Вот простая демонстрационная программа, которая выполняет все три операции - передает структуру в качестве параметра, возвращает структуру из функции и использует структуры в операторах присваивания:

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

Следующий пример почти такой же, но использует встроенный intтип для демонстрационных целей. Две программы имеют одинаковое поведение в отношении передачи по значению для передачи параметров, назначения и т. Д .:

#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;
}
Карл Норум
источник
15
Это довольно интересно. У меня всегда было впечатление, что для этого нужны указатели. Я был не прав :)
mmirzadeh
8
Вам, конечно, не нужны указатели. Тем не менее, большую часть времени вы захотите использовать их - неявные копии памяти, которые имеют разнесенные структуры по значению, могут быть настоящей тратой циклов ЦП, не говоря уже о пропускной способности памяти.
Карл Норум
10
@CarlNorum, насколько велика структура, чтобы копия стоила дороже, чем malloc + free?
josefx
7
@josefx, единственная копия? Наверное, огромный. Дело в том, как правило , если вы передаете структуры вокруг по значению вы копируете их много . Во всяком случае, это не так просто. Вы можете обойти локальные или глобальные структуры, и в этом случае стоимость размещения в трети будет в значительной степени бесплатной.
Карл Норум
7
Вам понадобятся указатели и выделение памяти для возвращаемого значения вне тела функции, как только объем памяти, выделенный для значения, не будет известен во время компиляции. Это для структур, поэтому функции C без проблем возвращают их.
reinierpost
33

При выполнении вызова, такого как a = foo();, компилятор может поместить адрес структуры результата в стек и передать его как «скрытый» указатель на foo()функцию. По сути, это может стать что-то вроде:

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

foo(&a);

Однако точная реализация этого зависит от компилятора и / или платформы. Как отмечает Карл Норум, если структура достаточно мала, она может быть полностью передана обратно в регистр.

Грег Хьюгилл
источник
11
Это полностью зависит от реализации. Например, armcc будет передавать достаточно маленькие структуры в регистры регулярной передачи параметров (или возвращаемого значения).
Карл Норум
Разве это не возвращало бы указатель на локальную переменную? Память для возвращаемой структуры не может быть частью fooкадра стека. Это должно быть в месте, которое выживает после возвращения foo.
Андерс Абель
@AndersAbel: Я думаю, что Грег имеет в виду, что компилятор берет указатель на переменную в главной функции и передает ее функции foo. Внутри функции fooвы просто выполняете задание
mmirzadeh
4
@AndersAbel: В *r = aконце (эффективно) будет скопирована локальная переменная в переменную вызывающего. Я говорю «эффективно», потому что компилятор может реализовать RVO и полностью исключить локальную переменную a.
Грег Хьюгилл
3
Хотя это не дает прямого ответа на этот вопрос, это причина, по которой многие люди попадают сюда через google c return struct: они знают, что в cdecl eaxвозвращается значение и что структуры вообще не вписываются внутрь eax. Это то, что я искал.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
14

struct bЛиния не работает , потому что это ошибка синтаксиса. Если вы расширите его, включив тип, он будет работать нормально

struct MyObj b = a;  // Runs fine

То, что делает здесь C, по сути является структурой memcpyот источника до места назначения. Это верно как для присваивания, так и для возврата structзначений (и действительно для любого другого значения в C)

JaredPar
источник
+1, на самом деле, многие компиляторы фактически выдают буквальный вызов memcpyв этом случае - по крайней мере, если структура достаточно велика.
Карл Норум
Итак, во время инициализации типа данных функция memcpy работает ??
bhuwansahni
1
@bhuwansahni Я не совсем уверен, что вы здесь спрашиваете. Не могли бы вы уточнить немного?
JaredPar
4
@JaredPar - составители часто буквально называют в memcpyфункцию структурных ситуаций. Например, вы можете создать программу быстрого тестирования и посмотреть, как это делает GCC. Для встроенных типов этого не произойдет - они недостаточно велики для запуска такого рода оптимизации.
Карл Норум
3
Это определенно возможно сделать - в проекте, над которым я работаю, memcpyсимвол не определен, поэтому мы часто сталкиваемся с ошибками компоновщика «неопределенный символ», когда компилятор решает выпустить его самостоятельно.
Карл Норум
9

да, возможно, мы можем передать структуру и вернуть структуру. Вы были правы, но на самом деле вы не передали тип данных, который должен быть таким, как эта структура MyObj b = a.

На самом деле, я также узнал, когда пытался найти лучшее решение для возврата более одного значения функции без использования указателя или глобальной переменной.

Теперь ниже приведен пример для того же, который рассчитывает отклонение оценок ученика от среднего.

#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;
}
Мужчина
источник
5

Насколько я помню, первые версии C позволяли возвращать только те значения, которые могли бы поместиться в регистр процессора, что означает, что вы могли возвращать только указатель на структуру. То же ограничение применяется к аргументам функции.

Более поздние версии позволяют передавать большие объекты данных, такие как структуры. Я думаю, что эта функция была распространена уже в восьмидесятых или начале девяностых.

Массивы, однако, все еще могут быть переданы и возвращены только как указатели.

Джорджио
источник
Вы можете вернуть массив по значению, если поместите его в структуру. То, что вы не можете вернуть по значению, это массив переменной длины.
хан
1
Да, я могу поместить массив в структуру, но я не могу, например, написать typedef char arr [100]; arr foo () {...} Невозможно вернуть массив, даже если размер известен.
Джорджио
Может ли downvoter объяснить причину downvote? Если мой ответ содержит неверную информацию, я был бы рад исправить это.
Джорджио
4

Вы можете назначить структуры в C. a = b; допустимый синтаксис.

Вы просто удалили часть типа - тег struct - в своей строке, которая не работает.

DigitalRoss
источник
4

Нет проблем при передаче структуры. Будет передано по значению

Но что, если структура содержит какой-либо член, который имеет адрес локальной переменной

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

Теперь здесь e1.name содержит адрес памяти, локальный для функции get (). Как только get () вернет, локальный адрес для имени будет освобожден. Итак, в вызывающей стороне, если мы пытаемся получить доступ к этому адресу, это может вызвать ошибку сегментации, так как мы пытаемся освободить адрес. Это плохо..

Где e1.id будет совершенно действительным, так как его значение будет скопировано в e2.id

Таким образом, мы всегда должны стараться не возвращать адреса локальной памяти функции.

Все, что может быть доставлено, может быть возвращено как и когда угодно

Джагана
источник
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);
}

прекрасно работает с новыми версиями компиляторов. Как и идентификатор, содержимое имени копируется в назначенную структурную переменную.

сарой панда
источник
1
Еще проще: struct emp get () {return {100, "john"}; }
Крис Рид
1

Адрес struct var e2 помещается как arg в стек вызываемого абонента, и там присваиваются значения. Фактически, get () возвращает адрес e2 в регистре eax. Это работает как звонок по ссылке.

Bala
источник