มีข้อเสียในการผ่าน structs ตามค่าใน C แทนที่จะส่งตัวชี้หรือไม่?


157

มีข้อเสียในการผ่าน structs ตามค่าใน C แทนที่จะส่งตัวชี้หรือไม่?

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

มันอาจจะน่าสนใจยิ่งขึ้นเมื่อใช้เป็นค่าตอบแทน C มีค่าส่งคืนเดียวจากฟังก์ชัน แต่บ่อยครั้งที่คุณต้องการหลายค่า ดังนั้นวิธีง่ายๆคือใส่พวกมันไว้ในโครงสร้างและคืนสิ่งนั้น

มีเหตุผลอะไรหรือขัดกับสิ่งนี้?

เนื่องจากอาจไม่ชัดเจนสำหรับทุกคนที่ฉันกำลังพูดถึงที่นี่ฉันจะยกตัวอย่างง่ายๆ

หากคุณกำลังเขียนโปรแกรมใน C คุณจะเริ่มเขียนฟังก์ชันที่มีลักษณะดังนี้:

void examine_data(const char *ptr, size_t len)
{
    ...
}

char *p = ...;
size_t l = ...;
examine_data(p, l);

นี่ไม่ใช่ปัญหา ปัญหาเดียวคือคุณต้องเห็นด้วยกับเพื่อนร่วมงานของคุณซึ่งลำดับพารามิเตอร์ควรเป็นเช่นนั้นเพื่อให้คุณใช้หลักการเดียวกันในทุกฟังก์ชั่น

แต่จะเกิดอะไรขึ้นเมื่อคุณต้องการส่งคืนข้อมูลประเภทเดียวกัน โดยปกติคุณจะได้รับสิ่งนี้:

char *get_data(size_t *len);
{
    ...
    *len = ...datalen...;
    return ...data...;
}
size_t len;
char *p = get_data(&len);

ใช้งานได้ดี แต่มีปัญหามากกว่า ค่าส่งคืนเป็นค่าส่งคืนยกเว้นว่าในการดำเนินการนี้จะไม่ ไม่มีวิธีที่จะบอกได้จากด้านบนว่าฟังก์ชั่น get_data ไม่ได้รับอนุญาตให้ดูสิ่งที่ len ชี้ไป และไม่มีสิ่งใดที่ทำให้คอมไพเลอร์ตรวจสอบว่ามีการส่งคืนค่าจริงผ่านตัวชี้นั้น ดังนั้นเดือนหน้าเมื่อคนอื่นแก้ไขโค้ดโดยไม่เข้าใจอย่างถูกต้อง (เพราะเขาไม่ได้อ่านเอกสารประกอบ) มันจะพังโดยที่ไม่มีใครสังเกตเห็นหรือมันเริ่มทำงานแบบสุ่ม

ดังนั้นทางออกที่ฉันเสนอคือโครงสร้างอย่างง่าย

struct blob { char *ptr; size_t len; }

ตัวอย่างสามารถเขียนใหม่ได้เช่นนี้

void examine_data(const struct blob data)
{
    ... use data.tr and data.len ...
}

struct blob = { .ptr = ..., .len = ... };
examine_data(blob);

struct blob get_data(void);
{
    ...
    return (struct blob){ .ptr = ...data..., .len = ...len... };
}
struct blob data = get_data();

ด้วยเหตุผลบางอย่างฉันคิดว่าคนส่วนใหญ่จะทำให้ exam_data ใช้ตัวชี้ไปยัง struct blob แต่โดยสัญชาตญาณฉันไม่เห็นสาเหตุ มันยังคงได้พอยน์เตอร์และจำนวนเต็มมันชัดเจนกว่ามากที่พวกมันไปด้วยกัน และในกรณี get_data มันเป็นไปไม่ได้ที่จะทำเลอะเทอะในแบบที่ฉันอธิบายมาก่อนเนื่องจากไม่มีค่าอินพุตสำหรับความยาวและต้องมีความยาวที่ส่งคืน


สำหรับสิ่งที่คุ้มค่าvoid examine data(const struct blob)ไม่ถูกต้อง
Chris Lutz

ขอขอบคุณเปลี่ยนเป็นชื่อตัวแปร
dkagedal

1
"ไม่มีทางที่จะบอกได้จากด้านบนว่าฟังก์ชั่น get_data ไม่ได้รับอนุญาตให้ดูสิ่งที่ len ชี้ไปและไม่มีอะไรที่ทำให้คอมไพเลอร์ตรวจสอบว่ามีการส่งคืนค่าจริงผ่านตัวชี้นั้น" - สิ่งนี้ไม่สมเหตุสมผลสำหรับฉันเลย (อาจเป็นเพราะตัวอย่างของคุณเป็นรหัสที่ไม่ถูกต้องเนื่องจากสองบรรทัดสุดท้ายปรากฏอยู่นอกฟังก์ชั่น) กรุณาอธิบายเพิ่มเติมหน่อยได้ไหม?
Adam Spires

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

1
เหตุผลสำคัญที่ผู้คนไม่ทำเช่นนี้บ่อยครั้งใน C คือประวัติศาสตร์ ก่อนหน้า C89 คุณไม่สามารถผ่านหรือส่งคืน structs ตามค่าดังนั้นอินเทอร์เฟซระบบทั้งหมดที่ลงวันที่ C89 และควรทำเช่นนั้น (เช่นgettimeofday) ใช้พอยน์เตอร์แทนและผู้คนก็ใช้มันเป็นตัวอย่าง
zwol

คำตอบ:


202

สำหรับ structs ขนาดเล็ก (เช่น point, rect) ที่ส่งผ่านตามค่านั้นเป็นที่ยอมรับได้อย่างสมบูรณ์ แต่นอกเหนือจากความเร็วแล้วยังมีอีกเหตุผลหนึ่งที่คุณควรระวังการผ่าน / ส่งคืน struct ขนาดใหญ่ด้วยค่า: พื้นที่สแต็ก

การเขียนโปรแกรม C จำนวนมากสำหรับระบบฝังตัวที่หน่วยความจำอยู่ที่ระดับพรีเมียมและขนาดสแต็กอาจวัดเป็น KB หรือไบต์ ... หากคุณผ่านหรือคืนค่าโครงสร้างตามค่าสำเนาของโครงสร้างเหล่านั้นจะถูกวางไว้ สแต็กอาจทำให้เกิดสถานการณ์ที่ไซต์นี้ตั้งชื่อตาม ...

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


2
"ถ้าคุณผ่านหรือส่งคืน structs ตามค่าสำเนาของ struct เหล่านั้นจะถูกวางลงบนสแต็ก" ฉันจะเรียกbraindead toolchain ที่ทำเช่นนั้น ใช่มันเป็นเรื่องน่าเศร้าที่คนจำนวนมากจะทำ แต่มันไม่ใช่ทุกอย่างที่มาตรฐาน C เรียกร้อง คอมไพเลอร์สติจะเพิ่มประสิทธิภาพมันออกมาทั้งหมด
Reinstate Monica

1
@KubaOber นี่คือเหตุผลที่ไม่ได้ทำบ่อยครั้ง: stackoverflow.com/questions/552134/…
Roddy

1
มีบรรทัดที่ชัดเจนที่แยกโครงสร้างขนาดเล็กจากโครงสร้างขนาดใหญ่หรือไม่
โจซี ธ อมป์สัน

63

เหตุผลหนึ่งที่ไม่ควรทำสิ่งนี้ซึ่งไม่ได้กล่าวถึงคืออาจทำให้เกิดปัญหาที่ความเข้ากันได้ของไบนารีนั้นสำคัญ

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

ดู: http://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html

-fpcc-struct ผลตอบแทน

-freg-struct ผลตอบแทน

หากคอมไพเลอร์สองคนไม่เห็นด้วยสิ่งต่าง ๆ ก็สามารถระเบิดได้ จำเป็นต้องพูดเหตุผลหลักที่ไม่ควรทำเช่นนี้คือการใช้สแต็กและเหตุผลด้านประสิทธิภาพ


4
นี่เป็นคำตอบที่ฉันต้องการ
dkagedal

2
จริง แต่ตัวเลือกเหล่านั้นไม่เกี่ยวข้องกับการส่งต่อค่า พวกเขาเกี่ยวข้องกับการคืนโครงสร้างซึ่งเป็นสิ่งที่แตกต่างกันโดยสิ้นเชิง การกลับมาโดยการอ้างอิงมักเป็นวิธีที่แน่นอนในการถ่ายภาพตัวเองด้วยเท้าทั้งสอง int &bar() { int f; int &j(f); return j;};
Roddy

19

ในการตอบคำถามนี้จริง ๆต้องขุดลึกเข้าไปในดินแดนชุมนุม:

(ตัวอย่างต่อไปนี้ใช้ gcc ใน x86_64 ทุกคนสามารถเพิ่มสถาปัตยกรรมอื่น ๆ เช่น MSVC, ARM และอื่น ๆ )

มีโปรแกรมตัวอย่างของเรา:

// foo.c

typedef struct
{
    double x, y;
} point;

void give_two_doubles(double * x, double * y)
{
    *x = 1.0;
    *y = 2.0;
}

point give_point()
{
    point a = {1.0, 2.0};
    return a;
}

int main()
{
    return 0;
}

รวบรวมมันด้วยการเพิ่มประสิทธิภาพเต็มรูปแบบ

gcc -Wall -O3 foo.c -o foo

ดูการชุมนุม:

objdump -d foo | vim -

นี่คือสิ่งที่เราได้รับ:

0000000000400480 <give_two_doubles>:
    400480: 48 ba 00 00 00 00 00    mov    $0x3ff0000000000000,%rdx
    400487: 00 f0 3f 
    40048a: 48 b8 00 00 00 00 00    mov    $0x4000000000000000,%rax
    400491: 00 00 40 
    400494: 48 89 17                mov    %rdx,(%rdi)
    400497: 48 89 06                mov    %rax,(%rsi)
    40049a: c3                      retq   
    40049b: 0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)

00000000004004a0 <give_point>:
    4004a0: 66 0f 28 05 28 01 00    movapd 0x128(%rip),%xmm0
    4004a7: 00 
    4004a8: 66 0f 29 44 24 e8       movapd %xmm0,-0x18(%rsp)
    4004ae: f2 0f 10 05 12 01 00    movsd  0x112(%rip),%xmm0
    4004b5: 00 
    4004b6: f2 0f 10 4c 24 f0       movsd  -0x10(%rsp),%xmm1
    4004bc: c3                      retq   
    4004bd: 0f 1f 00                nopl   (%rax)

ไม่รวมnoplแผ่นอิเล็กโทรดgive_two_doubles()มี 27 ไบต์ในขณะที่give_point()มี 29 ไบต์ ในทางกลับกันgive_point()ให้คำแนะนำน้อยกว่าหนึ่งgive_two_doubles()

สิ่งที่น่าสนใจคือการที่เราสังเกตเห็นคอมไพเลอร์ที่ได้รับสามารถที่จะเพิ่มประสิทธิภาพmovลงไปได้เร็วขึ้น SSE2 พันธุ์และmovapd movsdนอกจากนี้give_two_doubles()จริง ๆ แล้วย้ายข้อมูลเข้าและออกจากหน่วยความจำซึ่งทำให้สิ่งต่าง ๆ ช้า

เห็นได้ชัดว่าส่วนใหญ่อาจไม่สามารถใช้ได้ในสภาพแวดล้อมแบบฝังตัว (ซึ่งเป็นที่ที่สนามเด็กเล่นสำหรับ C ใช้เวลาส่วนใหญ่ในปัจจุบัน) ฉันไม่ได้เป็นตัวช่วยสร้างการชุมนุมดังนั้นความคิดเห็นใด ๆ ยินดีต้อนรับ!


6
การนับจำนวนคำสั่งนั้นไม่ใช่สิ่งที่น่าสนใจเว้นแต่คุณจะสามารถแสดงความแตกต่างอย่างมากหรือนับจำนวนด้านที่น่าสนใจเช่นจำนวนการกระโดดที่คาดเดายากเป็นต้นคุณสมบัติของประสิทธิภาพที่แท้จริงนั้นลึกซึ้งกว่าการสอน .
dkagedal

6
@dkagedal: จริง เมื่อมองย้อนกลับไปฉันคิดว่าคำตอบของตัวเองเขียนได้แย่มาก แม้ว่าฉันไม่ได้มุ่งเน้นไปที่จำนวนคำสั่งมากนัก (สิ่งที่ทำให้คุณประทับใจ: P) จุดที่ควรทำจริง ๆ คือการส่งผ่าน struct ตามค่านั้นดีกว่าที่จะผ่านโดยอ้างอิงสำหรับประเภทเล็ก ๆ อย่างไรก็ตามควรใช้การส่งผ่านค่าเพราะง่ายกว่า (ไม่เล่นกลตลอดชีวิตไม่จำเป็นต้องกังวลเกี่ยวกับการเปลี่ยนแปลงข้อมูลของคุณหรือconstตลอดเวลา) และฉันพบว่าไม่มีการลงโทษประสิทธิภาพมากนัก (หากไม่ได้รับ) ในการคัดลอกรหัสผ่านตามมูลค่า ตรงกันข้ามกับสิ่งที่หลายคนอาจเชื่อ
kizzx2

15

วิธีแก้ปัญหาอย่างง่ายจะส่งคืนรหัสข้อผิดพลาดเป็นค่าส่งคืนและทุกอย่างอื่นเป็นพารามิเตอร์ในฟังก์ชัน
พารามิเตอร์นี้อาจเป็นโครงสร้างของหลักสูตร แต่ไม่เห็นข้อได้เปรียบเฉพาะที่ส่งผ่านค่านี้โดยส่งตัวชี้
การส่งผ่านโครงสร้างตามค่านั้นเป็นสิ่งอันตรายคุณต้องระวังสิ่งที่คุณกำลังผ่านให้จำไว้ว่าไม่มีตัวสร้างการคัดลอกใน C หากพารามิเตอร์โครงสร้างตัวใดตัวหนึ่งเป็นตัวชี้ค่าตัวชี้จะถูกคัดลอกอาจทำให้สับสนและยากที่จะ เก็บรักษา

เพียงเพื่อให้คำตอบเสร็จสมบูรณ์ (ให้เครดิตเต็มกับRoddy ) การใช้สแต็กเป็นอีกเหตุผลที่ไม่ผ่านโครงสร้างตามค่าเชื่อว่าฉันดีบั๊กล้นล้นเป็น PITA จริง

เล่นซ้ำเพื่อแสดงความคิดเห็น:

การส่งผ่าน struct โดยตัวชี้หมายถึงเอนทิตีบางอย่างมีกรรมสิทธิ์ในวัตถุนี้และมีความรู้เต็มรูปแบบของสิ่งที่และเวลาที่ควรได้รับการปล่อยตัว การส่งผ่าน struct ตามค่าสร้างการอ้างอิงที่ซ่อนอยู่กับข้อมูลภายในของ struct (ตัวชี้ไปยังโครงสร้างอื่น ๆ ฯลฯ ) ที่นี่ยากที่จะรักษา (เป็นไปได้ แต่ทำไม?)


6
แต่การผ่านตัวชี้ไม่ใช่ "อันตราย" เพียงเพราะคุณวางไว้ใน struct ดังนั้นฉันจะไม่ซื้อ
dkagedal

จุดที่ดีในการคัดลอกโครงสร้างที่มีตัวชี้ จุดนี้อาจไม่ชัดเจนมาก สำหรับผู้ที่ไม่ทราบว่าเขาหมายถึงอะไรให้ค้นหาสำเนาลึกและสำเนาตื้น
zooropa

1
หนึ่งในอนุสัญญาฟังก์ชัน C คือให้แสดงพารามิเตอร์เอาต์พุตก่อนรายการพารามิเตอร์อินพุตเช่น int func (char * out, char * in);
zooropa

คุณหมายถึงว่าตัวอย่างเช่น getaddrinfo () ทำให้พารามิเตอร์เอาต์พุตสุดท้ายหรือไม่ :-) มีการประชุมหลายพันชุดและคุณสามารถเลือกได้ตามที่คุณต้องการ
dkagedal

10

สิ่งหนึ่งที่ผู้คนที่นี่ลืมพูดถึงจนถึงตอนนี้ (หรือฉันมองข้ามไป) คือ structs มักจะมีช่องว่างภายใน!

struct {
  short a;
  char b;
  short c;
  char d;
}

ถ่านทุกตัวคือ 1 ไบต์ทุก ๆ สั้นคือ 2 ไบต์ โครงสร้างมีขนาดใหญ่เท่าใด ไม่ไม่ใช่ 6 ไบต์ อย่างน้อยไม่ได้อยู่ในระบบที่ใช้กันมากขึ้น ในระบบส่วนใหญ่จะเป็น 8 ปัญหาคือการจัดตำแหน่งไม่คงที่มันขึ้นอยู่กับระบบดังนั้นโครงสร้างเดียวกันจะมีการจัดตำแหน่งที่แตกต่างกันและขนาดแตกต่างกันในระบบที่แตกต่างกัน

ไม่เพียง แต่การขยายนั้นจะทำให้กองซ้อนของคุณเสียไปมากขึ้น แต่ยังเพิ่มความไม่แน่นอนว่าจะไม่สามารถทำนายการขยายล่วงหน้าได้จนกว่าคุณจะรู้ว่าระบบแผ่นของคุณแล้วดูโครงสร้างที่คุณมีในแอปของคุณและคำนวณขนาด สำหรับมัน. การผ่านตัวชี้ใช้พื้นที่ที่สามารถคาดเดาได้ - ไม่มีความไม่แน่นอน ขนาดของตัวชี้เป็นที่รู้จักกันดีสำหรับระบบมันมีค่าเท่ากันเสมอโดยไม่คำนึงถึงลักษณะของ struct และขนาดของตัวชี้จะถูกเลือกเสมอในลักษณะที่จัดแนวและไม่ต้องขยาย


2
ใช่ แต่การเติมมีอยู่โดยไม่ต้องพึ่งพาการส่งผ่านโครงสร้างตามค่าหรือโดยการอ้างอิง
Ilya

2
@dkagedal: ส่วนไหนของ "ขนาดที่แตกต่างกันในระบบที่แตกต่างกัน" ที่คุณไม่เข้าใจ? เพียงเพราะเป็นวิธีนั้นในระบบของคุณคุณคิดว่ามันจะต้องเหมือนกันสำหรับระบบอื่น - นั่นคือสาเหตุที่คุณไม่ควรส่งผ่านค่า เปลี่ยนตัวอย่างดังนั้นจึงล้มเหลวในระบบของคุณเช่นกัน
Mecki

2
ฉันคิดว่าความคิดเห็นของ Mecki เกี่ยวกับโครงสร้าง padding มีความเกี่ยวข้องโดยเฉพาะอย่างยิ่งสำหรับระบบฝังตัวที่ขนาดสแต็กอาจเป็นปัญหา
zooropa

1
ฉันเดาด้านพลิกของการโต้เถียงว่าถ้า struct ของคุณเป็นแบบง่าย ๆ (มีสองประเภทดั้งเดิม) การส่งผ่านตามค่าจะช่วยให้คอมไพเลอร์เล่นปาหี่โดยใช้รีจิสเตอร์ - ในขณะที่ถ้าคุณใช้พอยน์เตอร์ หน่วยความจำซึ่งช้ากว่า ที่ได้รับในระดับต่ำและสวยมากขึ้นอยู่กับสถาปัตยกรรมเป้าหมายของคุณหากสิ่งเหล่านี้มีความสำคัญ
kizzx2

1
เว้นแต่ว่าโครงสร้างของคุณจะเล็กหรือซีพียูของคุณมีการลงทะเบียนจำนวนมาก (และซีพียูของ Intel ไม่มี) ข้อมูลจะสิ้นสุดลงบนสแต็กและยังเป็นหน่วยความจำและเร็ว / ช้าเท่ากับหน่วยความจำอื่น ๆ ตัวชี้ในอีกทางหนึ่งมีขนาดเล็กเสมอและเป็นเพียงตัวชี้และตัวชี้มักจะจบลงด้วยการลงทะเบียนเมื่อใช้บ่อยขึ้น
Mecki

9

ฉันคิดว่าคำถามของคุณสรุปได้ค่อนข้างดี

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


9

ฉันจะบอกว่าการผ่านโครงสร้าง (ไม่ใหญ่เกินไป) ตามค่าทั้งในฐานะพารามิเตอร์และค่าส่งคืนเป็นเทคนิคที่ถูกต้องตามกฎหมายอย่างสมบูรณ์ ต้องมีการดูแลแน่นอนว่า struct เป็นชนิด POD หรือซีแมนทิกส์การคัดลอกมีการระบุไว้อย่างดี

อัปเดต: ขออภัยฉันเปิด C ++ กำลังคิดอยู่ ฉันจำเวลาที่มันไม่ถูกกฎหมายใน C เพื่อส่งคืน struct จากฟังก์ชัน แต่อาจมีการเปลี่ยนแปลงตั้งแต่นั้นมา ฉันจะยังคงบอกว่ามันใช้ได้ตราบใดที่คอมไพเลอร์ทั้งหมดที่คุณคาดหวังที่จะใช้สนับสนุนการฝึก


โปรดทราบว่าคำถามของฉันเกี่ยวกับ C ไม่ใช่ C ++
dkagedal

มันเป็นเรื่องที่ถูกต้องที่จะกลับ struct จากการทำงานก็ไม่ :) ประโยชน์
Ilya

1
ฉันชอบคำแนะนำของ llya เพื่อใช้การส่งคืนเป็นรหัสข้อผิดพลาดและพารามิเตอร์สำหรับการส่งคืนข้อมูลจากฟังก์ชัน
zooropa

8

นี่คือสิ่งที่ไม่มีใครพูดถึง:

void examine_data(const char *c, size_t l)
{
    c[0] = 'l'; // compiler error
}

void examine_data(const struct blob blob)
{
    blob.ptr[0] = 'l'; // perfectly legal, quite likely to blow up at runtime
}

สมาชิกของconst structมีconstแต่ถ้าสมาชิกที่เป็นตัวชี้ (ชอบchar *) มันจะกลายเป็นchar *constมากกว่าconst char *ที่เราต้องการจริงๆ แน่นอนเราอาจสันนิษฐานได้ว่าconstเป็นเอกสารแสดงเจตจำนงและใครก็ตามที่ละเมิดสิ่งนี้คือการเขียนรหัสที่ไม่ดี (ซึ่งเป็น) แต่ก็ไม่ดีพอสำหรับบางคน (โดยเฉพาะผู้ที่ใช้เวลาสี่ชั่วโมงในการติดตามสาเหตุของการ ความผิดพลาด)

ทางเลือกอื่นอาจเป็นการstruct const_blob { const char *c; size_t l }ใช้และใช้สิ่งนั้น แต่มันค่อนข้างยุ่ง - มันเป็นปัญหาการตั้งชื่อแบบเดียวกับที่ฉันมีกับพtypedefอยน์เตอร์พอยน์เตอร์ ดังนั้นคนส่วนใหญ่มักจะมีเพียงสองพารามิเตอร์ (หรือมีแนวโน้มมากขึ้นสำหรับกรณีนี้โดยใช้ไลบรารีสตริง)


ใช่มันถูกกฎหมายอย่างสมบูรณ์และบางสิ่งที่คุณต้องการทำในบางครั้ง แต่ฉันยอมรับว่ามันเป็นข้อ จำกัด ของการแก้ปัญหาโครงสร้างที่คุณไม่สามารถทำให้พอยน์เตอร์ที่พวกเขาชี้ไปที่ const
dkagedal

Gotcha ที่น่ารังเกียจด้วยstruct const_blobวิธีแก้ปัญหาคือแม้ว่าจะconst_blobมีสมาชิกที่แตกต่างจากblobใน "อ้อม - เนส - เนส" เท่านั้นประเภทของstruct blob*a struct const_blob*จะได้รับการพิจารณาที่แตกต่างกันสำหรับวัตถุประสงค์ของกฎนามแฝงที่เข้มงวด ดังนั้นหากรหัสปลดเปลื้องblob*ไปconst_blob*ใด ๆ เขียนภายหลังจากโครงสร้างพื้นฐานโดยใช้ชนิดเดียวเงียบ ๆ จะทำให้คำแนะนำใด ๆ ที่มีอยู่ของประเภทอื่น ๆ เช่นว่าการใช้งานใด ๆ ที่จะก่อให้เกิดพฤติกรรมที่ไม่ได้กำหนด (ซึ่งมักจะอาจจะไม่เป็นอันตราย แต่อาจเป็นอันตรายถึงตาย) .
supercat

5

หน้า 150 ของบทเรียนเกี่ยวกับการประกอบพีซีในhttp://www.drpaulcarter.com/pcasm/มีคำอธิบายที่ชัดเจนเกี่ยวกับวิธีที่ C อนุญาตให้ฟังก์ชันส่งคืนโครงสร้าง:

C ยังอนุญาตให้ใช้ชนิดโครงสร้างเป็นค่าส่งคืนของฟังก์ชัน เห็นได้ชัดว่าโครงสร้างไม่สามารถส่งคืนได้ใน EAX register คอมไพเลอร์ต่างกันจัดการกับสถานการณ์นี้ต่างกัน วิธีแก้ปัญหาทั่วไปที่คอมไพเลอร์ใช้คือการเขียนฟังก์ชันภายในใหม่โดยใช้ตัวชี้โครงสร้างเป็นพารามิเตอร์ ตัวชี้ใช้เพื่อใส่ค่าส่งคืนลงในโครงสร้างที่กำหนดไว้ภายนอกของรูทีนที่เรียกว่า

ฉันใช้รหัส C ต่อไปนี้เพื่อตรวจสอบข้อความข้างต้น:

struct person {
    int no;
    int age;
};

struct person create() {
    struct person jingguo = { .no = 1, .age = 2};
    return jingguo;
}

int main(int argc, const char *argv[]) {
    struct person result;
    result = create();
    return 0;
}

ใช้ "gcc -S" เพื่อสร้างชุดประกอบสำหรับรหัส C ชิ้นนี้:

    .file   "foo.c"
    .text
.globl create
    .type   create, @function
create:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $16, %esp
    movl    8(%ebp), %ecx
    movl    $1, -8(%ebp)
    movl    $2, -4(%ebp)
    movl    -8(%ebp), %eax
    movl    -4(%ebp), %edx
    movl    %eax, (%ecx)
    movl    %edx, 4(%ecx)
    movl    %ecx, %eax
    leave
    ret $4
    .size   create, .-create
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $20, %esp
    leal    -8(%ebp), %eax
    movl    %eax, (%esp)
    call    create
    subl    $4, %esp
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

สแต็กก่อนการโทรสร้าง:

        +---------------------------+
ebp     | saved ebp                 |
        +---------------------------+
ebp-4   | age part of struct person | 
        +---------------------------+
ebp-8   | no part of struct person  |
        +---------------------------+        
ebp-12  |                           |
        +---------------------------+
ebp-16  |                           |
        +---------------------------+
ebp-20  | ebp-8 (address)           |
        +---------------------------+

สแต็คทันทีหลังจากที่สร้างสร้าง:

        +---------------------------+
        | ebp-8 (address)           |
        +---------------------------+
        | return address            |
        +---------------------------+
ebp,esp | saved ebp                 |
        +---------------------------+

2
มีสองปัญหาที่นี่ สิ่งที่ชัดเจนที่สุดคือสิ่งนี้ไม่ได้อธิบาย "วิธีที่ C อนุญาตให้ฟังก์ชันส่งคืนโครงสร้างได้" สิ่งนี้อธิบายเพียงว่ามันสามารถทำได้บนฮาร์ดแวร์ x86 แบบ 32 บิตซึ่งเป็นหนึ่งในสถาปัตยกรรมที่ จำกัด มากที่สุดเมื่อคุณดูจำนวนรีจิสเตอร์และอื่น ๆ ปัญหาที่สองคือวิธีที่คอมไพเลอร์ C สร้างรหัสเพื่อคืนค่า ถูกกำหนดโดย ABI (ยกเว้นฟังก์ชั่นที่ไม่ได้ถูกเอ็กซ์พอร์ตหรือ inlined) และโดยวิธีการฟังก์ชั่นอินไลน์อาจเป็นหนึ่งในสถานที่ที่ structs กลับมีประโยชน์มากที่สุด
dkagedal

ขอบคุณสำหรับการแก้ไข สำหรับการประชุมที่มีรายละเอียดที่สมบูรณ์en.wikipedia.org/wiki/Calling_conventionเป็นการอ้างอิงที่ดี
Jingguo Yao

@dkagedal: สิ่งที่สำคัญไม่ใช่แค่ว่า x86 เกิดขึ้นเพื่อทำสิ่งนี้ แต่มีวิธี "สากล" (เช่นนี้) ที่จะอนุญาตให้คอมไพเลอร์สำหรับแพลตฟอร์มใด ๆ เพื่อสนับสนุนผลตอบแทนประเภทโครงสร้างใด ๆ ที่ไม่ใช่ ' ไม่ใหญ่พอที่จะระเบิดกอง ในขณะที่คอมไพเลอร์สำหรับแพลตฟอร์มจำนวนมากจะใช้วิธีการอื่นที่มีประสิทธิภาพมากขึ้นสำหรับการจัดการค่าส่งคืนชนิดโครงสร้างบางอย่าง แต่ไม่จำเป็นต้องใช้ภาษาในการ จำกัด ประเภทการคืนโครงสร้างให้กับแพลตฟอร์มที่สามารถจัดการได้อย่างเหมาะสม
supercat

0

ฉันแค่ต้องการชี้ให้เห็นข้อดีอย่างหนึ่งของการส่งผ่าน structs ของคุณตามค่าคือคอมไพเลอร์การปรับให้เหมาะสมอาจทำให้โค้ดของคุณดีขึ้น

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