C ++ ส่งคืนการอ้างอิงถึงตัวแปรภายใน


117

รหัสต่อไปนี้ (func1 ()) ถูกต้องหรือไม่หากต้องส่งคืน i ฉันจำได้ว่าเคยอ่านที่ไหนสักแห่งว่ามีปัญหาเมื่อส่งคืนการอ้างอิงถึงตัวแปรท้องถิ่น ต่างจาก func2 () อย่างไร?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

1
หากคุณเปลี่ยน func1 () เพื่อใช้หน่วยความจำที่จัดสรรแบบไดนามิกมันจะเหมือนกัน :-)int& i = * new int;
Martin York

1
ที่เกี่ยวข้องสำหรับ const ชาวบ้าน: stackoverflow.com/questions/2784262/…
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบ:


193

ข้อมูลโค้ดนี้:

int& func1()
{
    int i;
    i = 1;
    return i;
}

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

int main()
{
    int& p = func1();
    /* p is garbage */
}

เวอร์ชันที่สองทำงานได้เนื่องจากมีการจัดสรรตัวแปรบนที่เก็บฟรีซึ่งไม่ผูกมัดกับอายุการใช้งานของการเรียกใช้ฟังก์ชัน แต่คุณมีความรับผิดชอบสำหรับไอเอ็นจีได้รับการจัดสรรdeleteint

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

โดยปกติแล้วคุณจะรวมตัวชี้ไว้ในคลาสRAIIและ / หรือฟังก์ชันจากโรงงานเพื่อที่คุณจะได้ไม่ต้องใช้deleteมันเอง

ไม่ว่าในกรณีใดคุณสามารถส่งคืนค่าได้เอง (แม้ว่าฉันจะรู้ว่าตัวอย่างที่คุณให้มานั้นอาจถูกสร้างขึ้น):

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

โปรดทราบว่าการส่งคืนอ็อบเจกต์ขนาดใหญ่ด้วยวิธีเดียวกันกับที่func3()ส่งคืนค่าดั้งเดิมเป็นเรื่องปกติเนื่องจากคอมไพเลอร์ทุกตัวในปัจจุบันใช้การเพิ่มประสิทธิภาพค่าตอบแทนบางรูปแบบ:

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

ที่น่าสนใจที่มีผลผูกพันชั่วคราวไปconstอ้างอิงทางกฎหมายได้อย่างสมบูรณ์แบบ C ++

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}

2
คำอธิบายที่สวยงาม : hattip: ในข้อมูลโค้ดที่สามคุณกำลังลบint* p = func2(); delete p;Now เมื่อคุณลบ "p" หมายความว่าหน่วยความจำที่จัดสรร "ภายใน" func2()คำจำกัดความของฟังก์ชันถูกลบด้วยหรือไม่?
Aquarius_Girl

2
@Anisha Kaul: ใช่ค่ะ หน่วยความจำถูกจัดสรรภายในfunc2()และปล่อยออกมาภายนอกในบรรทัดถัดไป มันเป็นวิธีที่ค่อนข้างผิดพลาดในการจัดการกับหน่วยความจำเช่นที่ฉันบอกว่าคุณจะใช้ RAII บางรูปแบบแทน คุณดูเหมือนกำลังเรียนรู้ C ++ ฉันขอแนะนำให้หยิบหนังสือ C ++ เบื้องต้นดีๆขึ้นมาเพื่อเรียนรู้ นอกจากนี้สำหรับการอ้างอิงในอนาคตหากคุณมีคำถามคุณสามารถโพสต์คำถามใน Stack Overflow ได้ตลอดเวลา ความคิดเห็นไม่ได้มีไว้สำหรับการถามคำถามใหม่ทั้งหมด
ในซิลิโค

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

และคุณได้แก้ไขคำตอบแล้ว ?? : mad: ฉันอาจจะพลาดง่ายๆ ;);)
Aquarius_Girl

@Anisha Kaul: ไม่ฉันไม่ได้ ครั้งสุดท้ายที่ฉันแก้ไขคำตอบคือเมื่อวันที่ 10 มกราคมตามการประทับเวลาใต้โพสต์ของฉัน
ในซิลิโค

18

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

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

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

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


2

สิ่งที่ควรจำไว้คือกฎง่ายๆเหล่านี้และใช้กับทั้งพารามิเตอร์และประเภทการส่งคืน ...

  • ค่า - ทำสำเนาของรายการที่เป็นปัญหา
  • ตัวชี้ - หมายถึงที่อยู่ของรายการที่เป็นปัญหา
  • อ้างอิง - คือรายการที่มีปัญหา

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

void func1(int& oValue)
{
    oValue = 1;
}

การทำเช่นนั้นจะเปลี่ยนค่าของพารามิเตอร์ที่คุณส่งผ่านโดยตรง ในขณะที่รหัสนี้ ...

void func1(int oValue)
{
    oValue = 1;
}

จะไม่ มันจะเปลี่ยนค่าของoValuelocal เป็นการเรียกใช้ฟังก์ชัน สาเหตุนี้เป็นเพราะคุณกำลังจะเปลี่ยนเพียงสำเนา "ในเครื่อง" oValueไม่ใช่ของoValueตัวเอง

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