Kembalikan `struct` dari fungsi di C

171

Hari ini saya mengajar beberapa teman bagaimana cara menggunakan Cs struct. Salah satu dari mereka bertanya apakah Anda dapat mengembalikan sebuah structfungsi, yang saya jawab: "Tidak! Anda akan mengembalikan pointer ke malloced secara dinamis struct."

Berasal dari seseorang yang terutama melakukan C ++, saya berharap tidak dapat mengembalikan structnilai s. Dalam C ++ Anda bisa membebani operator =objek Anda secara berlebihan dan masuk akal untuk memiliki fungsi untuk mengembalikan objek Anda berdasarkan nilai. Namun, dalam C, Anda tidak memiliki opsi itu dan itu membuat saya berpikir apa yang sebenarnya dilakukan oleh kompiler. Pertimbangkan yang berikut ini:

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

Saya mengerti mengapa struct b = a;tidak bekerja - Anda tidak bisa kelebihan operator =untuk tipe data Anda. Bagaimana a = foo();kompilasi itu baik-baik saja? Apakah ini berarti sesuatu selain struct b = a;? Mungkin pertanyaan yang ingin diajukan adalah: Apa yang sebenarnya dilakukan oleh returnpernyataan bersama =?

[Sunting]: Ok, saya hanya menunjuk struct b = aadalah kesalahan sintaksis - itu benar dan saya idiot! Tapi itu membuatnya semakin rumit! Penggunaan struct MyObj b = amemang berhasil! Apa yang kulewatkan di sini?

mmirzadeh
sumber
24
struct b = a;adalah kesalahan sintaksis. Bagaimana jika Anda mencoba struct MyObj b = a;?
Greg Hewgill
2
@GregHewgill: Anda benar sekali. Cukup menarik, bagaimanapun, struct MyObj b = a;tampaknya berhasil :)
mmirzadeh

Jawaban:

200

Anda dapat mengembalikan struktur dari suatu fungsi (atau menggunakan =operator) tanpa masalah. Ini adalah bagian bahasa yang didefinisikan dengan baik. Satu-satunya masalah struct b = aadalah Anda tidak memberikan jenis yang lengkap. struct MyObj b = aakan bekerja dengan baik. Anda juga bisa meneruskan struktur ke fungsi - struktur persis sama dengan tipe bawaan apa pun untuk keperluan pemindahan parameter, nilai pengembalian, dan penugasan.

Berikut adalah program demonstrasi sederhana yang melakukan ketiganya - melewatkan struktur sebagai parameter, mengembalikan struktur dari suatu fungsi, dan menggunakan struktur dalam pernyataan penugasan:

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

Contoh berikutnya hampir persis sama, tetapi menggunakan inttipe bawaan untuk tujuan demonstrasi. Kedua program memiliki perilaku yang sama sehubungan dengan pass-by-value untuk passing parameter, tugas, dll .:

#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
sumber
14
Cukup menarik. Saya selalu di bawah kesan Anda perlu petunjuk untuk ini. Saya salah :)
mmirzadeh
8
Anda tentu tidak perlu petunjuk. Yang mengatakan, sebagian besar waktu Anda ingin menggunakannya - salinan memori implisit yang terjadi melempar struktur sekitar berdasarkan nilai dapat menjadi pemborosan siklus CPU, belum lagi bandwidth memori.
Carl Norum
10
@CarlNorum berapa besar struktur harus mendapatkan bahwa biaya salinan lebih dari malloc + gratis?
josefx
7
@ josefx, satu salinan? Mungkin sangat besar. Masalahnya, biasanya jika Anda melewati struktur sekitar dengan nilai Anda menyalin mereka banyak . Bagaimanapun itu tidak sesederhana itu. Anda dapat melewati struktur lokal atau global, dalam hal ini biaya alokasi tidak banyak gratis.
Carl Norum
7
Anda memerlukan pointer dan alokasi memori untuk nilai yang dikembalikan di luar fungsi fungsi segera setelah jumlah memori yang dialokasikan untuk nilai tidak diketahui pada waktu kompilasi. Ini untuk struct, jadi fungsi C tidak memiliki masalah mengembalikannya.
reinierpost
33

Saat melakukan panggilan seperti itu a = foo();, kompiler mungkin mendorong alamat struktur hasil pada stack dan meneruskannya sebagai pointer "tersembunyi" ke foo()fungsi. Secara efektif, itu bisa menjadi sesuatu seperti:

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

foo(&a);

Namun, implementasi yang tepat dari ini tergantung pada kompiler dan / atau platform. Seperti yang dicatat oleh Carl Norum, jika strukturnya cukup kecil, itu mungkin bahkan dikembalikan sepenuhnya dalam register.

Greg Hewgill
sumber
11
Itu sepenuhnya tergantung pada implementasi. Sebagai contoh, armcc akan melewati struktur cukup kecil di register lewat parameter biasa (atau nilai pengembalian).
Carl Norum
Bukankah itu akan mengembalikan pointer ke variabel lokal? Memori untuk struktur yang dikembalikan tidak dapat menjadi bagian dari foobingkai tumpukan. Itu harus di tempat yang bertahan melewati kembalinya foo.
Anders Abel
@AndersAbel: Saya pikir apa yang dimaksud Greg adalah kompilator mengambil pointer ke variabel di fungsi utama dan meneruskannya ke fungsi foo. Di dalam fungsi foo, Anda hanya melakukan tugas
mmirzadeh
4
@AndersAbel: Pada *r = aakhirnya akan (secara efektif) melakukan salinan variabel lokal ke variabel pemanggil. Saya mengatakan "secara efektif" karena kompiler dapat mengimplementasikan RVO dan menghilangkan variabel lokal asepenuhnya.
Greg Hewgill
3
Meskipun ini tidak langsung menjawab pertanyaan, ini adalah alasan mengapa banyak orang akan jatuh di sini melalui google c return struct: mereka tahu bahwa dalam cdecl eaxdikembalikan oleh nilai dan struct pada umumnya tidak cocok di dalam eax. Ini yang saya cari.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
14

The struct bgaris tidak bekerja karena itu kesalahan sintaks. Jika Anda mengembangkannya untuk memasukkan tipe itu akan berfungsi dengan baik

struct MyObj b = a;  // Runs fine

Apa yang dilakukan C di sini pada dasarnya adalah memcpydari struct sumber ke tujuan. Ini berlaku untuk penugasan dan pengembalian structnilai (dan benar-benar setiap nilai lain dalam C)

JaredPar
sumber
+1, pada kenyataannya, banyak kompiler akan memancarkan panggilan literal memcpydalam kasus ini - setidaknya, jika strukturnya cukup besar.
Carl Norum
Jadi, selama inisialisasi tipe data, fungsi memcpy berfungsi ??
bhuwansahni
1
@ bhuwansahni Saya tidak yakin apa yang Anda minta di sini. Bisakah Anda menguraikan sedikit?
JaredPar
4
@JaredPar - kompiler sering harfiah memanggil para memcpyfungsi untuk situasi struktur. Anda dapat membuat program pengujian cepat dan melihat GCC melakukannya, misalnya. Untuk tipe bawaan yang tidak akan terjadi - mereka tidak cukup besar untuk memicu optimasi semacam itu.
Carl Norum
3
Sangat mungkin untuk mewujudkannya - proyek yang sedang saya kerjakan tidak memiliki memcpysimbol yang ditentukan, jadi kami sering mengalami kesalahan linker "simbol tidak terdefinisi" ketika kompiler memutuskan untuk memuntahkannya sendiri.
Carl Norum
9

ya, mungkin kita bisa melewati struktur dan mengembalikan struktur juga. Anda benar tetapi sebenarnya Anda tidak melewati tipe data yang seharusnya seperti struct ini MyObj b = a.

Sebenarnya saya juga mengetahui ketika saya mencoba mencari solusi yang lebih baik untuk mengembalikan lebih dari satu nilai untuk fungsi tanpa menggunakan pointer atau variabel global.

Sekarang di bawah ini adalah contoh untuk hal yang sama, yang menghitung penyimpangan nilai siswa tentang rata-rata.

#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;
}
Seorang pria
sumber
5

Sejauh yang saya ingat, versi pertama C hanya diperbolehkan untuk mengembalikan nilai yang bisa masuk ke dalam register prosesor, yang berarti bahwa Anda hanya bisa mengembalikan pointer ke struct. Pembatasan yang sama diterapkan pada argumen fungsi.

Versi yang lebih baru memungkinkan untuk mengedarkan objek data yang lebih besar seperti struct. Saya pikir fitur ini sudah umum selama tahun delapan puluhan atau awal sembilan puluhan.

Array, bagaimanapun, masih bisa dilewati dan dikembalikan hanya sebagai pointer.

Giorgio
sumber
Anda bisa mengembalikan array dengan nilai jika Anda memasukkannya ke dalam struct. Yang tidak bisa Anda kembalikan berdasarkan nilainya adalah array panjang variabel.
han
1
Ya, saya bisa meletakkan array di dalam sebuah struct, tapi saya tidak bisa mis tulis typedef char arr [100]; arr foo () {...} Array tidak dapat dikembalikan, bahkan jika ukurannya diketahui.
Giorgio
Bisakah sang downvoter menjelaskan alasan downvote? Jika jawaban saya mengandung informasi yang salah, saya akan senang memperbaikinya.
Giorgio
4

Anda dapat menetapkan struct dalam C. a = b; sintaks yang valid.

Anda cukup meninggalkan bagian dari jenis - tag struct - di baris Anda yang tidak berfungsi.

DigitalRoss
sumber
4

Tidak ada masalah dalam melewati kembali struct. Itu akan diteruskan oleh nilai

Tetapi, bagaimana jika struct berisi anggota yang memiliki alamat variabel lokal

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

Sekarang, di sini e1.name berisi alamat memori lokal ke fungsi get (). Setelah mendapatkan () kembali, alamat lokal untuk nama akan dibebaskan. SO, di pemanggil jika kami mencoba mengakses alamat itu, itu dapat menyebabkan kesalahan segmentasi, karena kami mencoba alamat yang dibebaskan. Itu buruk..

Sedangkan e1.id akan benar-benar valid karena nilainya akan disalin ke e2.id

Jadi, kita harus selalu berusaha menghindari mengembalikan alamat memori lokal suatu fungsi.

Apa pun yang ditransfer dapat dikembalikan kapan saja

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

berfungsi dengan baik dengan versi kompiler yang lebih baru. Sama seperti id, konten nama akan disalin ke variabel struktur yang ditugaskan.

saroj panda
sumber
1
Lebih sederhana: struct emp get () {return {100, "john"}; }
Chris Reid
1

alamat struct var e2 didorong sebagai arg to callee stack dan nilai ditugaskan di sana. Bahkan, get () mengembalikan alamat e2 di eax reg. Ini berfungsi seperti panggilan dengan referensi.

Bala
sumber