เมื่อใดควรตรวจสอบพอยน์เตอร์สำหรับ NULL ใน C


18

สรุป :

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

รายละเอียด :

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

ตัวอย่างเช่นต่อไปนี้ปรากฏในซอร์สโค้ดของ git:

static unsigned short graph_get_current_column_color(const struct git_graph *graph)
{
    if (!want_color(graph->revs->diffopt.use_color))
        return column_colors_max;
    return graph->default_column_color;
}

หาก*graphเป็นNULLตัวชี้ Null จะถูกยกเลิกการพิจารณาอาจทำให้โปรแกรมขัดข้อง แต่อาจส่งผลให้เกิดพฤติกรรมที่คาดเดาไม่ได้อื่น ๆ ในทางกลับกันฟังก์ชั่นคือstaticดังนั้นโปรแกรมเมอร์อาจตรวจสอบอินพุตแล้ว ฉันไม่รู้ฉันเพิ่งเลือกแบบสุ่มเพราะมันเป็นตัวอย่างสั้น ๆ ในแอปพลิเคชันโปรแกรมที่เขียนใน C. ฉันเห็นสถานที่อื่น ๆ อีกหลายแห่งที่มีการใช้พอยน์เตอร์โดยไม่ตรวจสอบค่า NULL คำถามของฉันทั่วไปไม่เฉพาะเจาะจงกับส่วนรหัสนี้

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

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

ฉันสนใจคำถามนี้โดยทั่วไปสำหรับการเขียนรหัสการผลิต แต่ฉันก็สนใจในบริบทของการสัมภาษณ์รายการ ตัวอย่างเช่นตำราอัลกอริทึมจำนวนมาก (เช่น CLR) มีแนวโน้มที่จะนำเสนออัลกอริทึมในนามแฝงโดยไม่มีการตรวจสอบข้อผิดพลาดใด ๆ อย่างไรก็ตามในขณะนี้เป็นสิ่งที่ดีสำหรับการทำความเข้าใจหลักของอัลกอริทึมก็เห็นได้ชัดว่าไม่ใช่วิธีการเขียนโปรแกรมที่ดี ดังนั้นฉันไม่ต้องการบอกผู้สัมภาษณ์ว่าฉันข้ามการตรวจสอบข้อผิดพลาดเพื่อทำให้ตัวอย่างโค้ดของฉันง่ายขึ้น (อาจเป็นตำราเรียน) แต่ฉันก็ไม่ต้องการที่จะสร้างรหัสที่ไม่มีประสิทธิภาพด้วยการตรวจสอบข้อผิดพลาดมากเกินไป ตัวอย่างเช่นgraph_get_current_column_colorอาจมีการแก้ไขเพื่อตรวจสอบ*graphnull แต่ไม่ชัดเจนว่าจะทำอย่างไรถ้า*graphเป็นโมฆะอื่น ๆ นอกเหนือจากที่ไม่ควรตรวจสอบมัน


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

1
ดูเพิ่มเติมที่: stackoverflow.com/questions/4390007/…
Billy ONeal

ด้วยการเข้าใจถึงปัญหาย้อนหลังของปี 2560 ทำให้ระลึกถึงคำถามและคำตอบส่วนใหญ่ที่เขียนขึ้นในปี 2556 คำตอบใด ๆ ที่แก้ไขปัญหาพฤติกรรมที่ไม่ได้กำหนดเวลาเดินทางเนื่องจากการปรับแต่งคอมไพเลอร์หรือไม่?

ในกรณีของการเรียก API คาดว่าอาร์กิวเมนต์ตัวชี้ที่ถูกต้องฉันสงสัยว่าค่าของการทดสอบสำหรับ NULL เท่านั้นคืออะไร? ตัวชี้ที่ไม่ถูกต้องใด ๆ ที่ได้รับการลงทะเบียนจะไม่ดีเท่ากับ NULL และ segfault เหมือนกันทั้งหมด
PaulHK

คำตอบ:


15

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

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

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

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


คำถามค่อนข้างเป็นอัตนัย แต่ตอนนี้ดูเหมือนจะเป็นคำตอบที่ดีที่สุด ขอบคุณทุกคนที่ให้ความคิดกับคำถามนี้
Gabriel Southern

1
ใน iOS malloc จะไม่ส่งคืนค่า NULL หากไม่พบหน่วยความจำก็จะขอให้แอปพลิเคชันของคุณปล่อยหน่วยความจำก่อนจากนั้นระบบจะถามระบบปฏิบัติการ (ซึ่งจะขอให้แอปอื่น ๆ ปล่อยหน่วยความจำและอาจฆ่าพวกเขา) และหากยังไม่มีหน่วยความจำ . ไม่จำเป็นต้องตรวจสอบ
gnasher729

11

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

พวกเขารายงานว่ารู้สึกถ่อมตนอย่างรวดเร็วด้วยจำนวนครั้งที่พวกเขาเห็น "ไม่สามารถเกิดขึ้นได้" ออกมาที่เครื่อง

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

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

ถ้าคุณอยู่ในฟังก์ชันและตัวชี้ควรจะไม่ใช่ NULL ที่เข้ามาคุณควรตรวจสอบ

ถ้าคุณได้รับมันจากฟังก์ชั่นและมันควรจะเป็นค่า NULL ที่ออกมาคุณควรตรวจสอบมัน malloc () มีชื่อเสียงในเรื่องนี้โดยเฉพาะ (Nortel Networks ซึ่งหมดอายุแล้วมีมาตรฐานการเข้ารหัสที่เขียนได้ยากและรวดเร็วเกี่ยวกับเรื่องนี้ฉันต้องแก้ไขข้อผิดพลาดที่จุดหนึ่งที่ฉันสืบย้อนกลับไปที่ malloc () ส่งคืนตัวชี้ NULL และ coder idiot ไม่รบกวนการตรวจสอบ ก่อนที่เขาจะเขียนถึงมันเพราะเขาเพิ่งรู้ว่าเขามีความทรงจำมากมาย ... ฉันพูดสิ่งที่น่ารังเกียจมากเมื่อในที่สุดฉันก็พบมัน)


8
หากคุณอยู่ในฟังก์ชั่นที่ต้องใช้ตัวชี้ที่ไม่ใช่ค่า NULL แต่คุณตรวจสอบแล้วและมันเป็นค่า NULL ... จะทำอะไรต่อไป
Detly

1
@detly หยุดสิ่งที่คุณทำและกลับรหัสข้อผิดพลาดหรือการเดินทางยืนยัน
เจมส์

1
@ James - ไม่คิดassertอย่างแน่นอน ฉันไม่ชอบแนวคิดรหัสข้อผิดพลาดหากคุณกำลังพูดถึงการเปลี่ยนรหัสที่มีอยู่เพื่อรวมการNULLตรวจสอบ
detly

10
@detly คุณจะไม่ได้ไกลมากเป็น dev C ถ้าคุณทำไม่ได้เช่นรหัสข้อผิดพลาด
เจมส์

5
@ JohnR.Strohm - นี่คือ C มันเป็นการยืนยันหรือไม่มีอะไร: P
detly

5

คุณสามารถข้ามการตรวจสอบเมื่อคุณสามารถโน้มน้าวตัวเองอย่างใดที่ตัวชี้ไม่สามารถเป็นโมฆะ

โดยปกติแล้วการตรวจสอบตัวชี้โมฆะจะดำเนินการในรหัสที่คาดว่าจะเป็นโมฆะปรากฏเป็นตัวบ่งชี้ว่าวัตถุไม่สามารถใช้ได้ในปัจจุบัน Null ใช้เป็นค่า Sentinel เช่นเพื่อยุติรายการที่ลิงก์หรือแม้กระทั่งอาร์เรย์ของพอยน์เตอร์ argvเวกเตอร์ของสายผ่านเข้ามาmainจะต้องเป็นโมฆะสิ้นสุดโดยตัวชี้คล้าย ๆ กับวิธีสตริงถูกยกเลิกโดยอักขระ null A: argv[argc]เป็นตัวชี้โมฆะและคุณสามารถพึ่งพานี้เมื่อแยกบรรทัดคำสั่ง

while (*argv) {
   /* process argument string *argv */
   argv++; /* increment to next one */
}

ดังนั้นสถานการณ์สำหรับการตรวจสอบค่า null จึงเป็นสถานการณ์ที่ a เป็นค่าที่คาดไว้ การตรวจสอบค่า Null ใช้ความหมายของตัวชี้ Null เช่นหยุดการค้นหารายการที่เชื่อมโยง พวกเขาป้องกันรหัสจาก dereferencing ตัวชี้

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

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

เราอาจตั้งตัวชี้เป็นโมฆะแม้ว่าเราจะไม่ปล่อยวัตถุเพียงเพื่อแยกวัตถุหนึ่งจากวัตถุอื่น:

tty_driver->tty = NULL; /* detach low level driver from the tty device */

ข้อโต้แย้งที่น่าเชื่อที่สุดที่ฉันรู้ว่าตัวชี้ไม่สามารถเป็นโมฆะในบางจุดคือการตัดจุดนั้นใน "if (ptr! = NULL) {" และ "} ที่สอดคล้องกัน" นอกเหนือจากนั้นคุณอยู่ในอาณาเขตการยืนยันที่เป็นทางการ
John R. Strohm

4

ให้ฉันเพิ่มอีกหนึ่งเสียงลงในความทรงจำ

เช่นเดียวกับคำตอบอื่น ๆ อีกมากมายฉันว่า - อย่ากังวลกับการตรวจสอบที่จุดนี้; มันเป็นความรับผิดชอบของผู้โทร แต่ฉันมีพื้นฐานที่จะพัฒนาต่อไปแทนที่จะเป็นเรื่องง่าย ๆ (และความเย่อหยิ่งการเขียนโปรแกรม C)

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

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

นั่นคือสองเซ็นต์ของฉัน


1

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

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

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


1

ฉันจะบอกว่ามันขึ้นอยู่กับต่อไปนี้:

  1. การใช้งาน CPU สำคัญหรือไม่ การตรวจสอบค่า NULL ทุกครั้งต้องใช้เวลาพอสมควร
  2. อัตราเดิมพันใดที่ตัวชี้เป็น NULL มันเพิ่งใช้ในฟังก์ชั่นก่อนหน้า อาจมีการเปลี่ยนแปลงค่าของตัวชี้
  3. ระบบยึดเอาเสียก่อนหรือไม่? ความหมายของการเปลี่ยนแปลงงานเกิดขึ้นและเปลี่ยนค่าหรือไม่ ISR เข้ามาและเปลี่ยนค่าได้ไหม?
  4. รหัสคู่นั้นแน่นขนาดไหน?
  5. มีกลไกอัตโนมัติบางประเภทที่จะตรวจหาพอยน์เตอร์ NULL โดยอัตโนมัติหรือไม่?

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

ระบบการยึดเอาเสียก่อน หากรหัสของคุณกำลังทำงานอยู่และงานอื่นอาจขัดจังหวะและอาจเปลี่ยนแปลงค่าที่จะมีการตรวจสอบ

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

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

เพื่อทดสอบว่ามีให้สร้างโปรแกรมด้วยตัวชี้หรือไม่ ตั้งค่าตัวชี้เป็น 0 จากนั้นลองอ่าน / เขียน


ฉันไม่รู้ว่าฉันจะจำแนก segfault ว่าเป็นการตรวจสอบ NULL อัตโนมัติหรือไม่ ฉันยอมรับว่าการมีการป้องกันหน่วยความจำของ CPU ช่วยให้กระบวนการหนึ่งไม่สามารถทำความเสียหายต่อส่วนที่เหลือของระบบได้ แต่ฉันจะไม่เรียกมันว่าการป้องกันโดยอัตโนมัติ
Gabriel Southern

1

ในความเห็นของฉันการตรวจสอบอินพุต (pre / post-conditions, ie) เป็นสิ่งที่ดีในการตรวจสอบข้อผิดพลาดในการเขียนโปรแกรม แต่ถ้ามันส่งผลให้เกิดข้อผิดพลาดดังและน่าสะพรึงกลัว assertมักจะมีผลกระทบนั้น

สิ่งที่ขาดไปนี้อาจกลายเป็นฝันร้ายโดยไม่มีทีมที่ประสานงานอย่างระมัดระวัง และแน่นอนว่าทุกทีมมีการประสานงานอย่างระมัดระวังและเป็นหนึ่งเดียวกันภายใต้มาตรฐานที่แน่นหนา

เช่นเดียวกับตัวอย่างฉันทำงานกับเพื่อนร่วมงานบางคนที่เชื่อว่าควรตรวจสอบพอยน์เตอร์พอยน์เตอร์ทางศาสนาอย่างเคร่งครัดดังนั้นพวกเขาจึงโรยโค้ดจำนวนมากเช่นนี้:

void vertex_move(Vertex* v)
{
     if (!v)
          return;
     ...
}

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

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

แต่ปัญหาที่แท้จริงที่นี่คือการไม่สามารถตรวจพบปัญหานี้ได้อย่างง่ายดาย ดังนั้นฉันเคยพยายามที่จะดูว่าจะเกิดอะไรขึ้นถ้าฉันเปลี่ยนรหัสแอนะล็อกด้านบนเป็นassertเช่น:

void vertex_move(Vertex* v)
{
     assert(v && "Vertex should never be null!");
     ...
}

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

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

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


0

วิธีปฏิบัติอย่างหนึ่งคือทำการตรวจสอบ null เสมอเว้นแต่ว่าคุณได้ตรวจสอบแล้ว ดังนั้นหากอินพุตถูกส่งผ่านจากฟังก์ชัน A () ไปยัง B () และ A () ได้ตรวจสอบตัวชี้แล้วและคุณมั่นใจว่า B () ไม่ได้ถูกเรียกที่อื่นดังนั้น B () จึงสามารถเชื่อถือ A () ได้ ฆ่าเชื้อข้อมูล


1
... จนกระทั่งในอีก 6 เดือนครั้งที่มีคนมาพร้อมและเพิ่มบางรหัสอื่น ๆ ที่เรียก B () (อาจจะสมมติว่าใครก็ตามที่เขียน B () แน่นอนการตรวจสอบความถูกต้อง NULLs) ถ้างั้นคุณก็จะเมาใช่มั้ย กฎพื้นฐาน - หากมีเงื่อนไขที่ไม่ถูกต้องสำหรับอินพุตไปยังฟังก์ชันให้ตรวจสอบเพราะอินพุตอยู่นอกการควบคุมของฟังก์ชัน
Maximus Minimus

@ mh01 หากคุณเพิ่งจะสุ่มโค้ด (เช่นการสร้างสมมติฐานและไม่อ่านเอกสาร) จากนั้นฉันไม่คิดว่าการNULLตรวจสอบพิเศษจะต้องทำอะไรมากมาย ลองคิดดู: ตอนนี้B()ตรวจสอบNULLแล้ว ... ทำอะไร กลับมา-1? หากผู้โทรไม่ตรวจสอบNULLคุณจะมั่นใจได้อย่างไรว่าพวกเขาจะจัดการกับ-1กรณีค่าตอบแทนหรือไม่
detly

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