ฉันได้อ่านใน Stack Overflow ว่าฟังก์ชัน C บางฟังก์ชัน "ล้าสมัย" หรือ "ควรหลีกเลี่ยง" คุณช่วยยกตัวอย่างฟังก์ชันประเภทนี้และเหตุผลให้ฉันได้ไหม
มีทางเลือกใดบ้างสำหรับฟังก์ชันเหล่านั้น
เราสามารถใช้ได้อย่างปลอดภัย - มีแนวทางปฏิบัติที่ดีหรือไม่?
ฉันได้อ่านใน Stack Overflow ว่าฟังก์ชัน C บางฟังก์ชัน "ล้าสมัย" หรือ "ควรหลีกเลี่ยง" คุณช่วยยกตัวอย่างฟังก์ชันประเภทนี้และเหตุผลให้ฉันได้ไหม
มีทางเลือกใดบ้างสำหรับฟังก์ชันเหล่านั้น
เราสามารถใช้ได้อย่างปลอดภัย - มีแนวทางปฏิบัติที่ดีหรือไม่?
strncpy()
แทนทั่วไปstrcpy()
และฉันจะไม่ใช้strncat()
เลยเพราะมันมีอินเทอร์เฟซที่ไม่ใช้งานง่ายที่สุดเท่าที่จะเป็นไปได้ - คุณรู้หรือไม่ว่าพารามิเตอร์ความยาวระบุอะไร
คำตอบ:
ฟังก์ชันที่
ไม่รองรับไม่ปลอดภัย
ตัวอย่างที่สมบูรณ์แบบของฟังก์ชันดังกล่าวคือgets ()เนื่องจากไม่มีวิธีใดที่จะบอกได้ว่าบัฟเฟอร์ปลายทางมีขนาดใหญ่เพียงใด ดังนั้นโปรแกรมใด ๆ ที่อ่านอินพุตใช้รับ () มีช่องโหว่หน่วยความจำล้น สำหรับเหตุผลที่คล้ายกันควรใช้strncpy ()ในสถานที่ของstrcpy ()และstrncat ()ในสถานที่ของstrcat ()
ตัวอย่างเพิ่มเติมบางส่วน ได้แก่ฟังก์ชันtmpfile ()และmktemp ()เนื่องจากปัญหาด้านความปลอดภัยที่อาจเกิดขึ้นกับการเขียนทับไฟล์ชั่วคราวและสิ่งที่ถูกแทนที่ด้วยฟังก์ชันmkstemp () ที่ปลอดภัยยิ่งขึ้น
Non-Reentrant
ตัวอย่างอื่น ๆ ได้แก่gethostbyaddr ()และgethostbyname ()ซึ่งจะไม่ reentrant (และดังนั้นจึงไม่รับประกันว่าจะเป็นด้าย) และได้รับการแทนที่โดย reentrant getaddrinfo ()และfreeaddrinfo ()
คุณอาจสังเกตเห็นรูปแบบที่นี่ ... ทั้งการขาดความปลอดภัย (อาจเกิดจากการไม่รวมข้อมูลที่เพียงพอในลายเซ็นเพื่อนำไปใช้อย่างปลอดภัย) หรือการไม่กลับเข้ามาใหม่เป็นแหล่งที่มาของการเลิกใช้งานทั่วไป
ล้าสมัยไม่พกพา
ฟังก์ชันอื่น ๆ บางอย่างเลิกใช้งานไปแล้วเนื่องจากมีฟังก์ชันการทำงานซ้ำกันและไม่สามารถพกพาได้เหมือนกับฟังก์ชันอื่น ๆ ยกตัวอย่างเช่นbzero ()จะเลิกในความโปรดปรานของmemset ()
ความปลอดภัยและการตอบกลับของเธรดที่
คุณถามในโพสต์ของคุณเกี่ยวกับความปลอดภัยของเธรดและการย้อนกลับ มีความแตกต่างเล็กน้อย ฟังก์ชันจะถูกส่งกลับหากไม่ใช้สถานะที่แบ่งใช้ร่วมกันใด ๆ ที่เปลี่ยนแปลงได้ ตัวอย่างเช่นหากข้อมูลทั้งหมดที่ต้องการถูกส่งผ่านไปยังฟังก์ชันและบัฟเฟอร์ใด ๆ ที่จำเป็นจะถูกส่งผ่านไปยังฟังก์ชันด้วย (แทนที่จะใช้ร่วมกันโดยการเรียกใช้ฟังก์ชันทั้งหมด) ข้อมูลนั้นจะถูกส่งกลับ นั่นหมายความว่าเธรดที่แตกต่างกันโดยใช้พารามิเตอร์อิสระจะไม่เสี่ยงต่อสถานะการแชร์โดยไม่ได้ตั้งใจ การคืนสภาพเป็นการรับประกันที่แข็งแกร่งกว่าความปลอดภัยของด้าย ฟังก์ชันจะปลอดภัยหากสามารถใช้เธรดหลายเธรดพร้อมกันได้ ฟังก์ชันจะปลอดภัยต่อเธรดหาก:
โดยทั่วไปในSingle UNIX SpecificationและIEEE 1003.1 (เช่น "POSIX") ฟังก์ชันใด ๆ ที่ไม่รับประกันว่าจะ reentrant จะไม่รับประกันว่าจะปลอดภัยต่อเธรด ดังนั้นกล่าวอีกนัยหนึ่งเฉพาะฟังก์ชันที่รับประกันว่าสามารถ reentrant สามารถใช้แบบพกพาได้ในแอปพลิเคชันมัลติเธรด (โดยไม่ต้องล็อกภายนอก) อย่างไรก็ตามนั่นไม่ได้หมายความว่าการนำมาตรฐานเหล่านี้ไปใช้ไม่สามารถเลือกที่จะสร้างเธรดที่ปลอดภัยสำหรับฟังก์ชันที่ไม่สามารถย้อนกลับได้ ตัวอย่างเช่นลินุกซ์มักจะเพิ่มการซิงโครไนซ์ให้กับฟังก์ชันที่ไม่มีการป้อนกลับเพื่อเพิ่มการรับประกัน (นอกเหนือจากข้อกำหนด Single UNIX) ของเธรดความปลอดภัย
สตริง (และบัฟเฟอร์หน่วยความจำโดยทั่วไป)
คุณยังถามว่ามีข้อบกพร่องพื้นฐานบางอย่างกับสตริง / อาร์เรย์หรือไม่ บางคนอาจโต้แย้งว่าเป็นเช่นนั้น แต่ฉันขอยืนยันว่าไม่ไม่มีข้อบกพร่องพื้นฐานในภาษา C และ C ++ ต้องการให้คุณส่งผ่านความยาว / ความจุของอาร์เรย์แยกกัน (ไม่ใช่คุณสมบัติ ".length" เหมือนในภาษาอื่น ๆ ) นี่ไม่ใช่ข้อบกพร่อง แต่อย่างใด นักพัฒนา C และ C ++ ทุกคนสามารถเขียนโค้ดที่ถูกต้องได้ง่ายๆเพียงแค่ส่งความยาวเป็นพารามิเตอร์ตามต้องการ ปัญหาคือ API หลายตัวที่ต้องการข้อมูลนี้ไม่สามารถระบุเป็นพารามิเตอร์ได้ หรือสันนิษฐานว่าจะใช้ค่าคงที่ MAX_BUFFER_SIZE บางค่า ขณะนี้ API ดังกล่าวได้เลิกใช้แล้วและแทนที่ด้วย API ทางเลือกที่อนุญาตให้ระบุขนาดอาร์เรย์ / บัฟเฟอร์ / สตริงได้
Scanf (ในการตอบคำถามสุดท้ายของคุณ) โดย
ส่วนตัวแล้วฉันใช้ไลบรารี C ++ iostreams (std :: cin, std :: cout, ตัวดำเนินการ << และ >>, std :: getline, std :: istringstream, std :: ostringstream ฯลฯ ) ดังนั้นฉันมักจะไม่จัดการกับสิ่งนั้น ถ้าฉันถูกบังคับให้ใช้ C บริสุทธิ์โดยส่วนตัวฉันจะใช้fgetc ()หรือgetchar ()ร่วมกับstrtol () , strtoul ()ฯลฯ และแยกวิเคราะห์ด้วยตนเองเนื่องจากฉันไม่ใช่แฟนตัวยงของ varargs หรือสตริงรูปแบบ ที่กล่าวว่าจากความรู้ของฉันมากที่สุดไม่มีปัญหากับ[f] scanf () , [f] printf ()ฯลฯ ตราบเท่าที่คุณสร้างสตริงรูปแบบด้วยตัวเองคุณจะไม่ส่งผ่านสตริงรูปแบบที่กำหนดเองหรืออนุญาตให้ผู้ใช้ป้อนข้อมูลเป็นสตริงรูปแบบและคุณใช้มาโครการจัดรูปแบบที่กำหนดไว้ใน<inttypes.h>ตามความเหมาะสม (หมายเหตุควรใช้snprintf ()แทนsprintf ()แต่จะเกี่ยวข้องกับการไม่ระบุขนาดของบัฟเฟอร์ปลายทางและไม่ใช่การใช้สตริงรูปแบบ) ฉันควรชี้ให้เห็นว่าใน C ++ รูปแบบ boost ::ให้การจัดรูปแบบเหมือน printf โดยไม่มี varargs
strncpy
โดยทั่วไปควรหลีกเลี่ยงเช่นกัน มันไม่ได้ทำในสิ่งที่โปรแกรมเมอร์ส่วนใหญ่คิดว่ามันทำ ไม่รับประกันการยุติ (ซึ่งนำไปสู่การโอเวอร์รันบัฟเฟอร์) และจะทำให้สตริงสั้นลง (อาจทำให้ประสิทธิภาพลดลงในบางกรณี)
strncpy()
เลวร้ายไปกว่าstrncat()
นั้นคือการทดแทนที่สมเหตุสมผลสำหรับตัวแปรที่ไม่น้อยกว่า
เป็นอีกครั้งที่ผู้คนพูดซ้ำ ๆ กันเหมือนต้องมนต์การยืนยันอย่างไร้สาระว่าฟังก์ชัน str เวอร์ชัน "n" เป็นเวอร์ชันที่ปลอดภัย
หากนั่นคือสิ่งที่พวกเขาตั้งใจไว้พวกเขาก็จะยกเลิกสตริงเป็นโมฆะเสมอ
ฟังก์ชันเวอร์ชัน "n" ถูกเขียนขึ้นเพื่อใช้กับฟิลด์ที่มีความยาวคงที่ (เช่นรายการไดเร็กทอรีในระบบไฟล์ยุคแรก ๆ ) โดยที่ nul terminator จำเป็นต่อเมื่อสตริงไม่เต็มฟิลด์ นี่เป็นเหตุผลว่าทำไมฟังก์ชันจึงมีผลข้างเคียงแปลก ๆ ที่ไม่มีประสิทธิภาพหากใช้เพียงทดแทน - ใช้ strncpy () ตัวอย่างเช่น:
ถ้าอาร์เรย์ชี้ไปที่ s2 เป็นสตริงที่สั้นกว่า n ไบต์ไบต์ว่างจะถูกต่อท้ายกับสำเนาในอาร์เรย์ที่ชี้ด้วย s1 จนกว่าจะมีการเขียน n ไบต์ทั้งหมด
เนื่องจากบัฟเฟอร์ที่จัดสรรเพื่อจัดการกับชื่อไฟล์มักจะมีขนาด 4kbytes จึงทำให้ประสิทธิภาพการทำงานแย่ลงอย่างมาก
หากคุณต้องการเวอร์ชันที่ปลอดภัย "ตามสมควร" ให้ดาวน์โหลดหรือเขียน - กิจวัตร strl ของคุณเอง (strlcpy, strlcat ฯลฯ ) ซึ่งจะยุติสตริงและไม่มีผลข้างเคียง โปรดทราบว่าสิ่งเหล่านี้ไม่ปลอดภัยจริงๆเนื่องจากสามารถตัดสตริงได้อย่างเงียบ ๆ ซึ่งแทบจะไม่ได้เป็นแนวทางปฏิบัติที่ดีที่สุดในโปรแกรมใด ๆ ในโลกแห่งความเป็นจริง มีหลายครั้งที่สิ่งนี้ใช้ได้ แต่ก็มีหลายสถานการณ์เช่นกันที่อาจนำไปสู่ผลลัพธ์ที่ร้ายแรง (เช่นการพิมพ์ใบสั่งยาทางการแพทย์)
strncpy()
ไม่ได้ออกแบบมาเพื่อใช้กับฟิลด์ที่มีความยาวคงที่ - จริง ๆ แล้วมันถูกออกแบบมาเพื่อจำกัด จำนวนอักขระที่ต่อกัน มันค่อนข้างง่ายที่จะใช้สิ่งนี้เป็น "ปลอดภัย" โดยการติดตามพื้นที่ที่เหลืออยู่ในบัฟเฟอร์เมื่อทำการเรียงต่อกันหลาย ๆ ครั้งและยังง่ายกว่าที่จะใช้เป็น "ปลอดภัย" (โดยการตั้งค่าอักขระตัวแรกของบัฟเฟอร์ปลายทางเป็นก่อน เรียกมัน) จะยกเลิกสตริงปลายทางเสมอและจะไม่เขียนs พิเศษ strncat()
strncat()
strcat()
strcat()
strcpy()
'\0'
strncat()
'\0'
strncat()
ว่าไม่ได้ยุติปลายทางเสมอไปและถูกออกแบบมาเพื่อใช้กับฟิลด์ที่มีความยาวคงที่ซึ่งทั้งสองอย่างผิด
strncat()
จะทำงานอย่างถูกต้องโดยไม่คำนึงถึงความยาวของสตริงต้นทางในขณะที่strcat()
จะไม่ทำงาน ปัญหาของstrlcat()
ที่นี่คือมันไม่ใช่ฟังก์ชัน C มาตรฐาน
หลายคำตอบที่นี่ขอแนะนำให้ใช้strncat()
มากกว่าstrcat()
; ฉันขอแนะนำว่าควรหลีกเลี่ยงstrncat()
(และstrncpy()
) ด้วย มีปัญหาที่ทำให้ใช้งานได้ยากและนำไปสู่ข้อบกพร่อง:
strncat()
เกี่ยวข้องกับ (แต่ไม่ตรง - ดูจุดที่ 3) จำนวนอักขระสูงสุดที่สามารถคัดลอกไปยังปลายทางได้แทนที่จะเป็นขนาดของบัฟเฟอร์ปลายทาง ทำให้strncat()
ใช้งานได้ยากกว่าที่ควรจะเป็นโดยเฉพาะอย่างยิ่งถ้าหลายรายการจะเชื่อมต่อกับปลายทางs1
คือstrlen(s1)+n+1
" สำหรับการเรียกที่ดูเหมือนstrncat( s1, s2, n)
strncpy()
นอกจากนี้ยังมีปัญหาที่อาจทำให้เกิดข้อบกพร่องที่คุณพยายามใช้ด้วยวิธีที่เข้าใจง่าย - ไม่รับประกันว่าปลายทางจะถูกยกเลิก เพื่อให้แน่ใจว่าคุณต้องแน่ใจว่าคุณจัดการกรณีที่เข้ามุมโดยเฉพาะโดยการวาง'\0'
ในตำแหน่งสุดท้ายของบัฟเฟอร์ด้วยตัวคุณเอง (อย่างน้อยก็ในบางสถานการณ์)
ฉันขอแนะนำให้ใช้บางอย่างเช่น OpenBSD strlcat()
และstrlcpy()
(แม้ว่าฉันรู้ว่าบางคนไม่ชอบฟังก์ชั่นเหล่านั้นฉันเชื่อว่ามันใช้งานง่ายกว่าอย่างปลอดภัยมากกว่าstrncat()
/ strncpy()
)
นี่คือสิ่งที่ Todd Miller และ Theo de Raadt พูดถึงปัญหาstrncat()
และstrncpy()
:
มีหลายปัญหาที่พบเมื่อ
strncpy()
และstrncat()
จะถูกใช้เป็นรุ่นที่ปลอดภัยและstrcpy()
strcat()
ฟังก์ชันทั้งสองจัดการกับการสิ้นสุด NUL และพารามิเตอร์ความยาวในรูปแบบที่แตกต่างกันและไม่ใช้งานง่ายซึ่งสร้างความสับสนให้กับโปรแกรมเมอร์ที่มีประสบการณ์ นอกจากนี้ยังไม่มีวิธีง่ายๆในการตรวจจับเมื่อเกิดการตัดทอน ... จากปัญหาทั้งหมดนี้ความสับสนที่เกิดจากพารามิเตอร์ความยาวและปัญหาที่เกี่ยวข้องของการยกเลิก NUL นั้นสำคัญที่สุด เมื่อเราตรวจสอบแหล่งต้นไม้ OpenBSD สำหรับช่องโหว่ที่มีศักยภาพที่เราพบในทางที่ผิดอาละวาดและstrncpy()
strncat()
แม้ว่าสิ่งเหล่านี้จะไม่ส่งผลให้เกิดช่องโหว่ด้านความปลอดภัยที่ใช้ประโยชน์ได้ แต่ก็ทำให้ชัดเจนว่ากฎสำหรับการใช้งานstrncpy()
และstrncat()
การดำเนินการสตริงที่ปลอดภัยนั้นเข้าใจผิด
การตรวจสอบความปลอดภัยของ OpenBSD พบว่าจุดบกพร่องของฟังก์ชันเหล่านี้ "อาละวาด" ไม่เหมือนกับgets()
ฟังก์ชันเหล่านี้สามารถใช้งานได้อย่างปลอดภัย แต่ในทางปฏิบัติมีปัญหามากมายเนื่องจากอินเทอร์เฟซสับสนไม่เข้าใจง่ายและใช้งานได้ยาก ฉันรู้ว่า Microsoft ได้ทำการวิเคราะห์ด้วย (แม้ว่าฉันจะไม่รู้ว่าข้อมูลของพวกเขาอาจเผยแพร่ไปมากแค่ไหน) และด้วยเหตุนี้จึงมีการแบน (หรืออย่างน้อยก็ท้อใจอย่างยิ่ง - 'การแบน' อาจไม่สมบูรณ์) การใช้strncat()
และstrncpy()
(ท่ามกลางฟังก์ชันอื่น ๆ )
ลิงก์บางส่วนที่มีข้อมูลเพิ่มเติม:
memmove()
. (คุณสามารถใช้memcpy()
ในกรณีปกติเมื่อสตริงเป็นอิสระ)
strncat()
จะยกเลิกสตริงปลายทางเสมอ
strncat()
จุดที่สองของคุณคือผิด อย่างไรก็ตามมันถูกต้องสำหรับstrncpy()
ซึ่งมีปัญหาอื่น ๆ strncat()
เป็นแทนเหมาะสมstrcat()
แต่ไม่เปลี่ยนที่เหมาะสมสำหรับstrncpy()
strcpy()
strncat()
ไม่ได้ยุติลง ฉันสับสนกับพฤติกรรมของstrncat()
และstrncpy()
เล็กน้อย (อีกเหตุผลหนึ่งที่พวกเขาทำหน้าที่หลีกเลี่ยง - พวกเขามีชื่อที่บ่งบอกถึงพฤติกรรมที่คล้ายกัน แต่ในความเป็นจริงมีพฤติกรรมที่แตกต่างกันในรูปแบบที่สำคัญ ... ) ฉันได้แก้ไขคำตอบของฉันเพื่อแก้ไขปัญหานี้พร้อมทั้งเพิ่มข้อมูลเพิ่มเติม
char str[N] = ""; strncat(str, "long string", sizeof(str));
บัฟเฟอร์ล้นหาก N ไม่ใหญ่พอ strncat()
ฟังก์ชั่นเป็นเรื่องง่ายเกินไปที่จะในทางที่ผิด; ไม่ควรใช้ หากคุณสามารถใช้ได้strncat()
อย่างปลอดภัยคุณสามารถใช้memmove()
หรือใช้memcpy()
แทนได้ (และสิ่งเหล่านี้จะมีประสิทธิภาพมากกว่า)
setjmp.h
setjmp()
. ร่วมกับlongjmp()
ฟังก์ชันเหล่านี้ได้รับการยอมรับอย่างกว้างขวางว่าเป็นอันตรายต่อการใช้งานอย่างไม่น่าเชื่อซึ่งนำไปสู่การเขียนโปรแกรมแบบสปาเก็ตตี้มีพฤติกรรมที่ไม่ได้กำหนดหลายรูปแบบซึ่งอาจทำให้เกิดผลข้างเคียงที่ไม่ได้ตั้งใจในสภาพแวดล้อมของโปรแกรมเช่นส่งผลต่อค่าที่เก็บไว้ในสแต็ก อ้างอิง: MISRA-C 2012 กฎ 21.4 CERT C MSC22-Clongjmp()
. ดูsetjmp()
.stdio.h
gets()
. ฟังก์ชันถูกลบออกจากภาษา C (ตาม C11) เนื่องจากไม่ปลอดภัยตามการออกแบบ ฟังก์ชันนี้ถูกตั้งค่าสถานะว่าล้าสมัยใน C99 แล้ว ใช้fgets()
แทน ข้อมูลอ้างอิง: ISO 9899: 2011 K.3.5.4.1 ดูหมายเหตุ 404 ด้วยstdlib.h
atoi()
ตระกูลฟังก์ชั่น สิ่งเหล่านี้ไม่มีการจัดการข้อผิดพลาด แต่จะเรียกใช้พฤติกรรมที่ไม่ได้กำหนดเมื่อใดก็ตามที่เกิดข้อผิดพลาด ฟังก์ชันที่ไม่จำเป็นโดยสิ้นเชิงที่สามารถแทนที่ด้วยstrtol()
ตระกูลฟังก์ชัน ข้อมูลอ้างอิง: MISRA-C: 2012 rule 21.7สตริง h
strncat()
. มีอินเทอร์เฟซที่น่าอึดอัดซึ่งมักใช้ในทางที่ผิด ส่วนใหญ่เป็นฟังก์ชันที่ไม่จำเป็น strncpy()
ยังเห็นข้อสังเกตสำหรับstrncpy()
. ความตั้งใจของฟังก์ชันนี้ไม่เคยเป็นเวอร์ชันที่ปลอดภัยกว่าของstrcpy()
. จุดประสงค์เดียวคือจัดการรูปแบบสตริงโบราณบนระบบ Unix เสมอและการรวมอยู่ในไลบรารีมาตรฐานถือเป็นข้อผิดพลาดที่ทราบ ฟังก์ชั่นนี้เป็นอันตรายเนื่องจากอาจปล่อยให้สตริงโดยไม่มีการสิ้นสุดเป็นโมฆะและโปรแกรมเมอร์มักใช้อย่างไม่ถูกต้อง ข้อมูลอ้างอิง: เหตุใด strlcpy และ strlcat จึงถือว่าไม่ปลอดภัย .ยืนยัน h
assert()
. มาพร้อมกับค่าใช้จ่ายและโดยทั่วไปไม่ควรใช้ในรหัสการผลิต ควรใช้ตัวจัดการข้อผิดพลาดเฉพาะแอปพลิเคชันซึ่งแสดงข้อผิดพลาด แต่ไม่จำเป็นต้องปิดโปรแกรมทั้งหมดสัญญาณ h
signal()
. อ้างอิง: MISRA-C 2012 กฎ 21.5 CERT C SIG32-Cstdarg.h
va_arg()
ตระกูลฟังก์ชั่น การมีฟังก์ชั่นความยาวผันแปรในโปรแกรม C มักจะบ่งบอกถึงการออกแบบโปรแกรมที่ไม่ดี ควรหลีกเลี่ยงเว้นแต่คุณจะมีข้อกำหนดที่เฉพาะเจาะจงมากstdio.h
โดยทั่วไปแล้วไม่แนะนำให้ใช้ไลบรารีทั้งหมดนี้สำหรับโค้ดการผลิตเนื่องจากมีหลายกรณีของพฤติกรรมที่กำหนดไม่ดีและประเภทความปลอดภัยที่ไม่ดี
fflush()
. เหมาะอย่างยิ่งสำหรับใช้สำหรับสตรีมเอาต์พุต เรียกใช้พฤติกรรมที่ไม่ได้กำหนดหากใช้สำหรับอินพุตสตรีมgets_s()
. เวอร์ชันปลอดภัยที่gets()
รวมอยู่ในอินเทอร์เฟซการตรวจสอบขอบเขต C11 ขอแนะนำให้ใช้fgets()
แทนตามคำแนะนำมาตรฐาน C เอกสารอ้างอิง: ISO 9899: 2011 K.3.5.4.1printf()
ตระกูลฟังก์ชั่น ทรัพยากรฟังก์ชั่นหนักที่มาพร้อมกับพฤติกรรมที่ไม่ได้กำหนดจำนวนมากและความปลอดภัยประเภทที่ไม่ดี sprintf()
ยังมีช่องโหว่ ควรหลีกเลี่ยงฟังก์ชันเหล่านี้ในรหัสการผลิต เอกสารอ้างอิง: MISRA-C: 2012 rule 21.6.scanf()
ตระกูลฟังก์ชั่น ดูข้อสังเกตเกี่ยวกับprintf()
. นอกจากนี้ - scanf()
มีความเสี่ยงที่จะบัฟเฟอร์มากเกินไปหากใช้ไม่ถูกต้อง fgets()
ควรใช้เมื่อเป็นไปได้ เอกสารอ้างอิง: CERT C INT05-C , MISRA-C: 2012 rule 21.6tmpfile()
ตระกูลฟังก์ชั่น มาพร้อมกับปัญหาช่องโหว่ต่างๆ ข้อมูลอ้างอิง: CERT C FIO21-C .stdlib.h
malloc()
ตระกูลฟังก์ชั่น ดีอย่างสมบูรณ์แบบที่จะใช้ในระบบเจ้าภาพแม้ว่าจะตระหนักถึงปัญหาที่รู้จักกันดีใน C90 และดังนั้นจึงไม่ได้ทิ้งผล malloc()
ครอบครัวของฟังก์ชั่นไม่ควรที่จะใช้ในงานอิสระ เอกสารอ้างอิง: MISRA-C: 2012 rule 21.3.
นอกจากนี้โปรดทราบว่าrealloc()
เป็นอันตรายในกรณีที่คุณเขียนทับตัวชี้เก่าด้วยผลลัพธ์ของrealloc()
. ในกรณีที่ฟังก์ชันล้มเหลวคุณจะสร้างการรั่วไหล
system()
. มาพร้อมกับค่าใช้จ่ายจำนวนมากและแม้ว่าจะพกพาได้ แต่ก็มักจะใช้ฟังก์ชัน API เฉพาะระบบแทนได้ดีกว่า มาพร้อมกับพฤติกรรมที่กำหนดไว้ไม่ดีต่างๆ อ้างอิง: CERT C ENV33-C
สตริง h
strcat()
. strcpy()
ดูหมายเหตุสำหรับstrcpy()
. ใช้งานได้อย่างสมบูรณ์แบบเว้นแต่ว่าขนาดของข้อมูลที่จะคัดลอกนั้นไม่ทราบขนาดหรือใหญ่กว่าบัฟเฟอร์ปลายทาง หากไม่มีการตรวจสอบขนาดข้อมูลขาเข้าอาจมีการโอเวอร์รันบัฟเฟอร์ ซึ่งเป็นความผิดของไม่มีstrcpy()
ตัวเอง แต่ของแอพลิเคชันโทร - ที่strcpy()
ไม่ปลอดภัยส่วนใหญ่จะเป็นตำนานที่สร้างขึ้นโดยไมโครซอฟท์strtok()
. แก้ไขสตริงผู้โทรและใช้ตัวแปรสถานะภายในซึ่งอาจทำให้ไม่ปลอดภัยในสภาพแวดล้อมแบบมัลติเธรดstatic_assert()
ในassert()
กรณีที่สามารถแก้ไขเงื่อนไขได้ในเวลาคอมไพล์ นอกจากนี้ยังsprintf()
สามารถเปลี่ยนได้เกือบตลอดเวลาsnprintf()
ซึ่งปลอดภัยกว่าเล็กน้อย
strtok()
ในฟังก์ชัน A หมายความว่า (a) ฟังก์ชันไม่สามารถเรียกฟังก์ชันอื่นใดที่ใช้strtok()
ในขณะที่ A ใช้งานได้เช่นกันและ (b) หมายความว่าไม่มีฟังก์ชันใดที่เรียกใช้ A strtok()
เมื่อเรียกใช้ A กล่าวอีกนัยหนึ่งคือการใช้strtok()
เป็นพิษต่อสายโซ่; ไม่สามารถใช้อย่างปลอดภัยในรหัสไลบรารีได้เนื่องจากต้องจัดทำเอกสารที่ใช้strtok()
เพื่อป้องกันไม่ให้ผู้ใช้รายอื่นstrtok()
เรียกรหัสไลบรารี
strncpy
นี้เป็นฟังก์ชันที่เหมาะสมที่จะใช้เมื่อเขียนสตริงบัฟเฟอร์ที่มีเบาะเป็นศูนย์พร้อมข้อมูลที่นำมาจากสตริงที่มีการสิ้นสุดศูนย์หรือบัฟเฟอร์ที่มีเบาะเป็นศูนย์ซึ่งมีขนาดอย่างน้อยที่สุดเท่าที่ปลายทาง บัฟเฟอร์ที่ไม่มีเบาะเป็นศูนย์ไม่ใช่เรื่องธรรมดามากนัก แต่ก็ไม่ได้คลุมเครือเช่นกัน
บางคนจะอ้างว่าstrcpy
และstrcat
ควรหลีกเลี่ยงในความโปรดปรานของและstrncpy
strncat
นี่เป็นเรื่องส่วนตัวในความคิดของฉัน
ควรหลีกเลี่ยงอย่างแน่นอนเมื่อจัดการกับอินพุตของผู้ใช้ - ไม่ต้องสงสัยเลยที่นี่
ในรหัส "ห่างไกล" จากผู้ใช้เมื่อคุณเพิ่งรู้ว่าบัฟเฟอร์ยาวเพียงพอstrcpy
และstrcat
อาจมีประสิทธิภาพมากกว่าเล็กน้อยเนื่องจากการคำนวณn
เพื่อส่งต่อไปยังลูกพี่ลูกน้องของพวกเขาอาจไม่จำเป็น
strlcat
และstrlcpy
เนื่องจากเวอร์ชัน 'n' ไม่รับประกันการยุติสตริงปลายทางที่เป็น NULL
strncpy()
จะเขียนเป็นn
ไบต์โดยใช้อักขระ nul หากจำเป็น ในฐานะ Dan การใช้เวอร์ชันปลอดภัยเป็นตัวเลือกที่ดีที่สุด IMO
strncat()
อาจเป็นเรื่องยากที่จะใช้อย่างถูกต้องและปลอดภัย (และควรหลีกเลี่ยง) อยู่ในหัวข้อ
หลีกเลี่ยง
strtok
สำหรับโปรแกรมมัลติเธรดเนื่องจากไม่ปลอดภัยต่อเธรดgets
เนื่องจากอาจทำให้เกิดบัฟเฟอร์ล้นstrtok()
ไปเล็ก ๆ น้อย ๆ ที่อยู่นอกเหนือความปลอดภัยหัวข้อ - มันไม่ปลอดภัยแม้ในโปรแกรมเดียวเกลียวจนกว่าคุณจะรู้ได้อย่างชัดเจนว่าฟังก์ชั่นที่ไม่มีรหัสของคุณอาจจะเรียกในขณะที่ใช้strtok()
ไม่ได้ยังใช้ (หรือพวกเขาจะเลอะstrtok()
ของรัฐที่เหมาะสม จากใต้คุณ) ในความเป็นจริงคอมไพเลอร์ส่วนใหญ่ที่กำหนดเป้าหมายแพลตฟอร์มแบบมัลติเธรดจะดูแลstrtok()
ปัญหาที่อาจเกิดขึ้นได้เท่าที่เธรดดำเนินการโดยใช้ที่จัดเก็บเธรดโลคัลสำหรับstrtok()
ข้อมูลคงที่ของเธรด แต่นั่นก็ยังไม่สามารถแก้ไขปัญหาของฟังก์ชันอื่น ๆ ที่ใช้งานได้ในขณะที่คุณใช้งานอยู่ (ในเธรดเดียวกัน)
strtok()
คือมันเก็บบัฟเฟอร์เพียงตัวเดียวที่จะทำงานได้ดังนั้นการแทนที่ที่ดีส่วนใหญ่จำเป็นต้องมีการเก็บค่าบัฟเฟอร์ไว้รอบ ๆ และส่งผ่านไป
strcspn
ทำสิ่งที่คุณต้องการเกือบทั้งหมด - ค้นหาตัวคั่นโทเค็นถัดไป คุณสามารถนำรูปแบบที่มีเหตุผลมาstrtok
ใช้ใหม่ได้
อาจคุ้มค่าที่จะเพิ่มอีกครั้งที่strncpy()
ไม่ใช่การแทนที่ด้วยวัตถุประสงค์ทั่วไปstrcpy()
ที่ชื่ออาจแนะนำ ได้รับการออกแบบมาสำหรับฟิลด์ที่มีความยาวคงที่ซึ่งไม่จำเป็นต้องมี nul-terminator (เดิมถูกออกแบบมาเพื่อใช้กับรายการไดเรกทอรี UNIX แต่อาจมีประโยชน์สำหรับสิ่งต่างๆเช่นช่องคีย์การเข้ารหัส)
อย่างไรก็ตามเป็นเรื่องง่ายที่จะใช้strncat()
แทนstrcpy()
:
if (dest_size > 0)
{
dest[0] = '\0';
strncat(dest, source, dest_size - 1);
}
( if
เห็นได้ชัดว่าการทดสอบสามารถลดลงได้ในกรณีทั่วไปซึ่งคุณรู้ว่าdest_size
ไม่ใช่ศูนย์แน่นอน)
นอกจากนี้ยังตรวจสอบรายชื่อ Microsoft ของAPI ที่ต้องห้าม API เหล่านี้ (รวมถึงหลายรายการที่แสดงไว้ที่นี่แล้ว) ที่ถูกแบนจากรหัสของ Microsoft เนื่องจากมักใช้ผิดประเภทและนำไปสู่ปัญหาด้านความปลอดภัย
คุณอาจไม่เห็นด้วยกับพวกเขาทั้งหมด แต่ก็คุ้มค่าที่จะพิจารณา พวกเขาเพิ่ม API ลงในรายการเมื่อการใช้งานผิดวัตถุประสงค์ทำให้เกิดข้อบกพร่องด้านความปลอดภัยจำนวนมาก
เกือบทุกฟังก์ชันที่เกี่ยวข้องกับสตริงที่ยกเลิก NUL อาจไม่ปลอดภัย หากคุณกำลังรับข้อมูลจากโลกภายนอกและจัดการข้อมูลผ่านทางฟังก์ชัน str * () แสดงว่าคุณเตรียมพร้อมสำหรับภัยพิบัติ
อย่าลืมเกี่ยวกับ sprintf - มันเป็นสาเหตุของปัญหามากมาย นี่เป็นความจริงเนื่องจากทางเลือกอื่น snprintf มีการใช้งานที่แตกต่างกันในบางครั้งซึ่งอาจทำให้คุณไม่สามารถรายงานรหัสได้
ลินุกซ์: http://linux.die.net/man/3/snprintf
windows: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx
ในกรณีที่ 1 (linux) ค่าส่งคืนคือจำนวนข้อมูลที่จำเป็นในการจัดเก็บบัฟเฟอร์ทั้งหมด (หากมีขนาดเล็กกว่าขนาดของบัฟเฟอร์ที่กำหนดผลลัพธ์จะถูกตัดทอน)
ในกรณีที่ 2 (windows) ค่าที่ส่งคืนเป็นจำนวนลบในกรณีที่เอาต์พุตถูกตัดทอน
โดยทั่วไปคุณควรหลีกเลี่ยงฟังก์ชันที่ไม่ใช่:
บัฟเฟอร์ล้นปลอดภัย (มีการกล่าวถึงฟังก์ชั่นมากมายที่นี่)
เธรดปลอดภัย / ไม่ reentrant (เช่น strtok)
ในคู่มือของแต่ละฟังก์ชันคุณควรค้นหาคีย์เวิร์ดเช่น: safe, sync, async, thread, buffer, bugs
_sprintf()
ถูกสร้างขึ้นโดย Microsoft ก่อนที่มาตรฐานจะsnprintf()
มาถึง IIRC ´StringCbPrintf () ´ค่อนข้างคล้ายกับsnprintf()
แม้ว่า
sprintf
อย่างใดอย่างปลอดภัยในบางกรณี: sprintf(buffer,"%10s",input);
จำกัด nb คัดลอกไบต์ถึง 10 (ถ้าbuffer
เป็นchar buffer[11]
มันปลอดภัยแม้ว่าข้อมูลอาจลมขึ้นตัดทอน.
มันยากมากที่จะใช้scanf
อย่างปลอดภัย การใช้งานที่ดีscanf
สามารถหลีกเลี่ยงการเกิดบัฟเฟอร์ล้น แต่คุณยังคงเสี่ยงต่อพฤติกรรมที่ไม่ได้กำหนดเมื่ออ่านตัวเลขที่ไม่ตรงกับประเภทที่ร้องขอ ในกรณีส่วนใหญ่fgets
ตามด้วยตนเองแยก (ใช้sscanf
, strchr
ฯลฯ ) เป็นตัวเลือกที่ดีกว่า
แต่ฉันจะไม่พูดว่า "หลีกเลี่ยงscanf
ตลอดเวลา" scanf
มีการใช้งาน ตัวอย่างเช่นสมมติว่าคุณต้องการอ่านอินพุตของผู้ใช้ในchar
อาร์เรย์ที่มีความยาว 10 ไบต์ คุณต้องการลบบรรทัดต่อท้ายถ้ามี หากผู้ใช้ป้อนอักขระมากกว่า 9 ตัวก่อนขึ้นบรรทัดใหม่คุณต้องการจัดเก็บอักขระ 9 ตัวแรกในบัฟเฟอร์และทิ้งทุกอย่างจนกว่าจะขึ้นบรรทัดใหม่ถัดไป คุณทำได้:
char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();
เมื่อคุณคุ้นเคยกับสำนวนนี้แล้วมันจะสั้นกว่าและในบางวิธีก็สะอาดกว่า:
char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
char *nl;
if ((nl = strrchr(buf, '\n')) == NULL) {
int c;
while ((c = getchar()) != EOF && c != '\n') {
;
}
} else {
*nl = 0;
}
}
ในสถานการณ์จำลองสตริงคัดลอก / ย้ายทั้งหมด - strcat (), strncat (), strcpy (), strncpy () ฯลฯ - สิ่งต่าง ๆ จะดีขึ้นมาก ( ปลอดภัยกว่า ) หากมีการบังคับใช้ฮิวริสติกสองสามแบบ:
1. เติม NUL เสมอ บัฟเฟอร์ของคุณก่อนเพิ่มข้อมูล
2. ประกาศบัฟเฟอร์อักขระเป็น [SIZE + 1] โดยมีค่าคงที่มาโคร
ตัวอย่างเช่นกำหนด:
#define BUFSIZE 10
char Buffer[BUFSIZE+1] = { 0x00 }; /* The compiler NUL-fills the rest */
เราสามารถใช้รหัสเช่น:
memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");
ค่อนข้างปลอดภัย memset () ควรปรากฏก่อน strncpy () แม้ว่าเราจะเริ่มต้นบัฟเฟอร์ในเวลาคอมไพล์เนื่องจากเราไม่ทราบว่ามีการใส่โค้ดอื่นใดลงไปก่อนที่ฟังก์ชันของเราจะถูกเรียกใช้ strncpy () จะตัดทอนข้อมูลที่คัดลอกเป็น "1234567890" และจะไม่ยุติ NUL อย่างไรก็ตามเนื่องจากเราได้เติม NUL เต็มบัฟเฟอร์ทั้งหมดแล้ว - sizeof (Buffer) แทนที่จะเป็น BUFSIZE จึงรับประกันได้ว่าจะมีการยกเลิก NUL แบบ "นอกขอบเขต" ขั้นสุดท้ายตราบใดที่เรา จำกัด การเขียนของเราโดยใช้ BUFSIZE ค่าคงที่แทนที่จะเป็น sizeof (บัฟเฟอร์)
บัฟเฟอร์และ BUFSIZE ทำงานได้ดีสำหรับ snprintf ():
memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
/* Do some error-handling */
} /* If using MFC, you need if(... < 0), instead */
แม้ว่า snprintf () จะเขียนเฉพาะอักขระ BUFIZE-1 เท่านั้นตามด้วย NUL แต่ก็ทำงานได้อย่างปลอดภัย ดังนั้นเราจึง "เสีย" NUL ไบต์ที่ไม่เกี่ยวข้องในตอนท้ายของบัฟเฟอร์ ... เราป้องกันทั้งบัฟเฟอร์ล้นและเงื่อนไขสตริงที่ไม่สิ้นสุดสำหรับหน่วยความจำต้นทุนเล็กน้อย
การโทรของฉันบน strcat () และ strncat () นั้นยากกว่า: อย่าใช้มัน เป็นการยากที่จะใช้ strcat () อย่างปลอดภัยและ API สำหรับ strncat () นั้นใช้งานง่ายมากจนความพยายามที่จำเป็นในการใช้งานอย่างถูกต้องจะลบล้างผลประโยชน์ใด ๆ ฉันเสนอดรอปอินต่อไปนี้:
#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)
การสร้างดร็อปอิน strcat () เป็นเรื่องที่น่าดึงดูด แต่ไม่ใช่ความคิดที่ดี:
#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)
เนื่องจากเป้าหมายอาจเป็นตัวชี้ (ดังนั้น sizeof () จึงไม่ส่งคืนข้อมูลที่เราต้องการ) ฉันไม่มีโซลูชัน "สากล" ที่ดีสำหรับอินสแตนซ์ของ strcat () ในโค้ดของคุณ
ปัญหาที่ฉันพบบ่อยจากโปรแกรมเมอร์ "strFunc () - awareness คือความพยายามในการป้องกันบัฟเฟอร์ล้นโดยใช้ strlen () นี่เป็นเรื่องปกติหากรับประกันว่าเนื้อหาจะถูกยกเลิก NUL มิฉะนั้น strlen () เองอาจทำให้เกิดข้อผิดพลาดบัฟเฟอร์โอเวอร์รันได้ (โดยปกติจะนำไปสู่การละเมิดการแบ่งกลุ่มหรือสถานการณ์การถ่ายโอนข้อมูลหลักอื่น ๆ ) ก่อนที่คุณจะไปถึงโค้ด "ที่มีปัญหา" ที่คุณพยายามป้องกัน
atoi ไม่ด้ายปลอดภัย ฉันใช้ strtol แทนตามคำแนะนำจาก man page
strtol()
จะปลอดภัยต่อเธรดและatoi()
จะไม่เป็นเช่นนั้น
man atoi
(ควรมี)