โดยเฉพาะสิ่งที่เป็นอันตรายในการคัดเลือกผลลัพธ์ของ malloc?


87

ก่อนที่ผู้คนจะเริ่มทำเครื่องหมายสิ่งนี้ฉันได้อ่านสิ่งต่อไปนี้ทั้งหมดซึ่งไม่มีคำตอบใดที่ให้คำตอบที่ฉันกำลังมองหา:

  1. C คำถามที่พบบ่อย: การร่ายค่าตอบแทนของ malloc มีอะไรผิดปกติ?
  2. SO: ฉันควรโยนค่าตอบแทนของ malloc () อย่างชัดเจนหรือไม่
  3. SO: ตัวชี้ที่ไม่จำเป็นต้องร่ายใน C
  4. SO: ฉันสร้างผลลัพธ์ของ malloc หรือไม่?

ทั้งคำถามที่พบบ่อยเกี่ยวกับ C และคำตอบมากมายสำหรับคำถามข้างต้นกล่าวถึงข้อผิดพลาดที่ลึกลับซึ่งmallocค่าส่งคืนของการหล่อสามารถซ่อนได้ อย่างไรก็ตามไม่มีคนใดให้ตัวอย่างเฉพาะของข้อผิดพลาดดังกล่าวในทางปฏิบัติ ตอนนี้ให้ความสนใจที่ผมกล่าวว่าข้อผิดพลาดไม่เตือน

ตอนนี้ได้รับรหัสต่อไปนี้:

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

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

แก้ไขฉันเจอข้อโต้แย้งที่เขียนได้ดีสองข้อเกี่ยวกับปัญหานี้:

  1. In Favor of Casting: CERT Advisory: ส่งผลลัพธ์ของการเรียกใช้ฟังก์ชันการจัดสรรหน่วยความจำทันทีเป็นตัวชี้ไปยังประเภทที่จัดสรร
  2. ต่อต้านการแคสต์ (ข้อผิดพลาด 404 ณ วันที่ 2012-02-14: ใช้สำเนาInternet Archive Wayback Machineจาก 2010-01-27 {2016-03-18: "ไม่สามารถรวบรวมข้อมูลหรือแสดงเพจได้เนื่องจาก robots.txt"})

6
voidตัวชี้การหล่อช่วยให้สามารถรวบรวมรหัสเป็น C ++ บางคนบอกว่านั่นคือคุณสมบัติฉันว่ามันเป็นบั๊ก;)
Christoph

1
นอกจากนี้โปรดอ่านความคิดเห็นในลิงก์แรกของคุณเนื่องจากอธิบายถึงสิ่งที่คุณควรทำแทนการแค
สต์

3
ฉันจะรับคำแนะนำจาก CERT เพื่อรวมนักแสดง นอกจากนี้ฉันจะไม่ลืมที่จะใส่ stdlib.h ด้วย :)
Abhinav

1
นี่คือตัวอย่าง SOของข้อผิดพลาดรันไทม์ในการคอมไพล์เนื่องจากmallocค่าส่งคืนของการแคสต์: แคสต์ไปint*ที่ส่วนโค้ง 64 บิต
John_West

1
คำถามนี้Cไม่ถูกแท็กC++(เป็นสองภาษาที่แตกต่างกัน) ดังนั้นการสนทนาใด ๆ (เช่นเดียวกับในคำตอบบางคำ) จึงไม่เกี่ยวข้องกับคำถามนี้
user3629249

คำตอบ:


66

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

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

โชคดีที่คอมไพเลอร์สมัยใหม่ให้คำเตือนที่ชี้ถึงข้อผิดพลาดที่แท้จริงของคุณ ดูgccเอาต์พุตที่คุณให้มา: เตือนคุณว่าการประกาศโดยนัย ( int malloc(int)) ไม่สามารถใช้ร่วมกับบิวท์อินmallocได้ ดังนั้นgccดูเหมือนจะรู้ได้โดยไม่ต้องmallocstdlib.h

การออกจากการแคสต์เพื่อป้องกันข้อผิดพลาดนี้ส่วนใหญ่เป็นเหตุผลเดียวกับการเขียน

แทน

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

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


2
ดูเหมือนว่าเนื่องจากคอมไพเลอร์เตือนเกี่ยวกับการประกาศโดยนัยที่เข้ากันไม่ได้นี่จึงไม่ใช่ปัญหาตราบเท่าที่คุณใส่ใจกับคำเตือนของคอมไพเลอร์ของคุณ
Robert S.Barnes

4
@ โรเบิร์ต: ใช่ได้รับสมมติฐานบางอย่างเกี่ยวกับคอมไพเลอร์ เมื่อมีคนให้คำแนะนำว่าควรเขียน C โดยทั่วไปอย่างไรให้ดีที่สุดพวกเขาไม่สามารถสันนิษฐานได้ว่าบุคคลที่รับคำแนะนำกำลังใช้ gcc เวอร์ชันล่าสุด
Steve Jessop

4
โอ้และคำตอบสำหรับคำถามที่สองคือผู้โทรมีรหัสสำหรับรับค่าส่งคืน (ซึ่งคิดว่าเป็น int) และแปลงเป็น T * callee เพียงแค่เขียนค่าส่งคืน (เป็นโมฆะ *) และส่งคืน ดังนั้นขึ้นอยู่กับหลักการเรียก: ผลตอบแทน int และผลตอบแทนโมฆะ * อาจหรือไม่อยู่ใน "สถานที่เดียวกัน" (register หรือ stack slot); int และ void * อาจมีขนาดเท่ากันหรือไม่ก็ได้ การแปลงระหว่างทั้งสองอาจเป็นหรือไม่ก็ได้ ดังนั้นมันอาจ "ใช้ได้" หรือค่าอาจเสียหาย (บางบิตหายไปบางที) หรือผู้โทรอาจรับค่าที่ไม่ถูกต้องทั้งหมด
Steve Jessop

1
@ RobertS.Barnes มาสายปาร์ตี้ แต่: ค่าที่ส่งคืนโดยทั่วไปไม่ได้เป็นส่วนหนึ่งของลายเซ็นฟังก์ชันแม้แต่ใน C ++ ตัวเชื่อมโยงเพียงแค่สร้างการข้ามไปยังสัญลักษณ์นั่นคือทั้งหมด
Peter - Reinstate Monica

3
คุณจะได้รับข้อผิดพลาด runtime คาดเดาไม่ได้เมื่อใช้หล่อโดยไม่รวม stdlib.h นั่นเป็นความจริง แต่การไม่รวมstdlib.hถือเป็นข้อผิดพลาดในตัวเองอยู่แล้วแม้ว่าคุณจะได้รับคำเตือน "การประกาศโดยนัย" ก็ตาม
Jabberwocky

45

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

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

ชื่อประเภทอยู่ในการประกาศ ชื่อประเภทควร จำกัด เฉพาะการประกาศและเฉพาะการประกาศเท่านั้น

จากมุมมองนี้บิตของรหัสนี้ไม่ดี

และดีกว่านี้มาก

ไม่ใช่เพียงเพราะ "ไม่ได้ส่งผลลัพธ์ของmalloc" แต่เป็นเพราะเป็นประเภทอิสระ (หรือประเภท agnositic ถ้าคุณต้องการ) เนื่องจากจะปรับตัวเองโดยอัตโนมัติpเป็นประเภทใดก็ตามที่ประกาศด้วยโดยไม่ต้องมีการแทรกแซงใด ๆ จาก ผู้ใช้งาน.


Fwiw ฉันคิดว่านี่เป็นเหตุผลเดียวกันไม่มากก็น้อยเช่นนี้stackoverflow.com/questions/953112/…แต่เน้นที่ความเป็นอิสระของประเภทมากกว่า DIY แน่นอนคนแรกต่อจากที่สอง (หรือกลับกัน) ดังนั้นอย่างน้อยก็ถูกกล่าวถึงในบางครั้ง :)
คลาย

5
@unwind คุณน่าจะหมายถึงDRYมากกว่าDIY
kratenko

18

intไม่ใช่หน้าที่เป็นต้นแบบจะถือว่าผลตอบแทน

ดังนั้นคุณกำลังส่งintไปยังตัวชี้ หากพอยน์เตอร์กว้างกว่าintบนแพลตฟอร์มของคุณพฤติกรรมนี้มีความเสี่ยงสูง

นอกจากนี้แน่นอนว่าบางคนถือว่าคำเตือนเป็นข้อผิดพลาดกล่าวคือโค้ดควรรวบรวมโดยไม่มีพวกเขา

โดยส่วนตัวแล้วฉันคิดว่าการที่คุณไม่จำเป็นต้องแคสต์ไปvoid *ยังตัวชี้ประเภทอื่นเป็นคุณสมบัติใน C และพิจารณาโค้ดที่จะเสีย


14
ฉันมีความเชื่อว่าคอมไพเลอร์รู้ภาษามากกว่าที่ฉันทำดังนั้นหากมันเตือนฉันเกี่ยวกับบางสิ่งฉันก็ต้องใส่ใจ
György Andrasek

3
ในหลาย ๆ โปรเจ็กต์รหัส C ถูกคอมไพล์เป็น C ++ ซึ่งคุณไม่จำเป็นต้องแคสต์ไฟล์void*.
laalto

nit: " โดยค่าเริ่มต้นฟังก์ชันที่ไม่ใช่ต้นแบบจะถือว่าส่งคืนint" - คุณหมายความว่าสามารถเปลี่ยนประเภทการส่งคืนฟังก์ชันที่ไม่ใช่ต้นแบบได้หรือไม่?
PMG

1
@laalto - เป็น แต่ไม่ควร C คือ C ไม่ใช่ C ++ และควรคอมไพล์ด้วยคอมไพเลอร์ C ไม่ใช่คอมไพเลอร์ C ++ ไม่มีข้อแก้ตัว: GCC (หนึ่งในคอมไพเลอร์ C ที่ดีที่สุด) ทำงานบนเกือบทุกแพลตฟอร์มเท่าที่จะเป็นไปได้ (และสร้างโค้ดที่เหมาะสมที่สุดด้วย) เหตุผลอะไรที่คุณอาจต้องรวบรวม C ด้วยคอมไพเลอร์ C ++ นอกเหนือจากความเกียจคร้านและมาตรฐานหลวม
Chris Lutz

3
ตัวอย่างรหัสที่คุณอาจต้องการที่จะรวบรวมเป็นทั้ง C และ C #ifdef __cplusplus \nextern "C" { \n#endif static inline uint16_t swb(uint16_t a) {return ((a << 8) | ((a >> 8) & 0xFF); } \n#ifdef __cplusplus\n } \n#endif++: ตอนนี้ทำไมคุณถึงต้องการเรียก malloc ในฟังก์ชันอินไลน์แบบคงที่ฉันไม่รู้จริงๆ แต่ส่วนหัวที่ใช้งานได้ทั้งสองอย่างแทบจะไม่เคยได้ยินมาก่อน
Steve Jessop

11

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

แก้ไข: ขออภัยที่สั้นเกินไป นี่คือตัวอย่างส่วนของโค้ดสำหรับวัตถุประสงค์ในการสนทนา

หลัก()
{
   ถ่าน * c = (ถ่าน *) malloc (2);
   printf ("% p", c);
}

สมมติว่าตัวชี้ฮีปที่ส่งคืนเป็นสิ่งที่ใหญ่กว่าสิ่งที่แสดงได้ใน int ให้พูด 0xAB00000000

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


1
คุณช่วยอธิบายรายละเอียดว่าสิ่งนี้จะเกิดขึ้นได้อย่างไร
Robert S.Barnes

5
ฉันคิดว่า Peeter Joot พบว่า "โดยค่าเริ่มต้นฟังก์ชันที่ไม่ใช่ต้นแบบจะถือว่าส่งคืน int" โดยไม่มี stdlib.h และ sizeof (int) คือ 32 บิตในขณะที่ sizeof (ptr) คือ 64
ทดสอบ

4

กฎของซอฟต์แวร์ที่ใช้ซ้ำได้:

ในกรณีของการเขียนฟังก์ชันแบบอินไลน์ซึ่งใช้ malloc () เพื่อให้สามารถนำมาใช้ซ้ำสำหรับโค้ด C ++ ได้เช่นกันโปรดทำการแคสต์ประเภทที่ชัดเจน (เช่น (ถ่าน *)) มิฉะนั้นคอมไพเลอร์จะบ่น


หวังว่าด้วยการรวมการเพิ่มประสิทธิภาพเวลาลิงก์ (ล่าสุด) ไว้ใน gcc (ดูgcc.gnu.org/ml/gcc/2009-10/msg00060.html ) การประกาศฟังก์ชันอินไลน์ในไฟล์ส่วนหัวจะไม่จำเป็นอีกต่อไป
Christoph

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

2
เมื่อเขียน C ++ malloc / free ไม่ใช่วิธีการที่ถูกต้อง ค่อนข้างใช้ใหม่ / ลบ IE ไม่ควร / nada / ศูนย์โทรไปที่ malloc / ฟรีในรหัส C ++
user3629249

3
@ user3629249: เมื่อมีการเขียนฟังก์ชั่นที่ต้องการจะใช้งานได้จากภายในทั้งรหัส C หรือ C ++ รหัสโดยใช้malloc/ freeสำหรับทั้งเป็น apt จะดีกว่าพยายามที่จะใช้mallocใน C และnewใน C ++ โดยเฉพาะอย่างยิ่งถ้าโครงสร้างข้อมูลที่ใช้ร่วมกันระหว่าง C และ C ++ รหัสและมีความเป็นไปได้ที่วัตถุอาจถูกสร้างขึ้นในรหัส C และเผยแพร่ในรหัส C ++ หรือในทางกลับกัน
supercat

3

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


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