2009-12-18 28 views
23

Tôi đã viết một trò chơi Tetris đơn giản, hoạt động với mỗi khối như một thể hiện của một lớp đơn.C++ delete - Nó xóa các đối tượng của tôi nhưng tôi vẫn có thể truy cập dữ liệu?

class SingleBlock 
{ 
    public: 
    SingleBlock(int, int); 
    ~SingleBlock(); 

    int x; 
    int y; 
    SingleBlock *next; 
}; 

class MultiBlock 
{ 
    public: 
    MultiBlock(int, int); 

    SingleBlock *c, *d, *e, *f; 
}; 

SingleBlock::SingleBlock(int a, int b) 
{ 
    x = a; 
    y = b; 
} 

SingleBlock::~SingleBlock() 
{ 
    x = 222; 
} 

MultiBlock::MultiBlock(int a, int b) 
{ 
    c = new SingleBlock (a,b); 
    d = c->next = new SingleBlock (a+10,b); 
    e = d->next = new SingleBlock (a+20,b); 
    f = e->next = new SingleBlock (a+30,b); 
} 

Tôi có chức năng quét toàn bộ dòng và chạy qua danh sách các khối được liên kết xóa các liên kết và gán lại -> con trỏ tiếp theo.

SingleBlock *deleteBlock; 
SingleBlock *tempBlock; 

tempBlock = deleteBlock->next; 
delete deleteBlock; 

Hoạt động của trò chơi, các khối sẽ bị xóa chính xác và mọi chức năng như được cho là. Tuy nhiên về kiểm tra, tôi vẫn có thể truy cập các bit ngẫu nhiên của dữ liệu đã xóa.

Nếu tôi in từng giá trị "x" đã xóa sau khi xóa, một số sẽ trả về rác ngẫu nhiên (xác nhận xóa) và một số trong số chúng trả về 222, cho tôi biết ngay cả khi destructor được gọi là dữ liệu wasn ' t thực sự bị xóa khỏi heap. Nhiều thử nghiệm giống hệt nhau cho thấy nó luôn là các khối cụ thể giống nhau mà không bị xóa đúng cách.

Kết quả:

Existing Blocks: 
Block: 00E927A8 
Block: 00E94290 
Block: 00E942B0 
Block: 00E942D0 
Block: 00E942F0 
Block: 00E94500 
Block: 00E94520 
Block: 00E94540 
Block: 00E94560 
Block: 00E945B0 
Block: 00E945D0 
Block: 00E945F0 
Block: 00E94610 
Block: 00E94660 
Block: 00E94680 
Block: 00E946A0 

Deleting Blocks: 
Deleting ... 00E942B0, X = 15288000 
Deleting ... 00E942D0, X = 15286960 
Deleting ... 00E94520, X = 15286992 
Deleting ... 00E94540, X = 15270296 
Deleting ... 00E94560, X = 222 
Deleting ... 00E945D0, X = 15270296 
Deleting ... 00E945F0, X = 222 
Deleting ... 00E94610, X = 222 
Deleting ... 00E94660, X = 15270296 
Deleting ... 00E94680, X = 222 

là việc có thể truy cập dữ liệu từ thế giới bên kia mong đợi?

Xin lỗi nếu điều này hơi dài.

+1

Chính sách an toàn nhất là để xóa một mục khi nó không còn được sử dụng, và không bao giờ đề cập đến nó một lần nữa. Con trỏ thông minh có thể giúp khi nhiều hơn một con trỏ đang đề cập đến cùng một đối tượng trong bộ nhớ. –

+0

Nếu bạn có thể truy cập các khối, bạn có thể xóa chúng. Thật tồi tệ. Đừng làm thế. –

+15

Đôi khi tôi nghĩ rằng một từ khóa tốt hơn 'xóa' sẽ là' quên'; bạn không thực sự nói với trình biên dịch để * xóa * bất cứ điều gì nhiều như * ngừng quan tâm * nó (và để cho người khác làm bất cứ điều gì họ muốn với tôi) giống như trả lại một cuốn sách cho thư viện hơn là đốt nó. –

Trả lời

69

Có thể truy cập dữ liệu từ ngoài dự kiến ​​không?

Điều này về mặt kỹ thuật được gọi là Hành vi không xác định. Đừng ngạc nhiên nếu nó cung cấp cho bạn một lon bia hoặc.

+9

Ngoài ra, tốt nhất là thêm hệ quả của thực tế đó ... Nếu có dữ liệu "nhạy cảm" được lưu trữ trong bộ nhớ, người ta nên xem là thực hành tốt để ghi đè hoàn toàn trước khi xóa nó (để ngăn chặn khác các đoạn mã truy cập nó). – Romain

+0

Điều đó phải được xử lý trước cuộc gọi dtor. – dirkgently

+1

... hoặc ít nhất là trong dtor. – dirkgently

0

Nó sẽ không thay đổi bộ nhớ ... nhưng tại một thời điểm nào đó, tấm thảm sẽ được kéo từ dưới chân bạn.

Không chắc chắn nó không thể dự đoán được: nó phụ thuộc vào cách phân bổ bộ nhớ nhanh/deallocation được khuấy động.

28

Có thể truy cập dữ liệu từ ngoài dự kiến ​​không?

Trong hầu hết các trường hợp, có. Gọi xóa không phải là bộ nhớ.

Lưu ý rằng hành vi không được xác định. Sử dụng các trình biên dịch nhất định, bộ nhớ có thể được zeroed. Khi bạn gọi xóa, điều xảy ra là bộ nhớ được đánh dấu là có sẵn, do đó, lần sau khi ai đó thực hiện mới, bộ nhớ có thể được sử dụng.

Nếu bạn nghĩ về điều đó, đó là hợp lý - khi bạn nói với trình biên dịch rằng bạn không còn quan tâm đến bộ nhớ (sử dụng xóa), tại sao máy tính nên dành thời gian để zeroing nó.

+0

Tuy nhiên, không có gì đảm bảo rằng 'new' hoặc' malloc' sẽ không phân bổ một số đối tượng mới trên các đối tượng cũ. Một thảm họa khác có thể là bộ thu gom rác của hệ thống. Ngoài ra, nếu chương trình của bạn được cấp bộ nhớ từ một bộ nhớ toàn bộ hệ thống, các chương trình khác có thể ghi dữ liệu ma. –

+0

Thực ra, không. Việc truy cập thành công bộ nhớ đã xóa không phải là hành vi mong đợi, đó là hành vi không xác định. Một phân bổ khác có thể dễ dàng ghi đè lên bộ nhớ mà bạn vừa giải phóng. –

+4

@Thomas Matthews Tôi không nói rằng đó là một ý tưởng hay khi thử truy cập nó. @Curt Nichols Được phát bằng từ. Tùy thuộc vào trình biên dịch bạn sử dụng, bạn có thể * mong đợi * rằng bộ nhớ không phải là zeroed ngay lập tức khi gọi xóa. Bạn có thể rõ ràng là không chắc chắn về nó mặc dù. – Martin

1

xóa deallocates bộ nhớ, nhưng không sửa đổi nó hoặc không nó ra. Tuy nhiên, bạn không nên truy cập bộ nhớ deallocated.

9

Đó là những gì C++ gọi hành vi không xác định - bạn có thể truy cập dữ liệu, bạn có thể không. Trong mọi trường hợp, đó là điều sai trái để làm.

2

Hệ thống không xóa bộ nhớ khi bạn giải phóng bộ nhớ qua delete(). Do đó, nội dung vẫn có thể truy cập được cho đến khi bộ nhớ được gán để sử dụng lại và ghi đè.

1

Sau khi xóa một đối tượng, nó không được xác định những gì sẽ xảy ra với nội dung của bộ nhớ mà nó chiếm. Nó có nghĩa là bộ nhớ đó là miễn phí để được tái sử dụng, nhưng việc thực hiện không phải ghi đè lên dữ liệu đã có ban đầu và nó không phải tái sử dụng bộ nhớ ngay lập tức.

Bạn không nên truy cập vào bộ nhớ sau khi đối tượng đã biến mất nhưng không đáng ngạc nhiên là một số dữ liệu vẫn còn nguyên vẹn ở đó.

0

Có, đôi khi nó có thể được mong đợi. Trong khi new giữ chỗ cho dữ liệu, delete chỉ đơn giản là vô hiệu hóa một con trỏ được tạo với new, cho phép dữ liệu được ghi tại các vị trí được bảo lưu trước đó; nó không nhất thiết phải xóa dữ liệu. Tuy nhiên, bạn không nên dựa vào hành vi đó vì dữ liệu tại các vị trí đó có thể thay đổi bất kỳ lúc nào, có thể khiến chương trình của bạn hoạt động không đúng. Đây là lý do tại sao sau khi bạn sử dụng delete trên con trỏ (hoặc delete[] trên mảng được phân bổ với new[]), bạn nên gán NULL cho nó để bạn không thể giả mạo con trỏ không hợp lệ, giả sử bạn sẽ không cấp phát bộ nhớ bằng cách sử dụng new hoặc new[] trước khi sử dụng lại con trỏ đó.

+3

Không có gì trong tiêu chuẩn ngôn ngữ C++ ngăn chặn' xóa' khỏi xóa bộ nhớ đã bị xóa hoặc điền bằng một giá trị lạ. Nó được thực hiện xác định. –

8

Xóa không xóa bất kỳ thứ gì - nó chỉ đánh dấu bộ nhớ là "miễn phí để sử dụng lại". Cho đến khi một số dự trữ cuộc gọi phân bổ khác và lấp đầy không gian đó, nó sẽ có dữ liệu cũ. Tuy nhiên, dựa vào đó là một không-không lớn, về cơ bản nếu bạn xóa một cái gì đó quên nó đi.

Một trong những hoạt động trong lĩnh vực này thường bắt gặp trong các thư viện là một chức năng Delete:

template< class T > void Delete(T*& pointer) 
{ 
    delete pointer; 
    pointer = NULL; 
} 

Điều này ngăn cản chúng ta khỏi vô tình truy cập vào bộ nhớ không hợp lệ.

Lưu ý rằng bạn hoàn toàn có thể gọi số delete NULL;.

+0

Thậm chí nếu bạn không sử dụng macro, thực hành tốt là đặt con trỏ thành NULL ngay sau khi giải phóng nó. Đó là một thói quen tốt để có được, ngăn chặn những loại hiểu lầm này. –

+1

@Kornel Bất kỳ thư viện C++ nào sử dụng macro như vậy sẽ cực kỳ nghi ngờ, IMHO. Tại rất dễ chịu, nó phải là một chức năng mẫu nội tuyến. –

+8

@Mark Thiết lập con trỏ để NULL sau xóa không phải là thực hành tốt phổ quát trong C + +. Có những lúc nó là một điều tốt để làm, và thời gian khi nó là vô nghĩa và có thể che giấu các lỗi. –

1

Nó sẽ dẫn đến hành vi không xác định và xóa bộ nhớ deallocates, nó không khởi tạo lại nó bằng không.

Nếu bạn muốn làm cho nó không ra thì làm:

SingleBlock::~SingleBlock() 

{ x = y = 0 ; } 
3

bộ nhớ Heap là giống như một loạt các bảng đen. Hãy tưởng tượng bạn là một giáo viên. Trong khi bạn đang dạy lớp học của bạn, bảng đen thuộc về bạn, và bạn có thể làm bất cứ điều gì bạn muốn làm với nó. Bạn có thể viết nguệch ngoạc trên đó và ghi đè lên những thứ bạn muốn.

Khi lớp học kết thúc và bạn sắp rời khỏi phòng, không có chính sách yêu cầu bạn xóa bảng đen - bạn chỉ cần đưa bảng đen cho giáo viên tiếp theo, những người thường có thể xem bạn đã viết xuống.

+1

Nếu một trình biên dịch có thể xác định rằng mã chắc chắn sẽ truy cập (thậm chí nhìn vào) một phần của bảng đen nó không sở hữu, việc xác định đó sẽ giải phóng trình biên dịch khỏi luật thời gian và quan hệ nhân quả; một số trình biên dịch khai thác theo những cách mà có thể đã được coi là vô lý một thập kỷ trước (nhiều trong số đó vẫn là vô lý, IMHO). Tôi có thể hiểu rằng nếu hai đoạn mã không phụ thuộc vào nhau, một trình biên dịch có thể xen kẽ quá trình xử lý của chúng theo bất kỳ kiểu nào ngay cả khi UB gây ra "sớm", nhưng một khi UB trở thành không thể tránh khỏi, tất cả các quy tắc bay ra ngoài cửa sổ. – supercat

1

Mặc dù có thể thời gian chạy của bạn không báo cáo lỗi này, sử dụng thời gian chạy kiểm tra lỗi thích hợp như Valgrind sẽ cảnh báo bạn về việc sử dụng bộ nhớ sau khi nó được giải phóng.

Tôi khuyên bạn nếu bạn viết mã với new/delete và con trỏ thô (thay vì std::make_shared() và tương tự), bạn thực hiện các bài kiểm tra đơn vị của mình dưới Valgrind để ít nhất có cơ hội phát hiện lỗi như vậy.

1

Vâng, tôi đã tự hỏi về điều này trong một thời gian là tốt, và tôi đã cố gắng để chạy một số xét nghiệm để hiểu rõ hơn những gì đang xảy ra dưới mui xe. Câu trả lời tiêu chuẩn là sau khi bạn gọi xóa bạn không nên mong đợi bất cứ điều gì tốt từ việc truy cập vào điểm bộ nhớ đó. Tuy nhiên, điều này dường như không đủ với tôi. Điều gì thực sự xảy ra khi gọi số xóa (ptr)? Đây là những gì tôi đã tìm thấy. Tôi đang sử dụng g + + trên Ubuntu 16.04, vì vậy điều này có thể đóng một vai trò trong kết quả.

Điều đầu tiên tôi mong đợi khi sử dụng toán tử xóa là bộ nhớ giải phóng sẽ được chuyển lại cho hệ thống để sử dụng trong các quy trình khác. Hãy để tôi nói điều này không xảy ra trong bất kỳ trường hợp nào tôi đã thử.

Bộ nhớ được giải phóng với xóa dường như vẫn được cấp phát cho chương trình mà trước tiên nó đã phân bổ nó với mới. Tôi đã thử và không giảm mức sử dụng bộ nhớ sau khi gọi xóa. Tôi đã có một phần mềm có tổng số 30MB danh sách thông qua các cuộc gọi mới và sau đó phát hành chúng với xóa cuộc gọi tiếp theo. Điều gì đã xảy ra là, nhìn vào màn hình hệ thống trong khi chương trình đang chạy, ngay cả một giấc ngủ dài sau khi các cuộc gọi xóa, bộ nhớ tiêu thụ chương trình của tôi là như nhau. Không giảm! Điều này có nghĩa là xóa không giải phóng bộ nhớ cho hệ thống.

Thực tế, có vẻ như bộ nhớ được chương trình phân bổ là mãi mãi! Tuy nhiên, điểm là, nếu deallocated, bộ nhớ có thể được sử dụng một lần nữa bởi cùng một chương trình mà không cần phải phân bổ nữa. Tôi đã cố gắng phân bổ 15MB, giải phóng chúng, và sau đó phân bổ 15MB dữ liệu khác sau đó, và chương trình không bao giờ sử dụng 30MB. Hệ thống giám sát luôn luôn cho thấy nó khoảng 15MB. Những gì tôi đã làm, đối với các bài kiểm tra trước đó, chỉ là để thay đổi thứ tự trong đó mọi thứ đã xảy ra: phân bổ một nửa, nửa thỏa thuận, một nửa phân bổ.

Vì vậy, dường như bộ nhớ được chương trình sử dụng có thể tăng nhưng không bao giờ thu nhỏ. Tôi nghĩ rằng có lẽ bộ nhớ sẽ thực sự được phát hành cho các quá trình khác trong các tình huống quan trọng, chẳng hạn như khi không còn bộ nhớ. Sau khi tất cả, những gì cảm giác nó sẽ làm cho một chương trình giữ bộ nhớ riêng của mình mãi mãi, khi các quá trình khác đang yêu cầu nó? Vì vậy, tôi phân bổ 30MB một lần nữa, và trong khi deallocating chúng Tôi chạy một memtester với càng nhiều bộ nhớ vật lý tôi có thể. Tôi hy vọng sẽ thấy phần mềm của tôi đưa bộ nhớ của mình ra cho memtester. Nhưng đoán nó, nó đã không xảy ra!

Tôi đã thực hiện lên một screencast ngắn cho thấy các điều trong hành động:

Delete example memory

Để trở thành 100% trung thực, có một tình huống trong đó một cái gì đó xảy ra. Khi tôi thử memtester với nhiều hơn bộ nhớ vật lý có sẵn ở giữa quá trình deallocation của chương trình của tôi, bộ nhớ được sử dụng bởi chương trình của tôi giảm xuống còn khoảng 3MB. Tuy nhiên, quá trình memtester đã bị giết tự động, và những gì đã xảy ra thậm chí còn đáng ngạc nhiên hơn! Việc sử dụng bộ nhớ của chương trình của tôi tăng lên với mỗi cuộc gọi xóa! Nó giống như là Ubuntu đã khôi phục lại tất cả bộ nhớ của nó sau sự kiện memtester.

Taken từ http://www.thecrowned.org/c-delete-operator-really-frees-memory

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