C ++ Trả về tham chiếu cho biến cục bộ


116

Mã sau (func1 ()) có đúng không nếu nó phải trả về i? Tôi nhớ đã đọc ở đâu đó rằng có vấn đề khi trả về tham chiếu cho một biến cục bộ. Nó khác với func2 () như thế nào?

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

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

1
Nếu bạn thay đổi Func1 () để sử dụng bộ nhớ cấp phát động sau đó họ đều giống nhau :-)int& i = * new int;
Martin York

Câu trả lời:


193

Đoạn mã này:

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

sẽ không hoạt động vì bạn đang trả về bí danh (tham chiếu) cho một đối tượng có thời gian tồn tại được giới hạn trong phạm vi của lệnh gọi hàm. Điều đó có nghĩa là một khi func1()trả về, int isẽ chết, làm cho tham chiếu được trả về từ hàm trở nên vô giá trị vì bây giờ nó tham chiếu đến một đối tượng không tồn tại.

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

Phiên bản thứ hai hoạt động vì biến được cấp phát trên cửa hàng miễn phí, không bị ràng buộc với thời gian tồn tại của lệnh gọi hàm. Tuy nhiên, bạn có trách nhiệm nhập deletephân bổ int.

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
}

Thông thường, bạn sẽ bọc con trỏ trong một số lớp RAII và / hoặc một hàm gốc để bạn không cần phải deletetự làm.

Trong cả hai trường hợp, bạn chỉ có thể trả về giá trị của chính nó (mặc dù tôi nhận thấy ví dụ bạn cung cấp có thể đã được tạo ra):

int func3()
{
    return 1;
}

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

Lưu ý rằng hoàn toàn tốt nếu trả về các đối tượng lớn theo cùng một cách func3()trả về các giá trị nguyên thủy vì hầu như mọi trình biên dịch hiện nay đều thực hiện một số hình thức tối ưu hóa giá trị trả về :

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();    
}

Điều thú vị là, ràng buộc tạm thời với một tham chiếu constC ++ hoàn toàn hợp pháp .

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
Lời giải thích hay. : hattip: Trong đoạn mã thứ ba, bạn đang xóa int* p = func2(); delete p;Bây giờ, khi bạn xóa 'p', có nghĩa là bộ nhớ được cấp "bên trong" func2()định nghĩa của hàm cũng bị xóa?
Aquarius_Girl

2
@Anisha Kaul: Vâng. Bộ nhớ được cấp phát bên trong func2()và giải phóng bên ngoài ở dòng tiếp theo. Đó là một cách khá dễ xảy ra lỗi để xử lý bộ nhớ, giống như tôi đã nói bạn sẽ sử dụng một số biến thể của RAII để thay thế. Nhân tiện, bạn có vẻ như đang học C ++. Tôi khuyên bạn nên chọn một cuốn sách C ++ nhập môn hay để học hỏi. Ngoài ra, để tham khảo trong tương lai nếu bạn có câu hỏi, bạn luôn có thể đăng câu hỏi trên Stack Overflow. Nhận xét không có nghĩa là để hỏi những câu hỏi hoàn toàn mới.
Trong silico,

Bây giờ tôi đã hiểu, bạn đã làm đúng! Hàm đang trả về một con trỏ và bên ngoài hàm đó, bạn đã xóa bộ nhớ mà nó đã trỏ tới. Bây giờ nó đã rõ ràng, và cảm ơn vì liên kết.
Aquarius_Girl

và bạn đã chỉnh sửa câu trả lời ?? : mad: Tôi có thể đã bỏ lỡ nó dễ dàng. ;);)
Aquarius_Girl

@Anisha Kaul: Không, tôi không có. Lần cuối cùng tôi chỉnh sửa câu trả lời của mình là vào ngày 10 tháng 1, theo dấu thời gian dưới bài đăng của tôi.
In silico,

18

Biến cục bộ là bộ nhớ trên ngăn xếp, bộ nhớ đó không tự động bị vô hiệu khi bạn đi ra khỏi phạm vi. Từ một Hàm được lồng sâu hơn (cao hơn trên ngăn xếp trong bộ nhớ), nó hoàn toàn an toàn để truy cập bộ nhớ này.

Một khi Hàm trả về và kết thúc, mọi thứ trở nên nguy hiểm. Thông thường bộ nhớ không bị xóa hoặc bị ghi đè khi bạn quay lại, có nghĩa là bộ nhớ tại các địa chỉ đó vẫn đang chứa dữ liệu của bạn - con trỏ có vẻ hợp lệ.

Cho đến khi một hàm khác xây dựng ngăn xếp và ghi đè lên nó. Đây là lý do tại sao điều này có thể hoạt động trong một thời gian - và sau đó đột nhiên ngừng hoạt động sau khi một bộ hàm đặc biệt được lồng sâu vào nhau hoặc một hàm có kích thước thực sự lớn hoặc nhiều đối tượng cục bộ, lại tiếp cận bộ nhớ ngăn xếp đó.

Thậm chí có thể xảy ra trường hợp bạn tiếp cận lại cùng một phần chương trình và ghi đè biến hàm cục bộ cũ của bạn bằng biến hàm mới. Tất cả điều này là rất nguy hiểm và nên được khuyến khích nhiều. Không sử dụng con trỏ đến các đối tượng cục bộ!


2

Một điều tốt cần nhớ là những quy tắc đơn giản này và chúng áp dụng cho cả tham số và kiểu trả về ...

  • Giá trị - tạo một bản sao của mục được đề cập.
  • Con trỏ - đề cập đến địa chỉ của mục được đề cập.
  • Tham chiếu - theo nghĩa đen là mục được đề cập.

Có thời gian và địa điểm cho mỗi việc, vì vậy hãy đảm bảo bạn làm quen với chúng. Các biến cục bộ, như bạn đã trình bày ở đây, chỉ là, giới hạn trong thời gian chúng tồn tại cục bộ trong phạm vi hàm. Trong ví dụ của bạn, có kiểu trả về int*và trả về &isẽ không chính xác như nhau. Bạn sẽ tốt hơn nếu làm điều này ...

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

Làm như vậy sẽ thay đổi trực tiếp giá trị của tham số được truyền vào của bạn. Trong khi mã này ...

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

không muốn. Nó sẽ chỉ thay đổi giá trị của oValuelocal thành lời gọi hàm. Lý do cho điều này là vì bạn thực sự chỉ đang thay đổi bản sao "cục bộ" của oValue, chứ không phải oValuechính nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.