อ้างอิงโมฆะได้หรือไม่?


104

รหัสชิ้นนี้ถูกต้อง (และพฤติกรรมที่กำหนด) หรือไม่

int &nullReference = *(int*)0;

ทั้งสองกรัม ++ ++ และเสียงดังกราวรวบรวมไว้โดยไม่มีการเตือนใด ๆ แม้เมื่อใช้-Wall, -Wextra, -std=c++98, -pedantic, -Weffc++...

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

if( & nullReference == 0 ) // null reference

1
คุณสามารถให้กรณีที่เป็นประโยชน์ได้หรือไม่? กล่าวอีกนัยหนึ่งนี่เป็นเพียงคำถามทางทฤษฎีหรือไม่?
cdhowie

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

8
ฉันคิดว่ามันขมวดคิ้ว
ค่าเริ่มต้น

22
"เราตรวจสอบได้" - ไม่คุณทำไม่ได้ มีคอมไพเลอร์ที่เปลี่ยนคำสั่งเป็นif (false)ลบการตรวจสอบอย่างแม่นยำเนื่องจากการอ้างอิงไม่สามารถเป็นโมฆะได้ มีเอกสารเวอร์ชันที่ดีกว่าในเคอร์เนลลินุกซ์ซึ่งการตรวจสอบ NULL ที่คล้ายกันมากได้รับการปรับให้เหมาะสม: isc.sans.edu/diary.html?storyid=6820
MSalters

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

คำตอบ:


76

การอ้างอิงไม่ใช่ตัวชี้

8.3.2 / 1:

การอ้างอิงจะต้องเริ่มต้นเพื่ออ้างถึงวัตถุหรือฟังก์ชันที่ถูกต้อง [หมายเหตุ: โดยเฉพาะอย่างยิ่งการอ้างอิงที่เป็นโมฆะไม่สามารถมีอยู่ในโปรแกรมที่กำหนดไว้อย่างดีเนื่องจากวิธีเดียวในการสร้างการอ้างอิงดังกล่าวคือการเชื่อมโยงกับ "วัตถุ" ที่ได้รับจากการอ้างถึงตัวชี้ค่าว่างซึ่งทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดไว้ ตามที่อธิบายไว้ใน 9.6 การอ้างอิงไม่สามารถเชื่อมโยงโดยตรงกับบิตฟิลด์ ]

1.9 / 4:

การดำเนินการอื่น ๆ บางอย่างได้อธิบายไว้ในมาตรฐานสากลฉบับนี้ว่าไม่ได้กำหนดไว้ (ตัวอย่างเช่นผลของการยกเลิกการอ้างอิงตัวชี้ค่าว่าง)

ดังที่ Johannes กล่าวในคำตอบที่ถูกลบมีข้อสงสัยว่า "dereferencing a null pointer" ควรระบุอย่างชัดเจนว่าเป็นพฤติกรรมที่ไม่ได้กำหนดหรือไม่ แต่นี่ไม่ใช่หนึ่งในกรณีที่ทำให้เกิดข้อสงสัยเนื่องจากตัวชี้โมฆะไม่ได้ชี้ไปที่ "วัตถุหรือฟังก์ชันที่ถูกต้อง" อย่างแน่นอนและคณะกรรมการมาตรฐานไม่มีความปรารถนาที่จะแนะนำการอ้างอิงที่เป็นโมฆะ


ฉันลบคำตอบของฉันออกไปตั้งแต่ฉันรู้ว่าปัญหาเพียงอย่างเดียวในการยกเลิกการอ้างอิงตัวชี้ว่างและการได้รับค่า lvalue ที่อ้างถึงนั้นเป็นสิ่งที่แตกต่างจากการผูกการอ้างอิงตามที่คุณกล่าวถึง แม้ว่า lvalues ​​จะกล่าวถึงวัตถุหรือฟังก์ชันด้วย (ดังนั้นในประเด็นนี้การเชื่อมโยงอ้างอิงจึงไม่มีความแตกต่างกัน) แต่ทั้งสองสิ่งนี้ก็ยังคงเป็นข้อกังวลที่แยกจากกัน สำหรับการอ้างอิงเพียงอย่างเดียวนี่คือลิงค์: open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1102
Johannes Schaub - litb

1
@MSalters (ตอบกลับความคิดเห็นเกี่ยวกับคำตอบที่ถูกลบเกี่ยวข้องที่นี่) ฉันไม่เห็นด้วยโดยเฉพาะกับตรรกะที่นำเสนอที่นั่น แม้ว่ามันอาจจะสะดวกในการถ่ายทอด&*pอย่างเป็นpสากล แต่ก็ไม่ได้แยกแยะพฤติกรรมที่ไม่ได้กำหนดออกมา (ซึ่งโดยธรรมชาติแล้วมันอาจจะ "ดูเหมือนจะได้ผล"); และฉันไม่เห็นด้วยที่typeidนิพจน์ที่พยายามกำหนดประเภทของ "ตัวชี้ค่าว่างที่ถูกยกเลิกการอ้างอิง" จริง ๆ แล้วจะ dereferences ตัวชี้ null ผมเคยเห็นคนเถียงอย่างจริงจังว่าไม่สามารถและไม่ควรพึ่งพาอาศัยและอยู่แล้วมันเป็นเรื่องง่ายและปลอดภัยในการเพียงแค่เขียน&a[size_of_array] a + size_of_array
Karl Knechtel

@ มาตรฐานเริ่มต้นในแท็ก [c ++] ควรสูง คำตอบของฉันฟังดูเหมือนว่าการกระทำทั้งสองเป็นสิ่งเดียวกัน :) ในขณะที่การอ้างอิงและรับค่า lvalue คุณไม่ได้ส่งผ่านสิ่งที่อ้างถึง "ไม่มีวัตถุ" อาจเป็นไปได้การจัดเก็บไว้ในการอ้างอิงที่หลีกเลี่ยงขอบเขตที่ จำกัด และอาจส่งผลกระทบในทันใด รหัสอื่น ๆ อีกมากมาย
Johannes Schaub - litb

@Karl ได้ดีใน C ++ "dereferencing" ไม่ได้หมายความว่าจะอ่านค่า บางคนคิดว่า "dereference" หมายถึงการเข้าถึงหรือแก้ไขค่าที่เก็บไว้ แต่นั่นไม่เป็นความจริง ตรรกะคือ C ++ บอกว่า lvalue หมายถึง "วัตถุหรือฟังก์ชัน" ถ้าเป็นเช่นนั้นคำถามคือสิ่งที่ lvalue *pอ้างถึงเมื่อpเป็นตัวชี้โมฆะ ปัจจุบัน C ++ ไม่มีแนวคิดเรื่อง lvalue ว่างซึ่งปัญหาที่ 232 ต้องการแนะนำ
Johannes Schaub - litb

การตรวจหาพอยน์เตอร์ว่างที่ถูกยกเลิกการอ้างอิงใน typeidงานตามไวยากรณ์แทนที่จะอิงตามความหมาย นั่นคือถ้าคุณทำtypeid(0, *(ostream*)0)คุณจะมีพฤติกรรมที่ไม่ได้กำหนด - ไม่bad_typeidรับประกันว่าจะถูกโยนแม้ว่าคุณผ่านการ lvalue ที่เกิดจากการ dereference ชี้โมฆะความหมาย แต่ในทางวากยสัมพันธ์ที่ระดับบนสุดมันไม่ใช่การหักล้าง แต่เป็นนิพจน์ตัวดำเนินการลูกน้ำ
Johannes Schaub - litb

27

คำตอบขึ้นอยู่กับมุมมองของคุณ:


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


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

อย่างไรก็ตามการปรับให้เหมาะสมนั้นขึ้นอยู่กับคอมไพลเลอร์เพื่อพิสูจน์พฤติกรรมที่ไม่ได้กำหนดซึ่งอาจไม่สามารถทำได้ พิจารณาฟังก์ชันง่ายๆนี้ภายในไฟล์converter.cpp:

int& toReference(int* pointer) {
    return *pointer;
}

เมื่อคอมไพลเลอร์เห็นฟังก์ชันนี้จะไม่ทราบว่าตัวชี้เป็นตัวชี้ค่าว่างหรือไม่ ดังนั้นจึงสร้างโค้ดที่เปลี่ยนตัวชี้ใด ๆ ให้เป็นการอ้างอิงที่เกี่ยวข้อง (Btw: นี่คือ noop เนื่องจากพอยน์เตอร์และการอ้างอิงเป็นสัตว์ร้ายตัวเดียวกันในแอสเซมเบลอร์) ตอนนี้ถ้าคุณมีไฟล์อื่นที่user.cppมีรหัส

#include "converter.h"

void foo() {
    int& nullRef = toReference(nullptr);
    cout << nullRef;    //crash happens here
}

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

คุณอาจถามว่าเหตุใดจึงเกี่ยวข้องอย่างไรก็ตามพฤติกรรมที่ไม่ได้กำหนดได้ถูกกระตุ้นภายในtoReference()แล้ว คำตอบคือการดีบัก: การอ้างอิง Null อาจแพร่กระจายและแพร่กระจายได้เช่นเดียวกับที่พอยน์เตอร์ว่างทำ หากคุณไม่ทราบว่าสามารถมีการอ้างอิงที่เป็นโมฆะได้และเรียนรู้ที่จะหลีกเลี่ยงการสร้างสิ่งเหล่านี้คุณอาจใช้เวลาพอสมควรในการพยายามหาสาเหตุว่าทำไมฟังก์ชันสมาชิกของคุณถึงขัดข้องเมื่อพยายามอ่านintสมาชิกเก่าธรรมดา(คำตอบ: อินสแตนซ์ ในการเรียกของสมาชิกเป็นการอ้างอิงที่เป็นโมฆะดังนั้นthisตัวชี้ที่เป็นโมฆะและสมาชิกของคุณจะถูกคำนวณให้อยู่ในตำแหน่งที่อยู่ 8)


แล้วการตรวจสอบการอ้างอิงว่างล่ะ? คุณให้สาย

if( & nullReference == 0 ) // null reference

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


TL; DR:

การอ้างอิงที่เป็นค่าว่างนั้นค่อนข้างมีความน่ากลัว:

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

ฉันหวังว่าจะไม่มาหลอกหลอนคุณ!


2
"ช้างปิง" คืออะไร?
Pharap

2
@Pharap ฉันไม่มีเงื่อนงำมันเป็นเพียงการพิมพ์ผิด แต่มาตรฐาน C ++ จะไม่สนใจว่าจะเป็นช้างสีชมพูหรือปิงที่ปรากฏ แต่อย่างใด ;-)
cmaster - คืนสถานะ monica

9

หากความตั้งใจของคุณคือการหาวิธีแทนค่าว่างในการแจงนับของอ็อบเจกต์ซิงเกิลตันก็เป็นความคิดที่ดีที่จะ (de) การอ้างอิงโมฆะ (C ++ 11, nullptr)

เหตุใดจึงไม่ประกาศวัตถุแบบสแตติกซิงเกิลตันที่แสดงถึง NULL ภายในคลาสดังต่อไปนี้และเพิ่มตัวดำเนินการ cast-to-pointer ที่คืนค่า nullptr

แก้ไข: แก้ไขความผิดพลาดหลายอย่างและเพิ่ม if-statement ใน main () เพื่อทดสอบว่าตัวดำเนินการ cast-to-pointer ใช้งานได้จริง (ซึ่งฉันลืมไป .. แย่จัง) - 10 มีนาคม 2558 -

// Error.h
class Error {
public:
  static Error& NOT_FOUND;
  static Error& UNKNOWN;
  static Error& NONE; // singleton object that represents null

public:
  static vector<shared_ptr<Error>> _instances;
  static Error& NewInstance(const string& name, bool isNull = false);

private:
  bool _isNull;
  Error(const string& name, bool isNull = false) : _name(name), _isNull(isNull) {};
  Error() {};
  Error(const Error& src) {};
  Error& operator=(const Error& src) {};

public:
  operator Error*() { return _isNull ? nullptr : this; }
};

// Error.cpp
vector<shared_ptr<Error>> Error::_instances;
Error& Error::NewInstance(const string& name, bool isNull = false)
{
  shared_ptr<Error> pNewInst(new Error(name, isNull)).
  Error::_instances.push_back(pNewInst);
  return *pNewInst.get();
}

Error& Error::NOT_FOUND = Error::NewInstance("NOT_FOUND");
//Error& Error::NOT_FOUND = Error::NewInstance("UNKNOWN"); Edit: fixed
//Error& Error::NOT_FOUND = Error::NewInstance("NONE", true); Edit: fixed
Error& Error::UNKNOWN = Error::NewInstance("UNKNOWN");
Error& Error::NONE = Error::NewInstance("NONE");

// Main.cpp
#include "Error.h"

Error& getError() {
  return Error::UNKNOWN;
}

// Edit: To see the overload of "Error*()" in Error.h actually working
Error& getErrorNone() {
  return Error::NONE;
}

int main(void) {
  if(getError() != Error::NONE) {
    return EXIT_FAILURE;
  }

  // Edit: To see the overload of "Error*()" in Error.h actually working
  if(getErrorNone() != nullptr) {
    return EXIT_FAILURE;
  }
}

เพราะมันช้า
wandalen

6

clang ++ 3.5 ยังเตือน:

/tmp/a.C:3:7: warning: reference cannot be bound to dereferenced null pointer in well-defined C++ code; comparison may be assumed to
      always evaluate to false [-Wtautological-undefined-compare]
if( & nullReference == 0 ) // null reference
      ^~~~~~~~~~~~~    ~
1 warning generated.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.