การตรวจสอบ“ โมฆะ” ใน C หรือ C ++ หมายถึงอะไร


21

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

  • อะไรที่เป็นโมฆะ?
  • "ตรวจสอบค่าว่าง" หมายความว่าอย่างไร
  • ฉันจำเป็นต้องตรวจสอบค่าว่างเสมอหรือไม่?

ตัวอย่างโค้ดใด ๆ จะได้รับการชื่นชมมาก



ฉันอยากจะแนะนำให้รับแบบฝึกหัดที่ดีกว่านี้ถ้าทุกคนที่คุณอ่านพูดคุยเกี่ยวกับการตรวจสอบโมฆะโดยไม่ต้องอธิบายพวกเขาและให้รหัสตัวอย่าง ...
underscore_d

คำตอบ:


26

ใน C และ C ++ พอยน์เตอร์ไม่ปลอดภัยโดยเนื้อแท้นั่นคือเมื่อคุณทำการอ้างอิงพอยน์เตอร์มันเป็นความรับผิดชอบของคุณเองที่จะต้องแน่ใจว่ามันชี้ไปที่ที่ถูกต้อง นี่เป็นส่วนหนึ่งของ "การจัดการหน่วยความจำด้วยตนเอง" เป็นเรื่องเกี่ยวกับ (ตรงข้ามกับแผนการจัดการหน่วยความจำอัตโนมัติที่ใช้ในภาษาเช่น Java, PHP, หรือ. NET runtime ซึ่งจะไม่อนุญาตให้คุณสร้างการอ้างอิงที่ไม่ถูกต้อง

วิธีแก้ไขปัญหาทั่วไปที่จับข้อผิดพลาดมากมายคือการตั้งค่าพอยน์เตอร์ทั้งหมดที่ไม่ได้ชี้ไปที่สิ่งใด ๆNULL(หรือใน C ++ ที่ถูกต้อง0) และตรวจสอบสิ่งนั้นก่อนที่จะเข้าถึงตัวชี้ โดยเฉพาะมันเป็นเรื่องธรรมดาที่จะเริ่มต้นพอยน์เตอร์ทั้งหมดให้เป็น NULL (เว้นแต่คุณมีบางอย่างที่จะชี้ไปที่เมื่อคุณประกาศมัน) และตั้งให้เป็น NULL เมื่อคุณdeleteหรือfree()พวกมัน (เว้นแต่พวกเขาจะออกนอกขอบเขตทันทีหลังจากนั้น) ตัวอย่าง (ใน C แต่ยังถูกต้อง C ++):

void fill_foo(int* foo) {
    *foo = 23; // this will crash and burn if foo is NULL
}

รุ่นที่ดีกว่า:

void fill_foo(int* foo) {
    if (!foo) { // this is the NULL check
        printf("This is wrong\n");
        return;
    }
    *foo = 23;
}

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


3
@MrLister คุณหมายถึงอะไรการตรวจสอบโมฆะไม่ทำงานใน C ++ คุณเพียงแค่ต้องเริ่มต้นตัวชี้ให้เป็นโมฆะเมื่อคุณประกาศ
TZHX

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

4
ยืนยัน () จะเป็นทางออกที่ดีกว่าที่นี่ ไม่มีประเด็นที่พยายาม "ปลอดภัย" ถ้า NULL ถูกส่งเข้ามามันผิดอย่างเห็นได้ชัดดังนั้นทำไมไม่ลองผิดพลาดอย่างชัดเจนเพื่อให้โปรแกรมเมอร์รู้ตัวเต็มที่? (และในการผลิตมันไม่สำคัญเพราะคุณได้พิสูจน์แล้วว่าไม่มีใครโทรหา fill_foo () ด้วย NULL ใช่ไหมมันไม่ยากเลยจริงๆ)
Ambroz Bizjak

7
อย่าลืมที่จะพูดถึงว่าฟังก์ชั่นรุ่นที่ดียิ่งกว่านี้ควรใช้การอ้างอิงแทนพอยน์เตอร์ทำให้การตรวจสอบ NULL ล้าสมัย
Doc Brown

4
นี่ไม่ใช่สิ่งที่เกี่ยวกับการจัดการหน่วยความจำแบบแมนนวลและโปรแกรมที่ได้รับการจัดการจะระเบิดเช่นกัน (หรือเพิ่มข้อยกเว้นอย่างน้อยเหมือนกับโปรแกรมเนทีฟในภาษาส่วนใหญ่) หากคุณพยายามอ้างอิงการอ้างอิงโมฆะ
Mason Wheeler

7

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

ฉันจะเพิ่มคำแนะนำของฉันเองที่นี่ หลีกเลี่ยงการตรวจสอบ null :) การตรวจสอบ Null (และรูปแบบอื่น ๆ ของการเขียนโปรแกรมป้องกัน) ถ่วงและทำให้เกิดข้อผิดพลาดได้ง่ายกว่าเทคนิคการจัดการข้อผิดพลาดอื่น ๆ

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

เป็นโบนัสนี่เป็นสิ่งเล็กน้อยที่คุณอาจไม่เคยรู้เกี่ยวกับตัวชี้โมฆะ (CAROOare) สำหรับภาษา Algol W ในปี 1965

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


6
Null Object ยิ่งแย่กว่าการมีตัวชี้ null หากอัลกอริทึม X ต้องการข้อมูล Y ที่คุณไม่มีอยู่นั่นเป็นข้อผิดพลาดในโปรแกรมของคุณซึ่งคุณเพียงแค่ซ่อนโดยแสร้งทำเป็นว่าคุณทำ
DeadMG

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

หากอัลกอริทึมไม่มีอะไรทำแล้วทำไมคุณถึงเรียกมัน และเหตุผลที่คุณอาจต้องการที่จะเรียกมันว่าในสถานที่แรกคือเพราะมันไม่สิ่งที่สำคัญ
DeadMG

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

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

4

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

int *p = malloc(sizeof *p * N);
p[0] = ...; // this will (usually) blow up if malloc returned NULL

ดังนั้นเราต้องแน่ใจว่าการmallocโทรสำเร็จโดยการตรวจสอบค่าของpเทียบกับ NULL:

int *p = malloc(sizeof *p * N);
if (p != NULL) // or just if (p)
  p[0] = ...;

ตอนนี้ลองไปที่ถุงเท้าของคุณสักครู่นี่จะเป็นเรื่องเล็กน้อย

มีตัวชี้โมฆะคือค่าและตัวชี้โมฆะคงที่และทั้งสองไม่จำเป็นต้องเหมือนกัน ตัวชี้ null ค่าเป็นสิ่งที่คุ้มค่าการใช้งานสถาปัตยกรรมต้นแบบที่จะเป็นตัวแทน "ไม่มีที่ไหนเลย" ค่านี้อาจเป็น 0x00000000 หรือ 0xFFFFFFFF หรือ 0xDEADBEEF หรือสิ่งที่แตกต่างอย่างสิ้นเชิง ไม่คิดว่าตัวชี้โมฆะค่าอยู่เสมอ 0

ค่าคงที่ตัวชี้โมฆะOTOH เป็นนิพจน์อินทิกรัล 0 ค่าเสมอ เท่าที่ซอร์สโค้ดของคุณเกี่ยวข้อง 0 (หรือนิพจน์ที่สำคัญใด ๆ ที่ประเมินเป็น 0) หมายถึงตัวชี้โมฆะ ทั้ง C และ C ++ กำหนดแมโคร NULL เป็นค่าคงที่ตัวชี้โมฆะ เมื่อรหัสของคุณจะรวบรวมตัวชี้ null คงที่จะถูกแทนที่ด้วยตัวชี้ null ที่เหมาะสมคุ้มค่าในรหัสเครื่องที่สร้างขึ้น

นอกจากนี้โปรดระวังว่า NULL เป็นเพียงหนึ่งในค่าตัวชี้ที่ไม่ถูกต้องที่เป็นไปได้ หากคุณประกาศตัวแปรตัวชี้อัตโนมัติโดยไม่มีการกำหนดค่าเริ่มต้นอย่างชัดเจนเช่น

int *p;

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

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


3

มีวิธีการสองสามวิธีโดยพื้นฐานแล้วทำสิ่งเดียวกันทั้งหมด

int * foo = NULL; // บางครั้งตั้งค่าเป็น 0x00 หรือ 0 หรือ 0L แทน NULL

ตรวจสอบ null (ตรวจสอบว่าตัวชี้เป็นโมฆะ), รุ่น A

ถ้า (foo == NULL)

ตรวจสอบ null รุ่น B

if (! foo) // เนื่องจาก NULL ถูกกำหนดเป็น 0,! foo จะส่งคืนค่าจากตัวชี้ null

ตรวจสอบ null รุ่น C

ถ้า (foo == 0)

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


2

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


1
@James: 'ใหม่' ในโหมดเคอร์เนล?
Nemanja Trifunovic

1
@James: การใช้งาน C ++ ซึ่งแสดงถึงความสามารถที่ผู้ใช้ส่วนใหญ่ใช้ในการใช้ C ++ ซึ่งรวมถึงทุกภาษา C ++ คุณสมบัติ 03 ภาษา (ยกเว้นexport) และ C ++ ทั้งหมด 03 คุณลักษณะห้องสมุดและ TR1 และอันดีของ C ++ 11
DeadMG

5
ผมทำคนต้องการที่จะไม่พูดว่า "การอ้างอิงรับประกันไม่ใช่ null." พวกเขาทำไม่ได้ มันเป็นเรื่องง่ายที่จะสร้างการอ้างอิงเป็น null เป็นตัวชี้โมฆะและพวกเขาเผยแพร่ในลักษณะเดียวกัน
mjfgates

2
@Stargazer: คำถามซ้ำซ้อน 100% เมื่อคุณใช้เครื่องมือในแบบที่นักออกแบบภาษาและแนวปฏิบัติที่ดีแนะนำให้คุณ
DeadMG

2
@DeadMG มันไม่สำคัญว่าจะซ้ำซ้อน คุณไม่ได้ตอบคำถาม ฉันจะพูดอีกครั้ง: -1
riwalk

-1

หากคุณไม่ตรวจสอบค่า NULL โดยเฉพาะหากเป็นตัวชี้ไปยังโครงสร้างคุณอาจพบช่องโหว่ด้านความปลอดภัย - การตรวจสอบ NULL ตัวชี้ Nere pointer dereference อาจนำไปสู่ช่องโหว่ด้านความปลอดภัยที่ร้ายแรงอื่น ๆ เช่น buffer overflow สภาพการแข่งขัน ... ที่ทำให้ผู้โจมตีสามารถควบคุมคอมพิวเตอร์ของคุณได้

ผู้จำหน่ายซอฟต์แวร์หลายรายเช่น Microsoft, Oracle, Adobe, Apple ... ปล่อยตัวแก้ไขซอฟต์แวร์เพื่อแก้ไขช่องโหว่ด้านความปลอดภัยเหล่านี้ ฉันคิดว่าคุณควรตรวจสอบค่า NULL ของตัวชี้แต่ละตัว :)

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