2014-12-08 35 views
5

Câu hỏi của tôi:con trỏ và bộ nhớ động phân bổ

int* x = new int; 
cout << x<<"\n"; 
int* p; 
cout << p <<"\n"; 
p = x; 
delete p; 
cout << p <<"\n"; 

tôi đã viết này hoàn toàn bằng bản thân mình để hiểu được con trỏ và hiểu (hay còn bị lạc trong) năng động newdelete.

My XCode có thể biên dịch chương trình và trả về kết quả như sau:

0x100104250 
0x0 
0x100104250 

Tôi biết tôi chỉ có thể gọi xóa trên bộ nhớ cấp phát động. Tuy nhiên, tôi gọi là xóa trên p trong chương trình ở trên và nó biên dịch.

Có ai có thể giải thích điều này cho tôi không? Tại sao tôi có thể xóa p?

Hơn nữa, tôi thấy nếu chương trình thay đổi như sau:

int* x = new int; 
int* p; 
cout << p <<"\n"; 
delete p; 
cout << p <<"\n"; 

Sau đó Xcode của tôi một lần nữa biên dịch và trả về tôi:.

0x0 
0x0 
Program ended with exit code: 0 

và bây giờ, tôi đã hoàn toàn mất :( Có ai vui lòng giải thích cho tôi điều này không? Tại sao tôi có thể xóa p vì nó không có gì với x?


Vì Xcode biên dịch thành công, tôi cho rằng hai chương trình trên là chính xác cho máy tính. Tuy nhiên, tôi nghĩ rằng đó là một lần nữa tuyên bố của "chỉ gọi xóa trên bộ nhớ phân bổ động". Hoặc có lẽ, tôi không hoàn toàn hiểu con trỏ là gì và bộ nhớ được cấp phát động là gì. Tôi tìm thấy số này post khi tôi tìm kiếm trực tuyến. Nhưng tôi không nghĩ nó giống như trường hợp của tôi.

Hãy giúp tôi.


Tôi muốn hỏi một câu hỏi nữa. Mã số là here về cây tìm kiếm nhị phân. Từ dòng 28 đến 32, nó đề cập đến việc xóa một nút với một đứa trẻ. Tôi đặt phần mã này ở đây, trong trường hợp liên kết web không hoạt động.

else nếu (root-> left == NULL) { struct Node * temp = root; root = root-> right; xóa tạm thời; }

Đây là những mã dẫn tôi hỏi câu hỏi trên liên quan đến con trỏ. Theo sau câu trả lời được đưa ra bởi bài đăng này. Có đúng để hiểu mã theo cách sau không?

Tôi không thể liên kết trước tiên nút gốc của gốc từ gốc đến đúng con gốc. và sau đó xóa nút gốc, vì cây con bên dưới nút gốc cũng sẽ bị xóa. Vì vậy, tôi phải tạo ra một con trỏ tạm thời, trỏ đến khe cắm bộ nhớ, được trỏ đến bởi root. Sau đó, tôi liên kết nút cha của gốc để con phải của root. và bây giờ, tôi có thể xóa an toàn khe cắm bộ nhớ được chỉ bằng "gốc", (tức là tạm thời khi cả hai đều trỏ đến cùng một bộ nhớ). Bằng cách này, tôi giải phóng bộ nhớ và giữ liên kết giữa cha mẹ và con cái. Ngoài ra, temp vẫn còn ở đó và vẫn trỏ đến khe nhớ "đó". Tôi có nên đặt thành NULL sau khi xóa không?

Cảm ơn bạn trước một lần nữa.

Yaofeng

+1

Bạn "may mắn" là 'p' là' 0' trong mẫu mã thứ hai của bạn. Vì nó chưa được khởi tạo, nó có thể có bất kỳ giá trị nào. Vì nó là '0' (hay còn gọi là' NULL'), nó có giá trị để gọi 'delete' (điều này rất hữu ích để tránh có một triệu kiểm tra cho' NULL', đặc biệt là khi xử lý các điều kiện lỗi khi phân bổ không thành công, và bạn muốn dọn dẹp phần còn lại của phân bổ - nếu tất cả các con trỏ được khởi tạo thành 'NULL' trước, thì bạn có thể chỉ' xóa' mọi thứ, mà không lo lắng cái nào không được cấp phát). –

+1

Chỉ là một lời khuyên, bạn nên khởi tạo biến con trỏ của bạn như int * p = 0; hoặc int * p = NULL; Đó là bởi vì trong quá trình xây dựng gỡ lỗi, điều này sẽ được thực hiện cho bạn. Nhưng trong bản phát hành, nó sẽ không được thực hiện. Điều này có thể giúp bạn tiết kiệm rất nhiều thời gian gỡ lỗi. – user743414

+1

@ user743414 Trừ khi bạn đang duy trì mã cũ, bạn nên sử dụng C++ 11 và do đó 'int * p = nullptr;'. (Phần này) C++ 11 đã được hỗ trợ bởi tất cả các trình biên dịch chính trong nhiều năm nay. – Angew

Trả lời

2

Vâng, bạn có thể chỉ gọi delete vào bộ nhớ được phân bổ qua new. Lưu ý rằng đó là địa chỉ của bộ nhớ (giá trị của con trỏ) quan trọng và không phải là biến lưu trữ con trỏ. Vì vậy, mã đầu tiên của bạn:

int* x = new int; //(A) 
cout << x<<"\n"; 
int* p; 
cout << p <<"\n"; 
p = x; //(B) 
delete p; //(C) 
cout << p <<"\n"; //(D) 

Line (A) cấp phát bộ nhớ động tại một số địa chỉ (0x100104250 trong ví dụ đầu ra của bạn) và các cửa hàng địa chỉ này trong biến x. Bộ nhớ được cấp phát qua số new, có nghĩa là số delete cuối cùng phải được gọi theo địa chỉ 0x100104250.

Dòng (B) gán địa chỉ 0x100104250 (giá trị của con trỏ x) vào con trỏ p. Line (C) sau đó gọi delete p, có nghĩa là "deallocate bộ nhớ được trỏ đến bởi p." Điều này có nghĩa là nó gọi số delete trên địa chỉ 0x100104250 và tất cả đều tốt. Bộ nhớ tại địa chỉ 0x100104250 được phân bổ qua new và do đó không được phân bổ chính xác qua delete. Thực tế là bạn đã sử dụng một biến khác để lưu trữ giá trị không đóng vai trò nào.

Đường (D) chỉ in ra giá trị của con trỏ. delete hoạt động trên bộ nhớ mà con trỏ trỏ tới, không phải trên con trỏ. Giá trị của con trỏ vẫn giữ nguyên - nó vẫn trỏ đến cùng một bộ nhớ, bộ nhớ đó không còn được cấp phát nữa.


Ví dụ thứ hai là khác nhau - bạn đang gọi điện thoại delete p khi p không được khởi tạo vào bất cứ điều gì. Nó chỉ vào một mảnh bộ nhớ ngẫu nhiên, do đó, gọi delete trên đó là tất nhiên bất hợp pháp (về mặt kỹ thuật, nó có "Hành vi không xác định", và rất có thể sẽ bị lỗi).

Có vẻ như trong ví dụ cụ thể của bạn, bạn đang chạy bản dựng gỡ lỗi và trình biên dịch của bạn "giúp" khởi tạo biến cục bộ thành 0 nếu bạn không tự khởi tạo chúng. Vì vậy, nó thực sự gây ra ví dụ thứ hai của bạn không sụp đổ, kể từ khi gọi delete trên một con trỏ null là hợp lệ (và không có gì). Nhưng chương trình thực sự có một lỗi, vì các biến cục bộ thường không được khởi tạo ngầm.

+0

Cảm ơn bạn rất nhiều vì câu trả lời chi tiết và nhanh chóng của bạn. Nó khiến tôi cảm thấy thoải mái hơn nhiều về con trỏ. – Yaofeng

1
p = x; 

này sẽ làm cho p chứa cùng một giá trị như x (p sẽ trỏ đến cùng một đối tượng như x). Vì vậy, về cơ bản khi bạn nói

delete p; 

nó làm xóa trên địa chỉ được gọi bằng p mà là giống như của x. Do đó điều này là hoàn toàn hợp lệ như đối tượng được gọi bởi địa chỉ đó được phân bổ bằng cách sử dụng new.

trường hợp thứ hai: -

Co-Ngẫu nhiên con trỏ của bạn p được thiết lập bởi trình biên dịch như một NULL pointer (Bạn không nên phụ thuộc vào điều này). Vì vậy, xóa con trỏ đó là an toàn. Đã không được một con trỏ NULL, bạn sẽ có thể nhìn thấy một vụ tai nạn.

+0

Có, thỉnh thoảng nó bị treo. Tôi tìm thấy nó khi tôi thử các mã khác nhau. Tôi quên bao gồm nó trong câu hỏi của tôi. Cảm ơn rất nhiều vì đã xác nhận cho tôi thông tin này. – Yaofeng

1

Ok, hãy xem tài liệu dành cho toán tử "xóa". Theo http://en.cppreference.com/w/cpp/memory/new/operator_delete:

gọi bởi delete-biểu thức để deallocate lưu trữ trước đây được phân bổ cho một đối tượng duy nhất. Hành vi thực hiện thư viện chuẩn của hàm này là không xác định trừ khi ptr là một con trỏ rỗng hoặc là một con trỏ trước đây thu được từ việc thực hiện thư viện chuẩn của toán tử new (size_t) hoặc toán tử new (size_t, std :: nothrow_t).

Vì vậy, điều này xảy ra như sau: bạn đang gọi toán tử kích thước (int) cho biến int trong bộ nhớ. Phần đó trong bộ nhớ được tham chiếu bởi con trỏ của bạn x. Sau đó, bạn tạo một con trỏ khác, p, trỏ đến cùng một địa chỉ bộ nhớ. Khi bạn gọi xóa, bộ nhớ sẽ được giải phóng. Cả p và x vẫn trỏ đến cùng một bộ nhớ địa chỉ, ngoại trừ giá trị tại vị trí đó hiện là rác. Để làm điều này dễ dàng hơn để hiểu, tôi đổi mã của bạn như sau:

#include <iostream> 
using namespace std; 

int main() { 
    int * x = new int;    // Allocate sizeof(int) bytes, which x references to 
    *x = 8;       // Store an 8 at the newly created storage 
    cout << x << " " << *x << "\n"; // Shows the memory address x points to and "8". (e.g. 0x21f6010 8) 
    int * p;       // Create a new pointer 
    p = x;       // p points to the same memory location as x 
    cout << p << " " << *p << "\n"; // Shows the memory address p points to (the same as x) and "8". 
    delete p;      // Release the allocated memory 
    cout << x << " " << p << " " 
     << *x << " " << *p << "\n"; // p now points to the same memory address as before, except that it now contains garbage (e.g. 0x21f6010 0) 
    return 0; 
} 

Sau khi chạy, tôi có kết quả như sau:

0x215c010 8 
0x215c010 8 
0x215c010 0x215c010 0 0 

Vì vậy, hãy nhớ rằng, bằng cách sử dụng xóa bạn giải phóng bộ nhớ, nhưng con trỏ vẫn trỏ đến cùng một địa chỉ. Đó là lý do tại sao nó thường là một thực hành an toàn để cũng thiết lập con trỏ để NULL sau đó. Hy vọng điều này có ý nghĩa hơn một chút bây giờ :-)

+0

Nó giúp tôi rất nhiều. Mặc dù Xcode của tôi trả về 8 8 thay vì 0 0 cuối cùng. Nhưng tôi nghĩ tôi có ý chính. – Yaofeng

0

Trước câu trả lời, bạn cần phải hiểu các điểm sau đây.

  1. khi bạn delete một con trỏ, nó cần không được chuyển nhượng với 0. Nó có thể là bất cứ điều gì.
  2. Bạn có thể xóa 0 hoặc NULL mà không gây hại gì.
  3. Hành vi không xác định là hành vi trong đó mọi thứ đều có thể xảy ra. Chương trình có thể sụp đổ, nó có thể hoạt động bình thường như không có gì xảy ra, nó có thể tạo ra một số kết quả ngẫu nhiên, vv

Tuy nhiên, tôi gọi xóa trên p trong chương trình ở trên và nó biên dịch.

Có ai có thể giải thích điều này cho tôi không? Tại sao tôi có thể xóa p?

Đó là do bạn chỉ định địa chỉ bộ nhớ được phân bổ bởi new qua x. (p = x;) x (hoặc p) là một vị trí bộ nhớ hợp lệ và có thể bị xóa.

Bây giờ x được gọi là Dangling pointer. Bởi vì nó trỏ đến một bộ nhớ không còn hợp lệ nữa. Truy cập x sau khi xóa là hành vi không xác định.

Có ai vui lòng giải thích cho tôi điều này không? Tại sao tôi có thể xóa p vì nó không có gì với x?

Điều này là do p được chỉ định bằng 0. Do đó, bạn đang thoát khỏi hành vi không xác định. Tuy nhiên, không đảm bảo rằng con trỏ chưa được khởi tạo của bạn sẽ có giá trị là 0 hoặc NULL. Tại thời điểm này nó có vẻ làm việc tốt, nhưng bạn đang lội qua hành vi không xác định ở đây.

+0

Cảm ơn bạn rất nhiều vì đã chia sẻ thông tin về con trỏ lơ lửng. – Yaofeng

Các vấn đề liên quan