ทำไมฟังก์ชั่นมากมายที่ส่งกลับโครงสร้างใน C จริง ๆ แล้วส่งกลับพอยน์เตอร์ไปยังโครงสร้าง


49

ข้อดีของการส่งกลับตัวชี้ไปยังโครงสร้างต่างจากการส่งคืนโครงสร้างทั้งหมดในreturnคำสั่งของฟังก์ชันคืออะไร

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

ฉันเชื่อว่านี่เป็นตัวเลือกการออกแบบมากกว่าคำถามการเขียนโปรแกรมและฉันอยากรู้เพิ่มเติมเกี่ยวกับข้อดีและข้อเสียของทั้งสองวิธี

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

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


9
@ JohnR.Strohm ฉันลองแล้วใช้งานได้จริง ฟังก์ชั่นสามารถคืนค่า struct .... ดังนั้นอะไรคือเหตุผลที่ไม่ได้ทำ?
yoyo_fun

27
การกำหนดมาตรฐานล่วงหน้า C ไม่อนุญาตให้คัดลอก struct หรือผ่านค่า ไลบรารี่มาตรฐาน C นั้นมีหลายโฮลด์จากยุคนั้นที่จะไม่ถูกเขียนในลักษณะนี้ในวันนี้เช่นจะใช้เวลาจนถึง C11 สำหรับgets()ฟังก์ชั่นการออกแบบที่ผิดพลาดอย่างที่สุดที่จะถูกลบออก โปรแกรมเมอร์บางคนยังมีความเกลียดชังที่จะคัดลอก structs นิสัยเก่าตายยาก
amon

26
FILE*เป็นตัวจัดการทึบแสงได้อย่างมีประสิทธิภาพ รหัสผู้ใช้ไม่ควรสนใจว่าโครงสร้างภายในคืออะไร
CodesInChaos

3
การส่งคืนโดยการอ้างอิงเป็นค่าเริ่มต้นที่สมเหตุสมผลเมื่อคุณมีการรวบรวมขยะ
Idan Arye

6
@ JohnR.Strohm ดูเหมือนว่า "อาวุโสมาก" ในโปรไฟล์ของคุณย้อนกลับไปก่อนปี 1989 ;-) - เมื่อ ANSI C อนุญาตสิ่งที่ K&R C ไม่ได้: คัดลอกโครงสร้างในงานที่มอบหมายการส่งผ่านพารามิเตอร์และค่าส่งคืน หนังสือต้นฉบับของ K&R ระบุไว้อย่างชัดเจน (ฉันกำลังถอดความ): "คุณสามารถทำสองสิ่งได้อย่างแน่นอนด้วยโครงสร้างใช้ที่อยู่& และเข้าถึงสมาชิกด้วย."
ปีเตอร์ - Reinstate Monica

คำตอบ:


62

มีเหตุผลเชิงปฏิบัติหลายประการว่าทำไมฟังก์ชั่นเช่นพfopenอยน์เตอร์พอยน์เตอร์ถึงแทนที่จะเป็นอินสแตนซ์structประเภท:

  1. คุณต้องการซ่อนการเป็นตัวแทนของstructประเภทจากผู้ใช้;
  2. คุณกำลังจัดสรรวัตถุแบบไดนามิก
  3. คุณกำลังอ้างถึงอินสแตนซ์เดียวของวัตถุผ่านการอ้างอิงหลายรายการ

ในกรณีที่มีประเภทเช่นFILE *นั้นเป็นเพราะคุณไม่ต้องการเปิดเผยรายละเอียดเกี่ยวกับการเป็นตัวแทนของผู้ใช้ประเภท - FILE *วัตถุทำหน้าที่เป็นตัวจัดการทึบแสงและคุณเพิ่งผ่านหมายเลขอ้างอิงนั้นไปยังรูทีน I / O ต่างๆ (และFILEบ่อยครั้ง ใช้งานเป็นstructประเภทไม่จำเป็นต้องมี )

ดังนั้นคุณสามารถแสดงประเภทที่ไม่สมบูรณ์ structในส่วนหัวบางแห่ง:

typedef struct __some_internal_stream_implementation FILE;

ในขณะที่คุณไม่สามารถประกาศอินสแตนซ์ของชนิดที่ไม่สมบูรณ์คุณสามารถประกาศตัวชี้ ดังนั้นผมจึงสามารถสร้างFILE *และกำหนดให้มันผ่านfopen, freopenฯลฯ แต่ผมไม่สามารถจัดการกับวัตถุที่จะชี้ไปที่

เป็นไปได้ว่าfopenฟังก์ชั่นนั้นจะจัดสรรFILEวัตถุแบบไดนามิกโดยใช้mallocหรือคล้ายกัน ในกรณีนั้นมันสมเหตุสมผลที่จะคืนพอยน์เตอร์

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


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

6
@Barmar: แท้จริง ABI เสถียรภาพเป็นจุดขายใหญ่ของ C, และมันจะไม่เป็นที่มั่นคงโดยไม่ต้องชี้ทึบแสง
Matthieu M.

38

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

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

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


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

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

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

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

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

12

นอกจากคำตอบอื่น ๆ บางครั้งการคืนค่าเล็กน้อย structก็คุ้มค่า ตัวอย่างเช่นหนึ่งสามารถส่งคืนข้อมูลหนึ่งคู่และรหัสข้อผิดพลาด (หรือความสำเร็จ) ที่เกี่ยวข้องกับข้อมูลนั้น

ในการรับตัวอย่างfopenส่งคืนข้อมูลเพียงหนึ่งตัว (เปิดFILE*) และในกรณีที่มีข้อผิดพลาดให้รหัสข้อผิดพลาดผ่านerrnoตัวแปรหลอกส่วนกลาง แต่มันอาจจะดีกว่าถ้าส่งคืนstructสมาชิกสองคน: FILE*หมายเลขอ้างอิงและรหัสข้อผิดพลาด (ซึ่งจะถูกตั้งค่าหากหมายเลขอ้างอิงไฟล์เป็นNULL) ด้วยเหตุผลทางประวัติศาสตร์ไม่ใช่กรณี (และมีการรายงานข้อผิดพลาดerrnoทั่วโลกซึ่งวันนี้เป็นมาโคร)

ขอให้สังเกตว่าภาษาโกมีสัญกรณ์ที่ดีในการส่งคืนค่าสอง (หรือสองสาม)

โปรดสังเกตว่าบน Linux / x86-64 ABIและการเรียกการประชุม (ดูหน้าx86-psABI ) ระบุว่าstructสมาชิกสเกลาร์สองคน (เช่นตัวชี้และจำนวนเต็มหรือตัวชี้สองตัวหรือจำนวนเต็มสองตัว) จะถูกส่งคืนผ่านรีจิสเตอร์สองตัว (และนี่มีประสิทธิภาพมากและไม่ผ่านหน่วยความจำ)

ดังนั้นในรหัส C ใหม่การคืนค่า C ขนาดเล็กstructสามารถอ่านได้ง่ายขึ้นเป็นมิตรกับเธรดและมีประสิทธิภาพมากขึ้น


ที่จริง structs ขนาดเล็กที่มีการบรรจุrdx:raxลงใน ดังนั้นstruct foo { int a,b; };จะถูกส่งคืนเข้าสู่rax(เช่นกับ shift / หรือ) และจะต้องแตกด้วย shift / mov นี่คือตัวอย่าง Godbolt แต่ x86 สามารถใช้บิตต่ำ 32 บิตของการลงทะเบียน 64 บิตสำหรับการดำเนินการแบบ 32 บิตโดยไม่สนใจบิตสูงดังนั้นมันแย่เกินไปเสมอ แต่แย่กว่าการใช้ 2 รีจิสเตอร์ส่วนใหญ่สำหรับสมาชิก 2 คน
Peter Cordes

ที่เกี่ยวข้อง: bugs.llvm.org/show_bug.cgi?id=34840 std::optional<int>ผลตอบแทนบูลในครึ่งบนของraxคุณจึงต้องหน้ากาก 64 testบิตอย่างต่อเนื่องเพื่อทดสอบกับ btหรือคุณอาจจะใช้ แต่มันแย่สำหรับผู้โทรและ callee เปรียบเทียบกับการใช้dlซึ่งคอมไพเลอร์ควรทำเพื่อฟังก์ชั่น "ส่วนตัว" นอกจากนี้ยังเกี่ยวข้องกับ: libstdc ++ 's std::optional<T>ไม่นิด-copyable แม้เมื่อ T มีจึงเสมอกลับผ่านตัวชี้ที่ซ่อนอยู่: stackoverflow.com/questions/46544019/... (libc ++ สามารถคัดลอกได้เล็กน้อย)
Peter Cordes

@PeterCordes: สิ่งที่เกี่ยวข้องกับคุณคือ C ++ ไม่ใช่ C
Basile Starynkevitch

โอ๊ะโอ ดีสิ่งเดียวกันจะใช้ว่าจะstruct { int a; _Bool b; };ใน C, ถ้าโทรต้องการที่จะทดสอบบูลเพราะนิด-copyable structs C ++ ใช้ ABI เดียวกับซี
ปีเตอร์ Cordes

1
ตัวอย่างคลาสสิกdiv_t div()
chux

6

คุณมาถูกทางแล้ว

ทั้งเหตุผลที่คุณกล่าวถึงนั้นถูกต้อง:

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

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

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

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

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

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

ในความคิดเห็นฉันเห็นว่ามีคำถามว่าคุณสามารถคืนค่า struct จากฟังก์ชันได้หรือไม่ คุณสามารถทำสิ่งนี้ได้แน่นอน ต่อไปนี้ควรทำงาน:

struct s1 {
   int integer;
};

struct s1 f(struct s1 input){
   struct s1 returnValue = xinput
   return returnValue;
}

int main(void){
   struct s1 a = { 42 };
   struct s1 b= f(a);

   return 0;
}

เป็นไปได้อย่างไรที่จะไม่ทราบจำนวนหน่วยความจำที่แน่นอนของตัวแปรบางตัวที่จะต้องใช้ถ้าคุณได้กำหนดชนิดของ struct ไว้แล้ว?
yoyo_fun

9
@JenniferAnderson C มีแนวคิดของประเภทที่ไม่สมบูรณ์: ชื่อประเภทสามารถประกาศได้ แต่ยังไม่ได้กำหนดดังนั้นขนาดจึงไม่สามารถใช้งานได้ ฉันไม่สามารถประกาศตัวแปรประเภทนั้น แต่สามารถประกาศตัวชี้struct incomplete* foo(void)ประเภทที่เช่น ด้วยวิธีนี้ฉันสามารถประกาศฟังก์ชั่นในส่วนหัว แต่เพียงกำหนด structs ภายในไฟล์ C จึงอนุญาตให้มีการห่อหุ้ม
amon

@ มอนนี่คือวิธีการประกาศฟังก์ชั่นส่วนหัว (ต้นแบบ / ลายเซ็น) ก่อนที่จะประกาศว่าพวกเขาทำงานจริง ๆ ใน C อย่างไร และเป็นไปได้ที่จะทำสิ่งเดียวกันกับโครงสร้างและสหภาพใน C
yoyo_fun

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

6

บางอย่างเช่น a FILE*ไม่ใช่ตัวชี้ไปยังโครงสร้างที่เกี่ยวข้องกับรหัสลูกค้า แต่เป็นรูปแบบของตัวระบุทึบแสงที่เกี่ยวข้องกับเอนทิตีอื่น ๆเช่นไฟล์ เมื่อโปรแกรมเรียกfopenใช้โดยทั่วไปจะไม่สนใจเกี่ยวกับเนื้อหาใด ๆ ของโครงสร้างที่ส่งคืน - สิ่งที่มันจะสนใจก็คือฟังก์ชั่นอื่น ๆ เช่นfreadจะทำสิ่งที่พวกเขาต้องการจะทำกับมัน

หากไลบรารีมาตรฐานเก็บไว้ในFILE*ข้อมูลเกี่ยวกับเช่นตำแหน่งการอ่านปัจจุบันภายในไฟล์นั้นการเรียกไปยังfreadจะต้องสามารถอัปเดตข้อมูลนั้นได้ ต้องfreadได้รับตัวชี้ไปที่FILEทำให้ง่าย หากfreadได้รับ a แทนFILEจะไม่มีวิธีอัปเดตFILEออบเจกต์ที่ถือโดยผู้โทร


3

การซ่อนข้อมูล

ข้อดีของการส่งกลับตัวชี้ไปยังโครงสร้างต่างจากการส่งคืนโครงสร้างทั้งหมดในคำสั่ง return ของฟังก์ชันคืออะไร

หนึ่งที่พบมากที่สุดคือที่หลบซ่อนข้อมูล C ไม่ได้พูดความสามารถในการสร้างเขตข้อมูลของstructส่วนตัวให้คนเดียวให้วิธีการในการเข้าถึงพวกเขา

ดังนั้นหากคุณต้องการป้องกันไม่ให้นักพัฒนาสามารถดูและแก้ไขเนื้อหาของ Pointee ได้เช่นเดียวFILEแล้ววิธีเดียวคือป้องกันพวกเขาจากการสัมผัสกับคำจำกัดความโดยการใช้ตัวชี้เป็นทึบแสงซึ่งมีขนาดของ Pointee และ คำนิยามไม่เป็นที่รู้จักของโลกภายนอก คำจำกัดความของFILEจะปรากฏให้เห็นเฉพาะผู้ที่ใช้งานการดำเนินการที่ต้องการคำนิยามเช่นfopenในขณะที่การประกาศโครงสร้างจะปรากฏเฉพาะส่วนหัวสาธารณะ

ความเข้ากันได้ไบนารี

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

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

อย่างมีประสิทธิภาพ

การคืนโครงสร้างที่เป็น NULL นั้นยากขึ้นฉันคิดว่าหรือมีประสิทธิภาพน้อยกว่า นี่เป็นเหตุผลที่ถูกต้องหรือไม่

โดยทั่วไปแล้วจะมีประสิทธิภาพน้อยกว่าโดยสมมติว่าประเภทสามารถพอดีและจัดสรรบนสแต็กได้เว้นแต่ว่าโดยทั่วไปจะมีตัวจัดสรรหน่วยความจำแบบทั่วไปที่ใช้อยู่ด้านหลังน้อยกว่าmallocเช่นขนาดคงที่แทนที่จะจัดสรรหน่วยความจำขนาดรวมกัน มันเป็นความปลอดภัยของการปิดในกรณีนี้ส่วนใหญ่มีแนวโน้มที่จะช่วยให้นักพัฒนาห้องสมุดเพื่อรักษาค่าคงที่ (การค้ำประกันความคิด) FILEที่เกี่ยวข้องกับ

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

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

ฮอตสปอตและการแก้ไข

สำหรับกรณีอื่น ๆ ฉันได้รวบรวมรหัส C ที่สิ้นเปลืองจำนวนมากในฐานรหัสเดิมที่มีฮอตสปอตmallocและแคชบังคับที่ไม่จำเป็นเนื่องจากการใช้วิธีนี้บ่อยเกินไปกับตัวชี้ทึบและจัดสรรสิ่งต่าง ๆ มากมายโดยไม่จำเป็นบนฮีป ลูปขนาดใหญ่

แนวทางปฏิบัติทางเลือกที่ฉันใช้แทนคือการเปิดเผยคำจำกัดความของโครงสร้างแม้ว่าลูกค้าไม่ได้ตั้งใจจะดัดแปลงพวกเขาโดยใช้มาตรฐานการประชุมการตั้งชื่อเพื่อสื่อสารว่าไม่มีใครควรสัมผัสฟิลด์:

struct Foo
{
   /* priv_* indicates that you shouldn't tamper with these fields! */
   int priv_internal_field;
   int priv_other_one;
};

struct Foo foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_something(struct Foo* foo);

หากมีข้อสงสัยเกี่ยวกับความเข้ากันได้ของไบนารีในอนาคตฉันก็พบว่ามันดีพอที่จะจองพื้นที่พิเศษเพิ่มเติมเพื่อวัตถุประสงค์ในอนาคตได้เช่น:

struct Foo
{
   /* priv_* indicates that you shouldn't tamper with these fields! */
   int priv_internal_field;
   int priv_other_one;

   /* reserved for possible future uses (emergency backup plan).
     currently just set to null. */
   void* priv_reserved;
};

พื้นที่สงวนนั้นค่อนข้างสิ้นเปลือง แต่สามารถช่วยชีวิตหากเราพบในอนาคตที่เราต้องการเพิ่มข้อมูลเพิ่มเติมFooโดยไม่ทำลายไบนารีที่ใช้ห้องสมุดของเรา

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

int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
    foo_something(&foo);
    foo_destroy(&foo);
}

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

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