โค้ด "ใช้งานจริง" (วิธีตลก ๆ ในการสะกด "buggy") ที่ใช้งานไม่ได้มีลักษณะดังนี้:
void foo(X* p) {
p->bar()->baz();
}
และมันลืมที่จะอธิบายถึงความจริงที่ว่าp->bar()
บางครั้งส่งกลับตัวชี้โมฆะซึ่งหมายความว่าการยกเลิกการลงทะเบียนเพื่อโทรbaz()
นั้นไม่ได้กำหนดไว้
ไม่ใช่รหัสทั้งหมดที่มีข้อผิดพลาดอย่างชัดเจนif (this == nullptr)
หรือมีการif (!p) return;
ตรวจสอบ บางกรณีเป็นเพียงฟังก์ชั่นที่ไม่สามารถเข้าถึงตัวแปรสมาชิกใด ๆ และดูเหมือนว่าจะทำงานได้ดี ตัวอย่างเช่น:
struct DummyImpl {
bool valid() const { return false; }
int m_data;
};
struct RealImpl {
bool valid() const { return m_valid; }
bool m_valid;
int m_data;
};
template<typename T>
void do_something_else(T* p) {
if (p) {
use(p->m_data);
}
}
template<typename T>
void func(T* p) {
if (p->valid())
do_something(p);
else
do_something_else(p);
}
ในรหัสนี้เมื่อคุณเรียกfunc<DummyImpl*>(DummyImpl*)
ตัวชี้โมฆะมี "ความคิด" ของ dereference ตัวชี้การเรียกร้องp->DummyImpl::valid()
แต่ในความจริงที่ว่าฟังก์ชันสมาชิกเพียงผลตอบแทนโดยไม่ต้องเข้าถึงfalse
*this
ที่return false
สามารถ inline และในทางปฏิบัติตัวชี้ไม่จำเป็นต้องเข้าถึงเลย ดังนั้นด้วยคอมไพเลอร์บางตัวก็ดูเหมือนว่าจะทำงานได้ดี: ไม่มี segfault สำหรับ dereferencing null, p->valid()
เป็นเท็จ, ดังนั้นการเรียกรหัสdo_something_else(p)
, ซึ่งตรวจสอบพอยน์เตอร์พอยน์เตอร์, และไม่ทำอะไรเลย. ไม่พบความผิดพลาดหรือพฤติกรรมที่ไม่คาดคิด
ด้วย GCC 6 คุณยังคงได้รับสายp->valid()
แต่ตอนนี้คอมไพเลอร์ infers จากการแสดงออกที่p
จะต้องไม่เป็นโมฆะ (มิฉะนั้นp->valid()
จะเป็นพฤติกรรมที่ไม่ได้กำหนด) และทำการบันทึกข้อมูลนั้น ข้อมูลที่สรุปนั้นถูกใช้โดยเครื่องมือเพิ่มประสิทธิภาพดังนั้นหากการเรียกเพื่อdo_something_else(p)
รับอินไลน์การif (p)
ตรวจสอบจะถูกพิจารณาซ้ำซ้อนเนื่องจากคอมไพเลอร์จดจำว่าไม่เป็นโมฆะและอินไลน์รหัสเพื่อ:
template<typename T>
void func(T* p) {
if (p->valid())
do_something(p);
else {
// inlined body of do_something_else(p) with value propagation
// optimization performed to remove null check.
use(p->m_data);
}
}
ตอนนี้จะทำการตรวจสอบตัวชี้โมฆะจริง ๆ ดังนั้นรหัสที่ปรากฏก่อนหน้านี้จะหยุดทำงาน
ในตัวอย่างนี้มีข้อบกพร่องfunc
ซึ่งควรตรวจสอบเป็นโมฆะก่อน (หรือผู้โทรไม่ควรเรียกว่าเป็นโมฆะ):
template<typename T>
void func(T* p) {
if (p && p->valid())
do_something(p);
else
do_something_else(p);
}
จุดสำคัญที่ต้องจำคือการเพิ่มประสิทธิภาพส่วนใหญ่เช่นนี้ไม่ใช่กรณีของคอมไพเลอร์ที่พูดว่า "อ่าโปรแกรมเมอร์ทดสอบตัวชี้นี้กับโมฆะฉันจะลบมันออกไปเพื่อสร้างความรำคาญ" สิ่งที่เกิดขึ้นคือการปรับให้เหมาะสมที่สุดที่ทำงานแบบหลากหลายเช่นอินไลน์และการแพร่กระจายช่วงของค่ารวมกันเพื่อทำให้การตรวจสอบเหล่านั้นซ้ำซ้อนเพราะมาหลังจากการตรวจสอบก่อนหน้าหรือการอ้างอิง หากคอมไพเลอร์รู้ว่าตัวชี้ไม่ใช่จุดว่างที่จุด A ในฟังก์ชั่นและตัวชี้ไม่เปลี่ยนแปลงก่อนจุดต่อไป B ในฟังก์ชั่นเดียวกันก็จะรู้ว่ามันไม่เป็นโมฆะที่ B เมื่อเกิดการอินไลน์ คะแนน A และ B อาจเป็นชิ้นส่วนของรหัสที่มีอยู่เดิมในฟังก์ชั่นที่แยกจากกัน แต่ตอนนี้ได้รวมกันเป็นหนึ่งชิ้นส่วนของรหัสและคอมไพเลอร์สามารถใช้ความรู้ที่ชี้ไม่เป็นโมฆะในสถานที่อื่น