ส่งกลับ `struct 'จากฟังก์ชันใน C


171

วันนี้ฉันสอนเพื่อนสองสามคนถึงวิธีใช้ C structs หนึ่งของพวกเขาถามว่าคุณจะกลับstructจากฟังก์ชั่นเพื่อที่ฉันตอบว่า: "ไม่มีคุณจะกลับตัวชี้ไปยังแบบไดนามิกmallocเอ็ดstructs. แทน"

มาจากคนที่ทำ C ++ เป็นหลักฉันคาดหวังว่าจะไม่สามารถคืนstructค่า s ใน 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;ล่ะ
Greg Hewgill

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
คุณไม่จำเป็นต้องมีพอยน์เตอร์แน่นอน ที่กล่าวว่าส่วนใหญ่เวลาที่คุณต้องการใช้ - สำเนาหน่วยความจำโดยนัยที่เกิดขึ้นรอบ ๆ โครงสร้างตามค่าอาจเป็นการเสียรอบ CPU จริงไม่ต้องพูดถึงแบนด์วิดธ์หน่วยความจำ
Carl Norum

10
@CarlNorum โครงสร้างมีขนาดใหญ่เพียงใดเพื่อให้ได้สำเนาที่เสียค่าใช้จ่ายมากกว่า malloc + ฟรี
josefx

7
@josefx สำเนาเดียว? อาจใหญ่มาก สิ่งที่เป็นปกติถ้าคุณผ่านโครงสร้างรอบโดยค่าคุณคัดลอกพวกเขามาก อย่างไรก็ตามมันไม่ง่ายอย่างนั้น คุณสามารถผ่านโครงสร้างท้องถิ่นหรือระดับโลกซึ่งในกรณีนี้ต้นทุนการจัดสรรของคุณฟรีมาก
Carl Norum

7
คุณต้องมีพอยน์เตอร์และการจัดสรรหน่วยความจำสำหรับค่าที่ส่งคืนภายนอกฟังก์ชันของฟังก์ชันทันทีที่ไม่ทราบจำนวนหน่วยความจำที่จัดสรรสำหรับค่าในเวลารวบรวม มันมีไว้สำหรับ structs ดังนั้นฟังก์ชั่น C จึงไม่มีปัญหาในการส่งคืน
reinierpost

33

เมื่อทำการโทรเช่นa = foo();คอมไพเลอร์อาจผลักที่อยู่ของโครงสร้างผลลัพธ์บนสแต็กและส่งผ่านเป็นตัวชี้ "ซ่อน" ไปยังfoo()ฟังก์ชัน อย่างมีประสิทธิภาพมันอาจกลายเป็นสิ่งที่ชอบ:

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

foo(&a);

อย่างไรก็ตามการใช้งานที่แน่นอนของสิ่งนี้ขึ้นอยู่กับคอมไพเลอร์และ / หรือแพลตฟอร์ม ดังที่ Carl Norum กล่าวหากโครงสร้างมีขนาดเล็กพอมันอาจถูกส่งกลับอย่างสมบูรณ์ในทะเบียน


11
นั่นขึ้นอยู่กับการใช้งานโดยสิ้นเชิง ตัวอย่างเช่น armcc จะส่งผ่านโครงสร้างที่มีขนาดเล็กพอในการลงทะเบียนพารามิเตอร์ปกติ
Carl Norum

นั่นจะไม่ส่งคืนตัวชี้ไปยังตัวแปรโลคัลใช่ไหม หน่วยความจำสำหรับโครงสร้างที่ส่งคืนไม่สามารถเป็นส่วนหนึ่งของfooกรอบสแต็ก fooมันจะต้องมีในสถานที่ที่มีชีวิตที่ผ่านมาการกลับมาของการ
Anders Abel

@AndersAbel: ผมคิดว่าสิ่งที่เกร็กหมายความว่าคอมไพเลอร์จะใช้เวลาชี้ไปยังตัวแปรในส่วนหลักของfooการทำงานและส่งผ่านไปยังฟังก์ชั่น ภายในฟังก์ชั่นfooคุณเพียงแค่ทำการมอบหมาย
mmirzadeh

4
@AndersAbel: *r = aในตอนท้าย (อย่างมีประสิทธิภาพ) จะทำสำเนาของตัวแปรท้องถิ่นไปยังตัวแปรของผู้โทร ฉันพูดว่า "มีประสิทธิภาพ" เพราะคอมไพเลอร์อาจใช้RVOและกำจัดตัวแปรท้องถิ่นaทั้งหมด
เกร็กฮิวกิลล์

3
แม้ว่านี่จะไม่ได้โดยตรงตอบคำถามที่นี่คือเหตุผลว่าทำไมคนจำนวนมากจะตกอยู่ที่นี่ผ่านทาง Google c return struct: พวกเขารู้ว่าใน cdecl eaxถูกส่งกลับโดยมูลค่าและที่ structs eaxโดยทั่วไปไม่เหมาะสมภายใน นี่คือสิ่งที่ฉันกำลังมองหา
Ciro Santilli 郝海东冠状病六四事件法轮功

14

struct bสายไม่ได้เพราะมันเป็นข้อผิดพลาดทางไวยากรณ์ หากคุณขยายออกเพื่อรวมประเภทมันจะทำงานได้ดี

struct MyObj b = a;  // Runs fine

สิ่งที่ C กำลังทำอยู่ที่นี่โดยพื้นฐานแล้วmemcpyมาจากโครงสร้างต้นทางไปยังปลายทาง สิ่งนี้เป็นจริงสำหรับทั้งการกำหนดและการคืนstructค่า (และค่าอื่น ๆ ใน C)


+1 ในความเป็นจริงคอมไพเลอร์จำนวนมากจะปล่อยการโทรตามตัวอักษรmemcpyในกรณีนี้อย่างน้อยถ้าโครงสร้างมีขนาดใหญ่พอสมควร
Carl Norum

ดังนั้นในระหว่างการเริ่มต้นของประเภทข้อมูลฟังก์ชั่น memcpy ทำงานอย่างไร
bhuwansahni

1
@bhuwansahni ฉันไม่แน่ใจว่าสิ่งที่คุณถามที่นี่ คุณอธิบายรายละเอียดเล็กน้อยได้ไหม?
JaredPar

4
@JaredPar - คอมไพเลอร์มักจะแท้จริงเรียกmemcpyฟังก์ชั่นสำหรับสถานการณ์โครงสร้าง คุณสามารถสร้างโปรแกรมทดสอบอย่างรวดเร็วและดู GCC ทำมันตัวอย่างเช่น สำหรับประเภทในตัวที่ไม่เกิดขึ้น - มีขนาดไม่ใหญ่พอที่จะกระตุ้นการเพิ่มประสิทธิภาพประเภทนั้น
Carl Norum

3
เป็นไปได้แน่นอนที่จะทำให้มันเกิดขึ้น - โครงการที่ฉันกำลังทำงานไม่มีmemcpyสัญลักษณ์ที่กำหนดดังนั้นเราจึงมักพบข้อผิดพลาด "สัญลักษณ์ที่ไม่ได้กำหนด" ตัวเชื่อมโยงเมื่อคอมไพเลอร์ตัดสินใจที่จะคายหนึ่งออกมาด้วยตัวเอง
Carl Norum

9

ใช่เป็นไปได้ว่าเราสามารถผ่านโครงสร้างและโครงสร้างการส่งคืนได้เช่นกัน คุณพูดถูก แต่จริง ๆ แล้วคุณไม่ผ่านประเภทข้อมูลที่ควรจะเป็นเช่นนี้ MyObj struct = b

ที่จริงแล้วฉันก็รู้เมื่อฉันพยายามหาทางออกที่ดีกว่าเพื่อคืนค่ามากกว่าหนึ่งค่าสำหรับฟังก์ชั่นโดยไม่ใช้ตัวชี้หรือตัวแปรส่วนกลาง

ตอนนี้ด้านล่างเป็นตัวอย่างของสิ่งเดียวกันซึ่งคำนวณส่วนเบี่ยงเบนของคะแนนนักเรียนโดยเฉลี่ย

#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 อนุญาตให้ส่งคืนค่าที่พอดีกับการลงทะเบียนโปรเซสเซอร์ซึ่งหมายความว่าคุณสามารถส่งคืนพอยน์เตอร์ไปยังโครงสร้างได้เท่านั้น ข้อ จำกัด เดียวกันนี้ใช้กับอาร์กิวเมนต์ของฟังก์ชัน

เวอร์ชันล่าสุดอนุญาตให้ส่งผ่านวัตถุข้อมูลขนาดใหญ่เช่น structs ฉันคิดว่าคุณสมบัตินี้เป็นเรื่องธรรมดาอยู่แล้วในช่วงอายุแปดสิบหรือต้นยุค

อย่างไรก็ตามอาร์เรย์ยังคงสามารถผ่านและส่งกลับได้เป็นพอยน์เตอร์เท่านั้น


คุณสามารถส่งกลับอาร์เรย์ตามค่าถ้าคุณใส่ไว้ใน struct สิ่งที่คุณไม่สามารถคืนค่าได้คืออาร์เรย์ที่มีความยาวผันแปรได้
ฮัน

1
ใช่ฉันสามารถใส่อาร์เรย์ไว้ใน struct ได้ แต่ฉันไม่สามารถเขียน typedef char arr [100]; arr foo () {... } ไม่สามารถส่งคืนอาร์เรย์ได้แม้ว่าจะทราบขนาดแล้วก็ตาม
Giorgio

ผู้ลงคะแนนสามารถอธิบายเหตุผลของการลงคะแนนเสียงได้หรือไม่ หากคำตอบของฉันมีข้อมูลที่ไม่ถูกต้องฉันยินดีที่จะแก้ไข
Giorgio

4

คุณสามารถกำหนด structs ใน C. a = b; เป็นไวยากรณ์ที่ถูกต้อง

คุณปล่อยให้เป็นส่วนหนึ่งของประเภท - แท็ก struct - ในบรรทัดที่ไม่ทำงาน


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 มีที่อยู่หน่วยความจำภายในฟังก์ชั่นรับ () เมื่อได้รับ () ผลตอบแทนที่อยู่ในท้องถิ่นสำหรับชื่อจะได้รับการปลดปล่อย ดังนั้นในผู้โทรหากเราพยายามเข้าถึงที่อยู่นั้นอาจทำให้เกิดความผิดพลาดในการแบ่งกลุ่มตามที่เรากำลังพยายามที่อยู่ว่าง นั่นเป็นสิ่งที่ไม่ดี ..

โดยที่ e1.id จะใช้ได้อย่างสมบูรณ์แบบเนื่องจากค่าจะถูกคัดลอกไปยัง e2.id

ดังนั้นเราควรพยายามหลีกเลี่ยงการส่งคืนที่อยู่หน่วยความจำภายในของฟังก์ชัน

malloced อะไรที่สามารถส่งคืนได้ตามต้องการ


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 รับ () {return {100, "john"}; }
Chris Reid

1

ที่อยู่ struct var e2 ถูกพุชตามที่อ้างถึง callee stack และค่าจะถูกกำหนดที่นั่น อันที่จริง get () คืนค่าที่อยู่ของ e2 ใน eax reg งานนี้ชอบโทรโดยอ้างอิง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.