ฉันจะส่งผลของ malloc หรือไม่?


2407

ในคำถามนี้มีคนแนะนำในการแสดงความคิดเห็นว่าฉันไม่ควรแสดงผลลัพธ์mallocเช่น

int *sieve = malloc(sizeof(int) * length);

ค่อนข้างมากกว่า:

int *sieve = (int *) malloc(sizeof(int) * length);

ทำไมถึงเป็นเช่นนี้?


222
นอกจากนี้ยังสามารถบำรุงรักษาได้มากกว่าในการเขียนตะแกรง = malloc (ขนาดของความยาวตะแกรง * *);
วิลเลียม Pursell


3
คำตอบที่นี่เป็นเขตที่วางทุ่นระเบิดของฝ่ายเดียว คำตอบคือ "มันขึ้นอยู่กับ" ไม่ใช่สิ่งจำเป็นที่จะทำให้โปรแกรมทำงานได้ อย่างไรก็ตามมาตรฐานการเข้ารหัสบางอย่างจำเป็นต้องใช้ ... ตัวอย่างเช่นดูมาตรฐานการเข้ารหัส CERT C
Dr. Person Person II

NULLน่าแปลกที่ทุกคนยอมรับว่าคุณไม่หล่อ (นั่นอาจเป็นสาเหตุที่ C ++ แนะนำnullptr: C ++ ไม่อนุญาตให้ใช้ตัวชี้โดยนัย)
Sapphire_Brick

คุณทำได้ แต่ไม่จำเป็นต้องทำ แต่คุณต้องใช้ C ++
ไม่มีร่าง

คำตอบ:


2217

ไม่ ; คุณไม่ส่งผลเนื่องจาก:

  • ไม่จำเป็นเนื่องจากvoid *มีการเลื่อนตำแหน่งตัวชี้ชนิดอื่นโดยอัตโนมัติและปลอดภัยในกรณีนี้
  • มันเพิ่มความยุ่งเหยิงให้กับรหัสปลดเปลื้องไม่ง่ายต่อการอ่าน (โดยเฉพาะถ้าประเภทตัวชี้ยาว)
  • มันทำให้คุณทำซ้ำตัวเองซึ่งโดยทั่วไปจะไม่ดี
  • <stdlib.h>มันสามารถซ่อนข้อผิดพลาดหากคุณลืมใส่ สิ่งนี้อาจทำให้เกิดการขัดข้อง (หรือแย่กว่านั้นไม่ทำให้เกิดการขัดข้องจนกว่าจะถึงเวลาต่อมาในส่วนที่แตกต่างกันโดยสิ้นเชิงของรหัส) พิจารณาสิ่งที่เกิดขึ้นหากพอยน์เตอร์และจำนวนเต็มมีขนาดแตกต่างกัน จากนั้นคุณกำลังซ่อนคำเตือนโดยการส่งและอาจสูญเสียบิตของที่อยู่ที่ส่งคืน หมายเหตุ: เป็นฟังก์ชั่นโดยปริยาย C99 จะหายไปจาก C intและจุดนี้จะไม่เกี่ยวข้องเนื่องจากไม่มีสมมติฐานอัตโนมัติที่ฟังก์ชั่นที่ไม่ได้ประกาศกลับ

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

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

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

int *sieve = malloc(length * sizeof *sieve);

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


ในขณะที่การเคลื่อนย้ายlengthไปข้างหน้าอาจเพิ่มการมองเห็นในบางกรณีที่หายากบางคนควรให้ความสนใจว่าในกรณีทั่วไปควรเขียนนิพจน์เป็น:

int *sieve = malloc(sizeof *sieve * length);

sizeofในกรณีนี้การเก็บรักษาครั้งแรกทำให้แน่ใจว่าการคูณจะทำด้วยsize_tคณิตศาสตร์อย่างน้อย

เปรียบเทียบ: malloc(sizeof *sieve * length * width)เมื่อเทียบกับmalloc(length * width * sizeof *sieve)ครั้งที่สองอาจล้นlength * widthเมื่อwidthและเป็นชนิดที่มีขนาดเล็กกว่าlengthsize_t


21
โปรดพิจารณาอัปเดตคำตอบ การโยนนั้นไม่เป็นอันตรายอีกต่อไปและการทำซ้ำตัวเองไม่จำเป็นต้องเป็นสิ่งที่ไม่ดี (ความซ้ำซ้อนสามารถช่วยในการตรวจจับข้อผิดพลาด)
n คำสรรพนาม 'm

11
คอมไพเลอร์มีการเปลี่ยนแปลง คอมไพเลอร์ล่าสุดจะเตือนคุณเกี่ยวกับการประกาศ malloc ที่หายไป
n คำสรรพนาม 'm

55
@nm ตกลง ฉันคิดว่ามันไม่ดีที่จะสมมติว่าทุกคนที่อ่านที่นี่มีคอมไพเลอร์เฉพาะ อีกทั้งตั้งแต่ C11 แนวคิด "ฟังก์ชั่นโดยนัย" หมดไปฉันเลยไม่รู้ ยังฉันไม่เห็นจุดในการเพิ่มนักแสดงที่ไม่มีจุดหมาย คุณยังทำint x = (int) 12;เพียงเพื่อทำให้สิ่งต่าง ๆ ชัดเจน?
คลาย

22
@nm หากการชี้ตัวชี้เป็นโมฆะอย่างชัดเจนว่า "ช่วย" แก้ไขข้อผิดพลาดคุณน่าจะพบกับพฤติกรรมที่ไม่ได้กำหนดซึ่งอาจหมายถึงโปรแกรมที่มีปัญหานั้นมีข้อบกพร่องที่ยังไม่ได้เปิดซึ่งคุณยังไม่ได้ค้นพบ และวันหนึ่งในช่วงเย็นของฤดูหนาวคุณจะกลับบ้านจากที่ทำงานเพื่อค้นหาหน้า GitHub ของคุณที่เต็มไปด้วยรายงานปัญหาบ่นเกี่ยวกับปีศาจที่บินออกมาจากจมูกของผู้ใช้
Braden Best

12
@unwind แม้ฉันจะเห็นด้วยกับคุณ(int)12ก็ไม่สามารถเทียบเคียง 12 เป็นintหล่อไม่ทำอะไรเลยเพียงแค่ retval of malloc()is void *ไม่ใช่ประเภทของตัวชี้ที่ถูกส่งไป (ถ้าไม่ใช่void *ดังนั้นการเปรียบเทียบ(int)12จะเป็น(void*)malloc(…)สิ่งที่ไม่มีใครพูดถึง)
Amin Negm-Awad

376

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

int *sieve = malloc(sizeof *sieve * length);

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

บรรยากาศดุร้ายตามที่ผู้คนได้ชี้ให้เห็น โดยเฉพาะอย่างยิ่งตัวชี้ปลดเปลื้อง


71
@MAKZ ฉันจะเถียงว่าmalloc(length * sizeof *sieve)มันทำให้ดูเหมือนว่าsizeofเป็นตัวแปร - ดังนั้นฉันคิดว่าmalloc(length * sizeof(*sieve))อ่านง่ายขึ้น
Michael Anderson

21
และmalloc(length * (sizeof *sieve))อ่านง่ายขึ้น IMHO
Toby Speight

19
@Michael Anderson ()ออกด้านข้างโปรดทราบว่าสไตล์ที่คุณแนะนำเปลี่ยนลำดับ, พิจารณาเมื่อการนับองค์ประกอบถูกคำนวณเช่นlength*widthการรักษาsizeofอันดับแรกในกรณีนี้ทำให้มั่นใจว่าการคูณจะทำด้วยsize_tคณิตศาสตร์อย่างน้อย เปรียบเทียบmalloc(sizeof( *ptr) * length * width)กับ malloc(length * width * sizeof (*ptr))- 2 อาจล้นlength*widthเมื่อเป็นชนิดที่มีขนาดเล็กwidth,length size_t
chux - Reinstate Monica

3
@ chux ไม่ชัดเจน แต่คำตอบได้รับการแก้ไขเพื่อให้ความคิดเห็นของฉันไม่เกี่ยวข้อง - คำแนะนำดั้งเดิมคือmalloc(sizeof *sieve * length)
Michael Anderson

12
C ไม่ใช่ C ++ ทำท่าว่าพวกเขาจะนำไปสู่ความสับสนและความเศร้าในที่สุด หากคุณใช้ C ++ แล้วการส่งแบบ C ก็ไม่ดีเช่นกัน (เว้นแต่คุณกำลังใช้คอมไพเลอร์ C ++ ที่เก่ามาก) และstatic_cast>()(หรือreinterpret_cast<>()) เข้ากันไม่ได้กับภาษาถิ่น C.
David C.

349

คุณไม่หล่อเพราะ:

  • มันทำให้โค้ดของคุณพกพาได้มากขึ้นระหว่าง C และ C ++ และจากประสบการณ์ SO แสดงให้เห็นว่าโปรแกรมเมอร์จำนวนมากอ้างว่าพวกเขากำลังเขียนใน C เมื่อพวกเขาเขียนใน C ++ (หรือ C รวมถึงส่วนขยายคอมไพเลอร์ในเครื่อง)
  • ล้มเหลวในการทำเช่นนั้นสามารถซ่อนข้อผิดพลาด : บันทึกทุกตัวอย่าง SO ของความสับสนเมื่อจะเขียนเมื่อเทียบกับtype *type **
  • ความคิดที่ว่าจะช่วยให้คุณจากการสังเกตเห็นคุณล้มเหลวไปยัง#includeไฟล์ส่วนหัวที่เหมาะสมบอลเฉียงป่าต้นไม้ มันเหมือนกับการพูดว่า "ไม่ต้องกังวลกับความจริงที่ว่าคุณล้มเหลวในการขอให้คอมไพเลอร์บ่นเกี่ยวกับการไม่เห็นต้นแบบ - นั่นน่ารำคาญ stdlib.h เป็นสิ่งสำคัญที่ต้องจำไว้!"
  • มันกองกำลังพิเศษทางปัญญาข้ามการตรวจสอบ มันทำให้ประเภทที่ต้องการ (ที่ถูกกล่าวหา) ติดกับเลขคณิตที่คุณทำกับขนาดที่แท้จริงของตัวแปรนั้น ฉันพนันได้เลยว่าคุณสามารถทำการศึกษา SO ที่แสดงว่าmalloc()ข้อผิดพลาดนั้นถูกจับได้เร็วกว่าเมื่อมีนักแสดง เช่นเดียวกับการยืนยันคำอธิบายประกอบที่เปิดเผยเจตนาลดข้อบกพร่อง
  • ทำซ้ำตัวเองในแบบที่เครื่องสามารถตรวจสอบได้มักเป็นความคิดที่ดี ในความเป็นจริงนั้นเป็นสิ่งที่ยืนยันและการใช้งานของนักแสดงนี้เป็นการยืนยัน การยืนยันยังคงเป็นเทคนิคทั่วไปที่สุดที่เรามีสำหรับการแก้ไขโค้ดเนื่องจากทัวริงเกิดความคิดขึ้นเมื่อหลายปีก่อน

39
@ulidtko ในกรณีที่คุณไม่ทราบเป็นไปได้ที่จะเขียนโค้ดที่คอมไพล์ทั้งในรูปแบบ C และ C ++ อันที่จริงแล้วไฟล์ส่วนหัวเป็นแบบนี้และมักจะมีรหัส (ฟังก์ชั่นมาโครและอินไลน์) การมีไฟล์.c/ .cppเพื่อคอมไพล์เป็นทั้งสองอย่างนั้นไม่ค่อยมีประโยชน์ แต่มีกรณีหนึ่งที่เพิ่มการthrowสนับสนุนC ++ เมื่อคอมไพล์ด้วยคอมไพเลอร์ C ++ (แต่return -1;เมื่อคอมไพล์ด้วยคอมไพเลอร์ C หรืออะไรก็ตาม)
ไฮด์

37
หากมีคนโทร malloc แบบอินไลน์ในส่วนหัวฉันจะไม่ประทับใจ #ifdef __cplusplus และ extern "C" {} นั้นมีไว้สำหรับงานนี้ไม่ใช่การเพิ่ม cast พิเศษ
paulm

15
จุดที่ 1 นั้นไม่เกี่ยวข้องเนื่องจาก C! = C ++ จุดอื่น ๆ นั้นก็เล็กน้อยถ้าคุณใช้ตัวแปรในการmallocโทรของคุณ: char **foo = malloc(3*sizeof(*foo));ถ้าค่อนข้างพิสูจน์ได้เต็ม: 3 พอยน์เตอร์ไปยังพอยน์เตอร์พอยน์ foo[i] = calloc(101, sizeof(*(foo[i])));แล้วห่วงและไม่ จัดสรรอาเรย์ของ 101 chars ซึ่งเริ่มต้นเป็นศูนย์ได้อย่างเรียบร้อย ไม่จำเป็นต้องใช้การร่าย เปลี่ยนคำประกาศเป็นunsigned charหรือประเภทอื่น ๆ สำหรับเรื่องนั้นและคุณก็ยังดี
Elias Van Ootegem

34
เมื่อฉันคิดว่าฉันได้มันมาถึงแล้ว! คำตอบที่ยอดเยี่ยม มันเป็นครั้งแรกที่นี่ใน StackOverflow ที่ฉัน +1 สองคำตอบที่ตรงข้าม! +1 ไม่คุณไม่แคสต์และ +1 ใช่คุณแคสต์แล้ว! ฮ่า ๆ. พวกคุณยอดเยี่ยมมาก และสำหรับฉันและนักเรียนของฉันฉันทำให้ฉัน: ฉันหล่อ ชนิดของข้อผิดพลาดที่นักเรียนทำจะถูกมองเห็นได้ง่ายขึ้นเมื่อทำการคัดเลือก
ดร. Beco

15
@Lushushenko: การทำซ้ำตัวเองในลักษณะที่ไม่สามารถตรวจสอบได้ด้วยเครื่องหรือโดยการตรวจสอบในท้องถิ่นไม่ดี การทำซ้ำตัวเองในรูปแบบที่สามารถตรวจสอบได้ด้วยวิธีการดังกล่าวนั้นไม่ดีน้อยกว่า ได้struct Zebra *p; ... p=malloc(sizeof struct Zebra);รับ malloc ไม่สามารถหลีกเลี่ยงข้อมูลที่ซ้ำซ้อนเกี่ยวกับประเภทของ p แต่การตรวจสอบคอมไพเลอร์หรือรหัสท้องถิ่นจะไม่พบปัญหาใด ๆ หากมีการเปลี่ยนแปลงประเภทหนึ่ง แต่คนอื่นไม่ได้ เปลี่ยนรหัสไปp=(struct Zebra*)malloc(sizeof struct Zebra);และเรียบเรียงจะบ่นถ้าโยนประเภทไม่ตรงpและท้องถิ่นการตรวจสอบจะเปิดเผย ...
SuperCat

170

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

#ifdef __cplusplus
# define NEW(type, count) ((type *)calloc(count, sizeof(type)))
#else
# define NEW(type, count) (calloc(count, sizeof(type)))
#endif

ด้วยวิธีนี้คุณยังสามารถเขียนได้ด้วยวิธีที่กะทัดรัดมาก:

int *sieve = NEW(int, 1);

และมันจะรวบรวมสำหรับ C และ C ++


17
เนื่องจากคุณใช้มาโครทำไมคุณไม่ใช้newในคำจำกัดความของ C ++
Hosam Aly

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

8
ไม่ต้องพูดถึงถ้าไม่ใช่คุณที่ปลดปล่อยหน่วยความจำ แต่อาจเป็นไลบรารี C ที่คุณใช้เป็นต้นปัญหาที่เป็นไปได้มากมายโดยไม่ได้รับอะไร
quinmars

86
@Hosam: ใช่แน่นอน ถ้าคุณใช้newคุณต้องใช้deleteและถ้าคุณใช้คุณต้องคุณmalloc() free()ไม่เคยผสมพวกเขา
แกรมเพอร์โรว์

17
หากมีใครใช้วิธีนี้การเรียกมาโครNEWอาจเป็นความคิดที่ไม่ดีเนื่องจากทรัพยากรจะไม่ถูกส่งคืนโดยใช้delete(หรือDELETE) ดังนั้นคุณจึงผสมคำศัพท์ของคุณ ให้ตั้งชื่อMALLOCแทนหรือCALLOCในกรณีนี้จะทำให้มีเหตุผลมากกว่า
mah

139

จากวิกิพีเดีย :

ข้อดีในการหล่อ

  • การร่ายอาจรวมถึงโปรแกรม C หรือฟังก์ชั่นเพื่อคอมไพล์เป็น C ++

  • ผู้ส่งอนุญาตให้ malloc รุ่นก่อนปี 1989 ซึ่งเดิมส่งคืนอักขระ char *

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

ข้อเสียในการคัดเลือกนักแสดง

  • ภายใต้มาตรฐาน ANSI C การโยนซ้ำซ้อน

  • การเพิ่มเพี้ยนอาจปิดบังความล้มเหลวในการรวมส่วนหัวstdlib.hซึ่งพบต้นแบบสำหรับ malloc ในกรณีที่ไม่มีต้นแบบสำหรับ malloc มาตรฐานกำหนดให้คอมไพเลอร์ C ถือว่า malloc ส่งคืน int หากไม่มีการร่ายคำเตือนจะถูกออกเมื่อจำนวนเต็มนี้ถูกกำหนดให้กับตัวชี้ อย่างไรก็ตามด้วยตัวบล็อกคำเตือนนี้ไม่ได้สร้างขึ้นและซ่อนบั๊ก สำหรับสถาปัตยกรรมและโมเดลข้อมูลบางอย่าง (เช่น LP64 บนระบบ 64- บิตโดยที่ความยาวและตัวชี้เป็น 64- บิตและ int เป็น 32- บิต) ข้อผิดพลาดนี้อาจส่งผลให้เกิดพฤติกรรมที่ไม่ได้กำหนดจริง ๆ ค่าบิตในขณะที่ฟังก์ชั่นที่กำหนดไว้จริงส่งกลับค่า 64 บิต ขึ้นอยู่กับแผนการโทรและโครงร่างหน่วยความจำซึ่งอาจส่งผลให้สแตกยอดเยี่ยม ปัญหานี้มีโอกาสน้อยที่จะไม่มีใครสังเกตเห็นในคอมไพเลอร์สมัยใหม่ เนื่องจากพวกเขาสร้างคำเตือนอย่างสม่ำเสมอว่ามีการใช้ฟังก์ชันที่ไม่ได้ประกาศดังนั้นคำเตือนจะยังคงปรากฏ ตัวอย่างเช่นพฤติกรรมเริ่มต้นของ GCC คือการแสดงคำเตือนว่า "การประกาศโดยนัยที่เข้ากันไม่ได้ของฟังก์ชั่นในตัว" โดยไม่คำนึงว่านักแสดงมีหรือไม่

  • หากชนิดของตัวชี้เปลี่ยนไปเมื่อมีการประกาศอาจจำเป็นต้องเปลี่ยนทุกบรรทัดที่ malloc ถูกเรียกและส่ง

แม้ว่าmalloc โดยไม่ต้องแคสติ้งเป็นวิธีที่ต้องการและโปรแกรมเมอร์ที่มีประสบการณ์มากที่สุดเลือกมันแต่คุณควรใช้วิธีใดก็ตามที่คุณต้องการรับรู้ปัญหา

เช่น: ถ้าคุณต้องการที่จะรวบรวมโปรแกรม C เป็น C ++ (แม้ว่ามันจะเป็นภาษาที่แยกต่างหาก) mallocคุณต้องโยนผลมาจากการใช้งาน


1
อะไร " หล่อสามารถช่วยให้นักพัฒนาที่ระบุไม่สอดคล้องกันในการปรับขนาดชนิดควรประเภทการเปลี่ยนแปลงตัวชี้ปลายทางโดยเฉพาะอย่างยิ่งถ้าตัวชี้มีการประกาศให้ห่างไกลจากmalloc()การโทร " หมายความว่าอย่างไร คุณยกตัวอย่างได้ไหม
Spikatrix

3
@CoolGuy: ดูความคิดเห็นก่อนหน้านี้เมื่อคำตอบอื่น แต่ทราบว่าp = malloc(sizeof(*p) * count)สำนวนจะรับการเปลี่ยนแปลงในประเภทโดยอัตโนมัติดังนั้นคุณไม่จำเป็นต้องได้รับคำเตือนและเปลี่ยนอะไรเลย ดังนั้นนี่ไม่ใช่ข้อได้เปรียบที่แท้จริงกับทางเลือกที่ดีที่สุดสำหรับการไม่หล่อ
Peter Cordes

7
นี่คือคำตอบที่ถูกต้อง: มีข้อดีและข้อเสียและมันเดือดลงไปในเรื่องของรสนิยม (เว้นแต่รหัสจะต้องรวบรวมเป็น C ++ - จากนั้นนักแสดงมีผลบังคับใช้)
Peter - Reinstate Monica

3
จุดที่ 3 เป็นสิ่งที่สงสัยเนื่องจากหากประเภทของตัวชี้เปลี่ยนไปเมื่อมีการประกาศจึงควรตรวจสอบทุกอินสแตนซ์ของ malloc, realloc และ free inolving ประเภทนั้น การหล่อจะบังคับให้คุณทำอย่างนั้น
Michaël Roy

104

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


100

คุณไม่ได้แสดงผลลัพธ์ของ malloc เนื่องจากการทำเช่นนั้นจะเพิ่มความยุ่งเหยิงที่ไม่มีจุดหมายในรหัสของคุณ

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

ความคิดเห็นบางส่วน:

  • ตัวชี้โมฆะสามารถแปลงเป็น / จากชนิดตัวชี้อื่น ๆ โดยไม่ต้องร่ายอย่างชัดเจน (C11 6.3.2.3 และ 6.5.16.1)

  • อย่างไรก็ตาม C ++ จะไม่อนุญาตให้ทำการรัวโดยนัยระหว่างvoid*และประเภทพอยน์เตอร์อื่น ดังนั้นใน C ++ นักแสดงจะต้องถูกต้อง แต่ถ้าคุณเขียนโปรแกรมใน C ++ คุณควรใช้newและไม่ใช่ malloc () และคุณไม่ควรคอมไพล์รหัส C ด้วยคอมไพเลอร์ C ++

    หากคุณต้องการสนับสนุนทั้ง C และ C ++ ด้วยซอร์สโค้ดเดียวกันให้ใช้สวิตช์คอมไพเลอร์เพื่อทำเครื่องหมายความแตกต่าง อย่าพยายามป้อยอมาตรฐานภาษาทั้งสองด้วยรหัสเดียวกันเพราะไม่เข้ากัน

  • หากคอมไพเลอร์ C ไม่พบฟังก์ชันเนื่องจากคุณลืมใส่ส่วนหัวคุณจะได้รับข้อผิดพลาดเกี่ยวกับคอมไพเลอร์ / ลิงเกอร์ ดังนั้นหากคุณลืมที่จะรวม<stdlib.h>ที่ไม่ใหญ่คุณจะไม่สามารถสร้างโปรแกรมของคุณ

  • สำหรับคอมไพเลอร์โบราณที่เป็นไปตามมาตรฐานที่เก่ากว่า 25 ปีการลืมใส่เข้าไป<stdlib.h>จะส่งผลให้เกิดพฤติกรรมที่เป็นอันตราย intเพราะในมาตรฐานโบราณที่ฟังก์ชั่นโดยไม่ต้องเป็นต้นแบบที่มองเห็นได้โดยปริยายแปลงชนิดกลับไป การส่งผลลัพธ์จาก malloc อย่างชัดเจนจะซ่อนข้อบกพร่องนี้

    แต่นั่นไม่ใช่ปัญหาจริงๆ คุณไม่ได้ใช้คอมพิวเตอร์อายุ 25 ปีเหตุใดคุณจึงใช้คอมไพเลอร์อายุ 25 ปี


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

"ตัวชี้โมฆะสามารถแปลงเป็น / จากตัวชี้ประเภทอื่น ๆ ที่ไม่มีการร่ายอย่างชัดเจน" ไม่รองรับ 6.3.2.3 บางทีคุณคิดว่า "ตัวชี้ไปยังประเภทวัตถุใด ๆ "? "โมฆะตัวชี้" และ "ตัวชี้ไปยังฟังก์ชั่น" ไม่สามารถเปลี่ยนแปลงได้อย่างง่ายดาย
chux - Reinstate Monica

การอ้างอิงนั้นไม่สมบูรณ์ ส่วนที่เกี่ยวข้องสำหรับ "implicitness" คือกฎของการมอบหมายอย่างง่าย 6.5.16.1 "หนึ่งตัวถูกดำเนินการเป็นตัวชี้ไปยังประเภทวัตถุและอื่น ๆ เป็นตัวชี้ไปเป็นโมฆะรุ่นที่ผ่านการรับรองหรือไม่มีเงื่อนไข" ฉันได้เพิ่มการอ้างอิงนี้ไปยังคำตอบเพื่อความสมบูรณ์
Lundin

91

ใน C คุณจะได้รับการแปลงโดยนัยจากvoid *ตัวชี้ (ข้อมูล) อื่น ๆ


6
@Jens: ตกลงบางทีถ้อยคำที่เหมาะสมยิ่งกว่าก็คือ "การแปลงโดยนัย" เช่นเดียวกับการใช้ตัวแปรสำคัญในการแสดงออกจุดลอย
EFraim

@efraim ที่จริงจะส่งผลในการโยนและหนึ่งโดยนัยที่
นักฟิสิกส์บ้า

71

malloc()ไม่จำเป็นต้องทำการหล่อคืนค่าที่ส่งคืนแต่ฉันต้องการเพิ่มจุดหนึ่งที่ดูเหมือนว่าไม่มีใครชี้ให้เห็น:

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

การอ้างอิง: คำถามที่พบบ่อย C


2
การปิดคำเตือนของคอมไพเลอร์เป็นความคิดที่ผิด
Albert van der Horst

8
@AlbertvanderHorst ไม่ว่าคุณจะทำเช่นนั้นโดยการแก้ปัญหาที่แน่นอนคำเตือนก็คือเพื่อเตือนคุณ
Dan Bechard

@ ด่าน หากการแก้ไขปัญหาที่แน่นอนนั้นหมายถึงการเขียนรูทีนย่อยใหม่เพื่อส่งคืน ANSI C ชนิดใหม่แทนที่จะเป็นถ่าน * ฉันก็เห็นด้วย ฉันจะไม่เรียกมันว่าปิดคอมไพเลอร์ อย่าให้ผู้จัดการที่ยืนยันว่าไม่มีคำเตือนของคอมไพเลอร์แทนที่จะใช้พวกเขาโดยการคอมไพล์ใหม่แต่ละครั้งเพื่อค้นหาปัญหาที่อาจเกิดขึ้น Groetjes Albert
Albert van der Horst

53

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

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


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

2
"การคัดเลือกนักแสดงหลักจะไม่เปลี่ยนแปลงอะไรในวิธีการใช้งาน" การคัดเลือกนักแสดงประเภทการจับคู่ไม่ควรเปลี่ยนแปลงอะไรเลย แต่การเปลี่ยนประเภทของ var และนักแสดงไม่ตรงกันอีกต่อไปปัญหาอาจเกิดขึ้นได้หรือไม่ IWOs ประเภท cast และ var ควรถูกซิงค์อย่างต่อเนื่อง - สองเท่าของการบำรุงรักษา
chux - Reinstate Monica

ฉันเห็นได้ว่าทำไม Profs ถึงชอบการคัดเลือกนักแสดง การคัดเลือกนักแสดงอาจมีประโยชน์จากมุมมองทางการศึกษาที่บ่งบอกถึงข้อมูลผู้สอนและไม่จำเป็นต้องรักษารหัสนักเรียน - รหัสการโยนทิ้ง แต่จากมุมมองการเขียนโค้ดการตรวจสอบและการบำรุงรักษาp = malloc(sizeof *p * n);นั้นทำได้ง่ายและดีขึ้น
chux - Reinstate Monica

53

ไม่จำเป็นต้องส่งผลลัพธ์ของmallocเนื่องจากส่งคืนvoid*และvoid*สามารถชี้ไปที่ประเภทข้อมูลใด ๆ


34

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

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


2
ไม่ใช่กรณีการใช้งานปกติในการรวบรวมแหล่งเดียวเป็นทั้ง C และ C ++ (ซึ่งตรงข้ามกับการใช้ไฟล์ส่วนหัวที่มีการประกาศเพื่อเชื่อมโยงรหัส C และ C ++ ด้วยกัน) การใช้mallocและเพื่อน ๆ ใน C ++ เป็นสัญญาณเตือนที่ดีว่าสมควรได้รับความสนใจเป็นพิเศษ (หรือเขียนใหม่ใน C)
Toby Speight

1
"ตัวชี้โมฆะเป็นตัวชี้ทั่วไป" -> "ตัวชี้โมฆะเป็นตัวชี้วัตถุทั่วไป" ขนาดพอยน์เตอร์ของฟังก์ชั่นสามารถเกิน void *ได้ดังนั้นจึงvoid *ไม่เพียงพอในการจัดเก็บตัวชี้ฟังก์ชัน
chux - Reinstate Monica

ความตั้งใจของฉันในบรรทัดนั้นเหมือนกัน แต่ขอบคุณ @ chux สำหรับคำแนะนำ
มุมานะ

33

นี่คือสิ่งที่คู่มืออ้างอิงไลบรารีของ GNU Cพูดว่า:

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

และแน่นอนมาตรฐาน ISO C11 (p347) บอกว่า:

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


31

ชนิดที่ส่งคืนเป็นโมฆะ * ซึ่งสามารถส่งไปยังตัวชี้ชนิดข้อมูลที่ต้องการเพื่อยกเลิกการอ้างอิง


1
void* สามารถแปลงเป็นประเภทที่ต้องการ แต่ไม่จำเป็นต้องทำเช่นนั้นเพราะมันจะถูกแปลงโดยอัตโนมัติ ดังนั้นนักแสดงจึงไม่จำเป็นและในความเป็นจริงนั้นไม่พึงปรารถนาด้วยเหตุผลที่กล่าวถึงในคำตอบที่ให้คะแนนสูง
Toby Speight

แต่เฉพาะในกรณีที่คุณจำเป็นต้องตรวจสอบมัน "ในทันที" หากคุณสร้างตัวแปรแทนมันจะถูกแปลงอย่างปลอดภัยและโดยอัตโนมัติเป็นประเภทที่มีประสิทธิภาพของตัวแปรโดยไม่ต้องร่าย (ใน C)
Ferrarezi

28

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

#include <stdlib.h>
#define NEW_ARRAY(ptr, n) (ptr) = malloc((n) * sizeof *(ptr))
#define NEW(ptr) NEW_ARRAY((ptr), 1)

ด้วยสิ่งเหล่านี้คุณสามารถพูดได้

NEW_ARRAY(sieve, length);

สำหรับอาร์เรย์ที่ไม่ใช่แบบไดนามิกแมโครที่ต้องมีฟังก์ชันที่สามคือ

#define LEN(arr) (sizeof (arr) / sizeof (arr)[0])

ซึ่งทำให้อาร์เรย์ลูปปลอดภัยและสะดวกยิ่งขึ้น:

int i, a[100];

for (i = 0; i < LEN(a); i++) {
   ...
}

"ตัวชี้โมฆะสามารถกำหนดให้ใด ๆวัตถุชี้" ชี้ฟังก์ชั่นที่มีปัญหาอื่นแม้จะไม่ได้เป็นmalloc()อย่างใดอย่างหนึ่ง
chux - Reinstate Monica

การกำหนดvoid*to / from ตัวชี้ฟังก์ชันอาจสูญเสียข้อมูลดังนั้น "ตัวชี้ void สามารถกำหนดให้กับตัวชี้ใด ๆ " เป็นปัญหาในกรณีเหล่านั้น การกำหนด a void*, จากmalloc() ไปยังวัตถุตัวชี้ใด ๆนั้นไม่ใช่ปัญหา
chux - Reinstate Monica

doความคิดเห็นห่วงที่เกี่ยวข้องกับแมโครที่เกี่ยวข้องกับห่วงเลยว่าสงสัยออกไปจากคำถามชื่อ กำลังลบความคิดเห็นนั้น จะเอาอันนี้ลงภายหลังด้วย
chux - Reinstate Monica

27

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


1
ฟังก์ชันmallocส่งคืนตัวชี้ void ใน C เช่นกัน แต่กฎของภาษานั้นแตกต่างจาก C ++
สิงหาคม Karlstrom

16

ผู้คนที่เคยใช้ GCC และ Clang นั้นได้รับความเสียหาย มันไม่ใช่สิ่งที่ดีออกไป

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

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

อาร์กิวเมนต์ที่ไม่จำเป็นภายใต้มาตรฐานปัจจุบันค่อนข้างถูกต้อง แต่การถกเถียงนั้นละเว้นการปฏิบัติจริงของโลกแห่งความจริง เราไม่ได้รหัสในโลกที่ปกครองโดยมาตรฐานของวัน แต่โดยการปฏิบัติของสิ่งที่ฉันต้องการเรียกว่า "เขตข้อมูลความเป็นจริงของการจัดการท้องถิ่น" และนั่นก็คือการบิดและการบิดมากกว่าเวลาที่เคยเป็นมา :-)

YMMV

ฉันมักจะคิดว่าการหล่อ malloc เป็นการป้องกัน ไม่สวยไม่สมบูรณ์ แต่ปลอดภัยโดยทั่วไป (สุจริตถ้าคุณไม่รวม stdlib.h แล้วคุณได้วิธีปัญหามากกว่าหล่อ malloc!)


15

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

double d;
void *p = &d;
int *q = p;

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

ในความเป็นจริงการปฏิบัติที่ดีคือการห่อmalloc(และเพื่อน) ด้วยฟังก์ชั่นที่ส่งคืนunsigned char *และโดยทั่วไปจะไม่ใช้void *ในรหัสของคุณ หากคุณต้องการตัวชี้แบบทั่วไปไปยังวัตถุใด ๆ ให้ใช้char *หรือunsigned char *และได้ปลดเปลื้องในทั้งสองทิศทาง การผ่อนคลายอย่างหนึ่งที่สามารถผ่อนคลายได้คือการใช้ฟังก์ชั่นเหมือนmemsetและmemcpyไม่ได้ปลดเปลื้อง

ในหัวข้อของการแคสติ้งและความเข้ากันได้ของ C ++ ถ้าคุณเขียนโค้ดของคุณเพื่อให้คอมไพล์เป็นทั้ง C และ C ++ (ในกรณีที่คุณต้องส่งคืนค่าmallocเมื่อกำหนดให้กับสิ่งอื่นที่ไม่ใช่void *) คุณสามารถทำประโยชน์ได้มาก สิ่งที่คุณต้องทำเอง: คุณสามารถใช้มาโครในการคัดเลือกนักแปลที่แปลเป็นสไตล์ C ++ เมื่อคอมไพล์เป็น C ++ แต่ลดลงเป็น C cast เมื่อคอมไพล์เป็น C:

/* In a header somewhere */
#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

หากคุณปฏิบัติตามมาโครเหล่านี้การgrepค้นหาฐานรหัสของคุณอย่างง่าย ๆสำหรับตัวระบุเหล่านี้จะแสดงให้คุณเห็นว่าคาสต์ทั้งหมดของคุณอยู่ในตำแหน่งใดดังนั้นคุณสามารถตรวจสอบได้ว่าส่วนใดไม่ถูกต้อง

ถ้าคุณคอมไพล์โค้ดเป็นประจำด้วย C ++ มันจะบังคับใช้การร่ายที่เหมาะสม ตัวอย่างเช่นหากคุณใช้strip_qualเพียงเพื่อลบconstหรือvolatileแต่โปรแกรมเปลี่ยนไปในลักษณะที่การแปลงประเภทเกี่ยวข้องกับตอนนี้คุณจะได้รับการวินิจฉัยและคุณจะต้องใช้การรวมกันของ cast เพื่อรับการแปลงที่ต้องการ

เพื่อช่วยให้คุณปฏิบัติตามมาโครเหล่านี้คอมไพเลอร์ GNU C ++ (ไม่ใช่ C!) มีคุณสมบัติที่สวยงาม: การวินิจฉัยเสริมซึ่งถูกสร้างขึ้นสำหรับการโยนสไตล์ C ทุกครั้ง

     -Wold-style-cast (C ++ และ Objective-C ++ เท่านั้น)
         เตือนถ้ามีการใช้งานคาสต์แบบเก่า (แบบ C) ในแบบที่ไม่ใช่โมฆะ
         ภายในโปรแกรม C ++ casts สไตล์ใหม่ (dynamic_cast
         static_cast, reinterpret_cast และ const_cast) มีความเสี่ยงน้อยกว่า
         เพื่อเอฟเฟกต์ที่ไม่ตั้งใจและง่ายต่อการค้นหา

หากรหัส C ของคุณคอมไพล์เป็น C ++ คุณสามารถใช้-Wold-style-castตัวเลือกนี้เพื่อค้นหา(type)ไวยากรณ์การส่งทั้งหมดที่อาจคืบคลานเข้าไปในรหัสและติดตามการวินิจฉัยเหล่านี้โดยแทนที่ด้วยตัวเลือกที่เหมาะสมจากมาโครด้านบน (หรือ รวมกันหากจำเป็น)

การรักษาของการแปลงนี้เป็นที่ใหญ่ที่สุดแบบสแตนด์อโลนเหตุผลทางเทคนิคเดียวสำหรับการทำงานใน "Clean C": รวม C และ C ++ ภาษาซึ่งในทางกลับกันในทางเทคนิค justifies mallocหล่อค่าตอบแทนของ


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

@ Phil1970 มันเขียนด้วยภาษาถิ่นเดียวซึ่งเกิดขึ้นกับพกพาไปยังคอมไพเลอร์ C และ C ++ และใช้ประโยชน์จากความสามารถบางอย่างของ C ++ จะต้องมีการรวบรวมทั้งหมดเป็น C ++ มิฉะนั้นจะต้องรวบรวมเป็น C.
Kaz

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

15

สิ่งที่ดีที่สุดที่ควรทำเมื่อเขียนโปรแกรมใน C ทุกครั้งที่ทำได้:

  1. ทำให้โปรแกรมของคุณคอมไพล์ผ่านคอมไพเลอร์ C โดยเปิดคำเตือนทั้งหมด-Wallและแก้ไขข้อผิดพลาดและคำเตือนทั้งหมด
  2. ตรวจสอบให้แน่ใจว่าไม่มีตัวแปรที่ประกาศเป็น auto
  3. จากนั้นรวบรวมโดยใช้ภาษา C ++ คอมไพเลอร์ด้วยและ-Wall -std=c++11แก้ไขข้อผิดพลาดและคำเตือนทั้งหมด
  4. ตอนนี้คอมไพล์โดยใช้คอมไพเลอร์ C อีกครั้ง โปรแกรมของคุณควรจะรวบรวมโดยไม่มีการเตือนใด ๆ และมีข้อผิดพลาดน้อยลง

ขั้นตอนนี้ช่วยให้คุณสามารถใช้ประโยชน์จากการตรวจสอบอย่างเข้มงวดของ C ++ ซึ่งช่วยลดจำนวนข้อผิดพลาด โดยเฉพาะอย่างยิ่งขั้นตอนนี้บังคับให้คุณรวมstdlib.hหรือคุณจะได้รับ

malloc ไม่ได้ประกาศภายในขอบเขตนี้

และยังบังคับให้คุณส่งผลmallocหรือคุณจะได้รับ

การแปลงที่ไม่ถูกต้องจากvoid*เป็นT*

หรือประเภทเป้าหมายของคุณคืออะไร

ประโยชน์เฉพาะจากการเขียนใน C แทน C ++ ที่ฉันหาได้คือ

  1. C มี ABI ที่ระบุอย่างดี
  2. C ++ อาจสร้างรหัสเพิ่มเติม [ข้อยกเว้น RTTI แม่แบบpolymorphism แบบรันไทม์ ]

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

สำหรับผู้ที่พบกฎเข้มงวดของ C ++ ไม่สะดวกเราสามารถใช้คุณสมบัติ C ++ 11 กับประเภทที่อนุมานได้

auto memblock=static_cast<T*>(malloc(n*sizeof(T))); //Mult may overflow...

18
ใช้คอมไพเลอร์ C สำหรับรหัส C ใช้คอมไพเลอร์ C ++ สำหรับรหัส C ++ ไม่ว่าจะเป็นแค่ไม่มี การเขียนโค้ด C ซ้ำใน C ++ เป็นอีกสิ่งหนึ่งโดยสิ้นเชิงและอาจจะหรือไม่คุ้มค่ากับเวลาและความเสี่ยง
Toby Speight

2
ฉันต้องการเพิ่มคำแนะนำ @TobySpeight: หากคุณต้องการใช้รหัส C ในโครงการ C ++ คุณสามารถรวบรวมรหัส C เป็น C (เช่นgcc -c c_code.c), รหัส C ++ เป็น C ++ (เช่นg++ -c cpp_code.cpp) แล้วเชื่อมโยงเข้าด้วยกัน (เช่นgcc c_code.o cpp_code.oในทางกลับกันขึ้นอยู่กับการพึ่งพาของโครงการ) ตอนนี้ไม่มีเหตุผลที่จะกีดกันตัวคุณเองในคุณสมบัติที่ดีของภาษาใดภาษาหนึ่ง ...
ออทิสติก

1
@ user877329 มันเป็นทางเลือกที่สมเหตุสมผลกว่าในการเพิ่มการร่ายลงในรหัสอย่างระมัดระวังซึ่งจะลดความชัดเจนของรหัสเพื่อความเป็น "C ++ ที่เข้ากันได้" เท่านั้น
ออทิสติก

1
อาจเป็นประโยชน์หลักในบริบทนี้คือ C ช่วยให้คุณเขียนp = malloc(sizeof(*p));ซึ่งไม่จำเป็นต้องเปลี่ยนแปลงในสถานที่แรกหากpการเปลี่ยนแปลงชื่อประเภทที่แตกต่างกัน "การได้เปรียบ" จากการคัดเลือกนักแสดงคือคุณได้รับข้อผิดพลาดในการคอมไพล์ถ้าpเป็นประเภทที่ไม่ถูกต้อง
Peter Cordes

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

15

malloc()ไม่มีคุณไม่ได้โยนผลมาจากการ

โดยทั่วไปแล้วคุณไม่ได้โยนไปยังหรือจากvoid *

เหตุผลทั่วไปที่ให้ไว้สำหรับการไม่ทำเช่นนั้นคือการ#include <stdlib.h>ไม่สามารถสังเกตเห็นได้ นี่ไม่ใช่ปัญหาอีกต่อไปเป็นเวลานานเนื่องจาก C99 ได้ประกาศฟังก์ชั่นโดยนัยไว้อย่างผิดกฎหมายดังนั้นหากคอมไพเลอร์ของคุณสอดคล้องกับ C99 เป็นอย่างน้อยคุณจะได้รับข้อความวินิจฉัย

แต่มีเหตุผลที่ดีกว่ามากที่จะไม่แนะนำตัวชี้ที่ไม่จำเป็นปลดเปลื้อง:

ใน C เป็นนักแสดงตัวชี้มักจะมีข้อผิดพลาด นี่เป็นเพราะกฎต่อไปนี้ ( §6.5 p7ใน N1570 ซึ่งเป็นร่างล่าสุดสำหรับ C11):

วัตถุต้องมีค่าที่เก็บไว้เข้าถึงได้โดยนิพจน์ lvalue ที่มีประเภทใดประเภทหนึ่งต่อไปนี้:
- ชนิดที่เข้ากันได้กับชนิดของวัตถุที่มีประสิทธิภาพ
- รุ่นที่ผ่านการรับรองของชนิดที่เข้ากันได้กับชนิดประสิทธิผลของวัตถุ
- ประเภทที่เป็นประเภทที่ลงนามหรือไม่ได้ลงนามที่สอดคล้องกับประเภทของวัตถุที่มีประสิทธิภาพ
- ประเภทที่เป็นประเภทที่ลงนามหรือไม่ได้ลงนามที่สอดคล้องกับรุ่นที่มีประสิทธิภาพของประเภทของวัตถุ
- ประเภทรวมหรือสหภาพที่มี ของประเภทดังกล่าวในหมู่สมาชิก (รวมถึง recursively, สมาชิกของ subaggregate หรือสหภาพที่มีอยู่) หรือ
- ประเภทอักขระ

นอกจากนี้ยังเป็นที่รู้จักกันเป็นกฎ aliasing เข้มงวด ดังนั้นรหัสต่อไปนี้คือพฤติกรรมที่ไม่ได้กำหนด :

long x = 5;
double *p = (double *)&x;
double y = *p;

และบางครั้งก็น่าประหลาดใจต่อไปนี้ก็เช่นกัน:

struct foo { int x; };
struct bar { int x; int y; };
struct bar b = { 1, 2};
struct foo *p = (struct foo *)&b;
int z = p->x;

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

TL; DR

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


หมายเหตุด้านข้าง:

  • มีหลายกรณีที่คุณจำเป็นต้องใช้ตัวส่งจริงvoid *เช่นหากคุณต้องการพิมพ์ตัวชี้:

    int x = 5;
    printf("%p\n", (void *)&x);

    การส่งมีความจำเป็นที่นี่เนื่องจากprintf()เป็นฟังก์ชัน Variadic ดังนั้นการแปลงโดยนัยจึงไม่ทำงาน

  • ใน C ++ สถานการณ์จะแตกต่างกัน ประเภทของตัวชี้ชี้ขาดนั้นค่อนข้างธรรมดา (และถูกต้อง) เมื่อจัดการกับวัตถุของคลาสที่ได้รับ ดังนั้นจึงเหมาะสมที่ใน C ++ การแปลงไปและกลับไม่ได้บอกvoid *เป็นนัย C ++ มีทั้งชุดของการหล่อที่แตกต่างกัน


1
ในตัวอย่างของคุณคุณหลีกเลี่ยงโมฆะ * มีความแตกต่างระหว่างการร่ายจาก double * ถึง int * และในทางกลับกัน malloc ส่งคืน pointel ที่จัดอยู่ในประเภทมาตรฐานที่ใหญ่ที่สุดดังนั้นจึงไม่มีกฎนามแฝงใด ๆ ที่ใช้งานไม่ได้แม้ว่าบางคนจะปลดเปลื้องรูปแบบนี้
P__J__

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

@PeterJ: ในกรณีที่มีจุดคือการหลีกเลี่ยงการโยนตัวชี้ที่ไม่จำเป็นดังนั้นจึงไม่เหมือนรหัสที่คุณต้องให้ความสนใจเป็นพิเศษ

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

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

15

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


12

การส่งเป็นเพียงสำหรับ C ++ ไม่ใช่ C ในกรณีที่คุณใช้คอมไพเลอร์ C ++ คุณควรเปลี่ยนเป็นคอมไพเลอร์ C


9

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


11
" มันไม่ใช่ข้อบังคับ - แม้ว่าคุณจะต้องทำ " - ฉันคิดว่ามีข้อขัดแย้ง!
Toby Speight

5
ฉันคิดว่าคุณควรอ่านโพสต์นี้ให้กับบางคนและดูว่าพวกเขาเข้าใจในสิ่งที่คุณพยายามจะพูดหรือไม่ จากนั้นเขียนใหม่อีกครั้งทำให้ชัดเจนว่าคุณต้องการพูดอะไร ฉันไม่เข้าใจจริงๆว่าคำตอบของคุณคืออะไร
Bill Woodger

9

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

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


9
  1. ตามที่ระบุไว้อื่น ๆ มันไม่จำเป็นสำหรับ C แต่สำหรับ C ++

  2. การร่ายอาจรวมถึงโปรแกรม C หรือฟังก์ชั่นเพื่อคอมไพล์เป็น C ++

  3. ใน C ไม่จำเป็นเนื่องจาก void * จะได้รับการเลื่อนตำแหน่งอย่างอัตโนมัติและปลอดภัยสำหรับตัวชี้ประเภทอื่น ๆ

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

    เนื่องจากstdlib.hมีต้นแบบสำหรับ malloc ที่พบ ในกรณีที่ไม่มีต้นแบบสำหรับ malloc มาตรฐานกำหนดให้คอมไพเลอร์ C ถือว่า malloc ส่งคืน int หากไม่มีการร่ายคำเตือนจะถูกส่งเมื่อจำนวนเต็มนี้ถูกกำหนดให้กับตัวชี้ อย่างไรก็ตามด้วยตัวบล็อกคำเตือนนี้ไม่ได้สร้างขึ้นและซ่อนบั๊ก


7

การหล่อ malloc ไม่จำเป็นใน C แต่บังคับใน C ++

การหล่อนั้นไม่จำเป็นใน C เพราะ:

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

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


0

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

อาจมีเหตุผลอื่น แต่ด้วยเหตุผลอื่น ๆ แน่นอนว่าคุณจะประสบปัญหาร้ายแรงไม่ช้าก็เร็ว


0

คุณสามารถ แต่ไม่จำเป็นต้องส่งใน C. คุณต้องส่งถ้ารหัสนั้นคอมไพล์เป็น C ++

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