Return và Object Lifetime: reference vs pointer vs value

Trả về kiểu value như sau thì quá cơ bản và không có vấn đề với scope:

int return_Value()
{
int value = 500;
return value;
}

Trả về kiểu reference:

int& return_Reference()
{
int value = 10;
return value;
}

Như mình biết thì không nên trả về reference như trên vì biến value là local, khi hàm thực thi xong sẽ tự động được giải phóng. Nhưng khi mình thử dòng code dưới đây trong main() thì nó vẫn ra giá trị đúng.

int data = return_Reference(); // data sẽ có giá trị là 10.

Suy nghĩ của mình: mặc dù biến value được giải phóng sau khi hàm return_Reference() thực thi xong nhưng giá trị mà nó lưu trữ trên stack vẫn nằm ở đó. Chỉ khi nào có người khác dùng vùng nhớ đó thì nó mới thay đổi. Vì vậy, khi mình gán cho biến data ở trên thì nó vẫn trả ra giá trị là 10.
Nếu không trả về kiểu tham trị nhưng vẫn muốn gán giá trị của return_Referance() cho một biến nào đó thì mình có thể đổi lại như sau:

void return_Reference(int& output)
{
int value = 10;
output = value;
}

Trả về kiểu pointer:
Trường hợp 1: memory allocated on Heap

user_define* return_Heap_Pointer()
{
user_define* pointer = new user_define();
return pointer;
}

Theo mình hiểu thì nó chỉ trả về cái địa chỉ trỏ đến object của user_define vừa được tạo ra. user_define nằm trong heap nên muốn giải phóng phải dùng delete. Khi return_Heap_Pointer() thực thi xong thì biến pointer vẫn bị giải phóng vì nó là biến local. Nhưng như mình vừa nói ở trên, nó trả về cái địa chỉ mà nó trỏ đến nên khi mình gán như dưới đây thì không vấn đề gì, việc nó bị hủy chẳng ảnh hưởng gì ở đây.

user_define* p = return_Heap_Pointer();

Trường hợp 2: memory allocated on Stack

int* return_Stack_Pointer()
{
int age = 10;
int* p = &age;
return p;
}

Nếu mình dùng hàm return_Stack_Pointer() như sau:

int* data = return_Stack_Pointer();

thì kết quả nhận được khi dereference data là 10.
Cho mình hỏi khi một local variable được allocate trên stack mà lose scope thì nó như thế nào? Dựa vào mấy cái code trên thì mình nghĩ là khi lose scope, các variable này bị xóa nhưng giá trị mà nó được gán cho vẫn nằm trong stack, tại cái địa chỉ mà các variable này được tạo ra. Các vùng nhớ này nếu không được người khác sử dụng thì giá trị của các variale trên vẫn nằm ở đây. Không biết mình hiểu đúng không?

Vậy khi nào mới nên trả về kiểu reference? Câu trả lời của mình: chỉ dùng khi trả về cái gì đó nằm trên heap. Ví dụ: khi overload assignment operator.
Trả về kiểu pointer => tương tự referece.

Những cái sau đây mình hiểu có đúng không?

int main()
{
char name[50]; // local variable, nằm trên stack
WaterWater myCup; //WaterWater là user define type, local variable, nằm trên heap mặc dù new không đươc gọi.
WaterWater *myGlass = new WaterWater(); // myGlass là local variable, vùng nhớ mà myGlass trỏ tới được allocate trên heap và cần gọi delete sau khi dùng xong.
}
1 Like

ko được những dòng lệnh khác sử dụng thì đúng hơn. Chạy 1 hàm khác là vùng stack cũ đó đã bị thay đổi rồi.

nằm trên stack vẫn trả về reference / pointer được. Object của C++ tạo ra nằm trên STACK. Trừ phi cấp phát động cho nó thì nó mới nằm trên heap. Nếu object tồn tại lâu hơn reference tới thành phần của nó thì vô tư. Overload assignment operator trả về *this là nằm trên stack/heap rồi? Sao lại ko được?? Trả về 1 biến trên heap vẫn có thể ko xài được vậy?

int* f() { return new int(11); }

int* p = f();
int* q = p;
delete p;
*q; // giá trị rác, lỗi

chung quy vẫn là đừng xài biến đã được giải phóng. Nằm trên stack thì nó được tự động giải phóng, nhưng giải phóng khi nào?

int* p = nullptr;

{
    Object obj(11); // obj nằm trên stack. Ví dụ Object có 1 biến int value thì value cũng nằm trên stack
    p = obj.getIntValue(); // int* getIntValue() trả về &value là con trỏ tới 1 biến nằm trên stack

    // xài *p vô tư vì obj chưa bị giải phóng

} // obj thoát khỏi scope của nó, được tự động giải phóng
// ko được xài *p từ dòng này trở đi

ko đúng, myCup nằm hoàn toàn trên stack. Nếu muốn nó nằm trên heap thì xài câu lệnh new như ở dòng dưới em viết ấy. C++ nó ko có như Java. Java lúc nào object cũng ở trên heap. C++ tối ưu tốc độ nên obj sẽ nằm trên stack, trừ phi muốn nó nằm trên heap thì nó mới nằm trên heap. Hơi rắc rối vì vậy ông Java ổng muốn đơn giản ổng quăng lên heap hết.

ví dụ class mystring có 3 biến là char* data, int sizeint capacity. Khi khai báo

mystring s("hello");

thì s.data, s.size, s.capacity nằm trên stack hết, vì s nằm trên stack. Nhưng mảng ký tự "hello"s.data trỏ tới là nằm trên heap.
image

khi khai báo

mystring* p = new mystring("hello");

thì s.data, s.size, s.capacity nằm trên heap hết, vì *p nằm trên heap. Bản thân p nằm trên stack. Mảng ký tự "hello"s.data trỏ tới cũng nằm trên heap.
image

4 Likes

Mình viết một cái như trong ví dụ object của bạn.

class Object
{
private:
  int data;
public:
  Object(int info) : data(info) {};
  int* getIntValue() {
     return &data;
  }

  ~Object()
  {
     cout << "delete object" << endl;
  }
};

int main()
{
  int* p = nullptr;
  {
     Object obj(11);
     p = obj.getIntValue();
     cout << *p << endl;
  }
  cout << *p << endl;
}

Mình luôn nghĩ khi ra khỏi scope thì các biến local điều là rác. Theo đó, trong trường hợp này, obj bị giải phóng và field data của nó sẽ là giá trị rác.
Giá trị mà p trỏ tới vẫn là 11 chứ không phải rác. Vậy trong ví dụ trên khi nào giá trị của p mới là rac?

1 Like

khi obj được giải phóng. Bạn nghĩ là khi nào obj được giải phóng trong code trên? :V

ở dòng cout << *p << endl; thứ 2 :V ví dụ link này nó ra 0: https://rextester.com/YOCK42341

“lỡ” viết dtor rồi thì set luôn giá trị value thành giá trị rác nào đó, ví dụ 123456789, thì rõ hơn :V

cout << "delete object" << endl;
data = 0x12345678; // giả sử đây là giá trị rác
4 Likes

Không. Nó ra 11, không phải giá trị rác. Mình viết cout trong dtor để chắc chắn là object bị destroy khi mà ra khỏi scope. Mình viết trên VS 2019, không biết tại sao nó như vậy. Cái link của bạn thì cho ra giá trị rác thật.
Vậy mình nên set field attribute thành giá trị rác luôn sao? Mình chưa nghe bao giờ. Stack tự động giải phóng thì nó phải làm việc đó chứ. Việc đó sao đẩy cho người lập trình được.

1 Like

ý mình là set ở đây để thấy đúng là nó là rác thật, ko còn giá trị 11 nữa, “giả lập” giá trị rác đó là do hàm khác ghi vào đó mà :V

nếu là VS thì compile ở chế độ Debug nó sẽ cho ra giá trị rác 0xCCCCCCCC gì mà phải ko?

4 Likes

Nhưng vấn đề ở đây là mình không hiểu tại sao nó không tự động làm việc đó - xóa các giá trị nó được gán và thay bằng giá trị rác. Tại sao sau khi bị destroy giá trị đó vẫn là 11? Mình học lí thuyết thì nó đáng ra không nên như vậy.
Mình để debug mode x86 rồi build và chạy.

1 Like

vì tốc độ :V nếu gán giá trị rác thì tốn thêm 1 bước gán nữa, làm chậm chương trình. Nếu obj ko chỉ chứa 1 giá trị int mà 1000 int thì sao? Gán giá trị rác cho 1000 int này à? 100 obj như vậy là 10^5 lần gán, chậm chương trình khá nhiều, trong khi ở đây ko có cấp phát động gì, 1000 int nằm trên stack, giải phóng chỉ cần dịch chuyển stack pointer đi 4000 bytes là xong (nếu ko có dtor, nếu có dtor thì nó sẽ gọi dtor cho từng obj) :V

https://rextester.com/IMYUG19383 link này compile với -g -O0 nó ra tương đối “đúng” hành vi của stack đây. Sau khi obj bị xóa nó tạo a, b, c, c trùng vị trí với obj.data cũ, nó in ra giá trị 55 của c.

4 Likes

Vậy thì cứ nhớ lý thuyết là ra khỏi scope thì đừng dùng nữa cho chắc à?
Thực ra đó giờ nghe sao làm vậy. Nãy h ngồi vọc vọc mấy cái này thì thấy nó ra không giống như những gì mình được dạy mặc dù lý thuyết nghe rất hợp lý.

1 Like

chắc thấm đòn undefined behavior của nó thì nhớ =]

ngôn ngữ Rust nó bắt được vụ dangling pointer/reference này trong lúc compile luôn hay sao đó. Tác giả của nó cũng rất ghét vụ ko phát hiện được như thế này nên mới tạo ra Rust là nnlt ưu tiên về memory safety :V

còn hiện tại trong C++ thì bạn phải nhớ trỏ tới cái gì thì cái bị trỏ tới đó phải sống dai hơn con trỏ bạn trỏ tới :V

4 Likes

Cái này thấy là undefined behavior rồi đó :joy:

VS có tính năng code analysis chống dangling pointer nè

4 Likes

Cám ơn bạn :slight_smile:

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?