Повертаємо `struct` з функції в C


171

Сьогодні я навчав пару друзів, як користуватися C structs. Один із них запитав, чи можете ви повернути функцію structз функції, на що я відповів: "Ні! Натомість ви повернете вказівники на динамічне mallocредагування structs".

Походячи від того, хто в основному робить C ++, я очікував, що не зможу повернути structзначення за значеннями. У 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справді працює! Що я тут пропускаю?


23
struct b = a;- синтаксична помилка. Що робити, якщо спробувати struct MyObj b = a;?
Грег Хьюгілл

2
@GregHewgill: Ви абсолютно праві. Цікаво, однак, struct MyObj b = a;здається, що це працює :)
mmirzadeh

Відповіді:


199

Ви можете повернути структуру з функції (або використовувати =оператор) без проблем. Це чітко визначена частина мови. Єдина проблема в 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;
}

14
Це досить цікаво. Я завжди мав враження, що вам потрібні покажчики. Я помилявся :)
mmirzadeh

8
Вам точно не потрібні покажчики. Зважаючи на це, більшу частину часу ви хочете використовувати їх - неявні копії пам'яті, які відбуваються навколо за значенням, розгортаючи структури, можуть бути справжньою тратою циклів процесора, не кажучи вже про пропускну здатність пам'яті.
Карл Норум

10
@CarlNorum наскільки великою має бути структура, щоб копія коштувала дорожче, ніж malloc + free?
josefx

7
@josefx, одна копія? Напевно, величезна. Річ у тім, як правило, якщо ви передаєте структури за значенням, ви їх багато копіюєте . У будь-якому випадку це насправді не так просто. Ви можете проходити навколо місцевих чи глобальних структур, і в такому випадку вартість їх виділення є майже безкоштовною.
Карл Норум

7
Вам потрібні покажчики та розподіл пам'яті для повернутого значення поза тілом функції, як тільки обсяг пам'яті, виділений для значення, не відомий під час компіляції. Це для structs, тому функції 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. Це те, що я шукав.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

14

struct bЛінія не працює , тому що це помилка синтаксису. Якщо розгорнути його, щоб включити тип, він буде працювати чудово

struct MyObj b = a;  // Runs fine

Що тут робить C - це по суті, memcpyвід вихідної структури до місця призначення. Це справедливо як для присвоєння, так і для повернення structзначень (і справді для кожного іншого значення в C)


+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 () {...} Масив не можна повернути, навіть якщо розмір відомий.
Джорджіо

Чи може пояснювач пояснити причину пониження? Якщо моя відповідь містить неправильну інформацію, я би радий її виправити.
Джорджо

4

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

Ви просто залишили частину типу - тег Stru - у рядку, який не працює.


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

прекрасно працює з новішими версіями компіляторів. Як і id, вміст імені копіюється в призначену змінну структури.


1
Ще простіше: struct emp get () {return {100, "john"}; }
Кріс Рейд

1

address var e2-адреса, висунута як arg для стека виклику, і значення присвоюються там. Насправді, get () повертає адресу e2 в eax reg. Це працює як дзвінок за посиланням.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.