Visual Studio làm gì với một con trỏ bị xóa và tại sao?


130

Một cuốn sách C ++ tôi đã đọc được rằng khi một con trỏ bị xóa bằng deletetoán tử, bộ nhớ tại vị trí mà nó đang trỏ đến là "được giải phóng" và nó có thể bị ghi đè. Nó cũng nói rằng con trỏ sẽ tiếp tục trỏ đến cùng một vị trí cho đến khi nó được gán lại hoặc được đặt thành NULL.

Trong Visual Studio 2012 tuy nhiên; Điều này dường như không phải là trường hợp!

Thí dụ:

#include <iostream>

using namespace std;

int main()
{
    int* ptr = new int;
    cout << "ptr = " << ptr << endl;
    delete ptr;
    cout << "ptr = " << ptr << endl;

    system("pause");

    return 0;
}

Khi tôi biên dịch và chạy chương trình này, tôi nhận được kết quả sau:

ptr = 0050BC10
ptr = 00008123
Press any key to continue....

Rõ ràng địa chỉ mà con trỏ đang trỏ đến thay đổi khi xóa được gọi!

Tại sao chuyện này đang xảy ra? Điều này có liên quan gì đến Visual Studio không?

Và nếu xóa có thể thay đổi địa chỉ mà nó đang trỏ đến dù sao, tại sao xóa sẽ tự động đặt con trỏ thành NULLthay vì một số địa chỉ ngẫu nhiên?


4
Xóa một con trỏ, không có nghĩa là nó sẽ được đặt thành NULL, bạn phải quan tâm đến điều đó.
Matt

11
Tôi biết điều đó, nhưng cuốn sách tôi đang đọc đặc biệt nói rằng nó vẫn sẽ chứa cùng một địa chỉ mà nó đã trỏ đến trước khi xóa, nhưng nội dung của địa chỉ đó có thể bị ghi đè.
tjwrona1992

6
@ tjwrona1992, vâng, vì đây là điều thường xảy ra. Cuốn sách chỉ liệt kê kết quả rất có thể, không phải là quy tắc cứng.
SergeyA

5
@ tjwrona1992 Một cuốn sách C ++ tôi đã đọc - và tên của cuốn sách là ...?
PaulMcKenzie

4
@ tjwrona1992: Có thể đáng ngạc nhiên, nhưng đó là tất cả việc sử dụng giá trị con trỏ không hợp lệ là hành vi không xác định, không chỉ là hội nghị. "Kiểm tra xem nó đang trỏ đến" IS bằng cách sử dụng giá trị theo cách không được phép.
Ben Voigt

Câu trả lời:


175

Tôi nhận thấy rằng địa chỉ được lưu trữ ptrluôn bị ghi đè bằng 00008123...

Điều này có vẻ kỳ lạ, vì vậy tôi đã đào một ít và tìm thấy bài đăng trên blog của Microsoft này có một phần thảo luận về "Vệ sinh con trỏ tự động khi xóa các đối tượng C ++".

... kiểm tra cho NULL là một cấu trúc mã phổ biến có nghĩa là kiểm tra NULL hiện có kết hợp với sử dụng NULL làm giá trị khử trùng có thể che giấu một vấn đề an toàn bộ nhớ chính hãng mà nguyên nhân thực sự cần giải quyết.

Vì lý do này, chúng tôi đã chọn 0x8123 làm giá trị khử trùng - từ góc độ hệ điều hành, đây là trang cùng bộ nhớ với địa chỉ 0 (NULL), nhưng vi phạm truy cập tại 0x8123 sẽ tốt hơn cho nhà phát triển vì cần chú ý chi tiết hơn .

Nó không chỉ giải thích những gì Visual Studio làm với con trỏ sau khi bị xóa, nó còn trả lời tại sao họ chọn KHÔNG đặt nó thành NULLtự động!


"Tính năng" này được bật như một phần của cài đặt "kiểm tra SDL". Để bật / tắt, hãy truy cập: DỰ ÁN -> Thuộc tính -> Thuộc tính cấu hình -> C / C ++ -> Chung -> Kiểm tra SDL

Để xác nhận điều này:

Thay đổi cài đặt này và chạy lại cùng một mã tạo ra đầu ra sau:

ptr = 007CBC10
ptr = 007CBC10

"Tính năng" nằm trong dấu ngoặc kép vì trong trường hợp bạn có hai con trỏ đến cùng một vị trí, việc gọi xóa sẽ chỉ vệ sinh MỘT trong số chúng. Một cái khác sẽ được để lại chỉ vào vị trí không hợp lệ ...


CẬP NHẬT:

Sau 5 năm kinh nghiệm lập trình C ++, tôi nhận ra toàn bộ vấn đề này về cơ bản là một điểm cần thiết. Nếu bạn là một lập trình viên C ++ và vẫn đang sử dụng newdeleteđể quản lý các con trỏ thô thay vì sử dụng các con trỏ thông minh (phá vỡ toàn bộ vấn đề này), bạn có thể muốn xem xét một sự thay đổi trong con đường sự nghiệp để trở thành một lập trình viên C. ;)


12
Đó là một phát hiện tốt đẹp. Tôi muốn MS sẽ tài liệu gỡ lỗi tốt hơn như thế này. Ví dụ, thật tuyệt khi biết phiên bản trình biên dịch nào đã bắt đầu thực hiện điều này và tùy chọn nào bật / tắt hành vi.
Michael Burr

5
"Từ góc độ hệ điều hành, đây là trong cùng một trang bộ nhớ với địa chỉ 0" - hả? Không phải kích thước trang chuẩn (bỏ qua các trang lớn) trên x86 vẫn là 4kb cho cả windows và linux? Mặc dù tôi lờ mờ nhớ một cái gì đó về 64kb không gian địa chỉ đầu tiên trên blog của Raymond Chen, nhưng trong thực tế, tôi cũng nhận được kết quả tương tự,
Voo

12
Các cửa sổ @Voo dự trữ RAM trị giá 64kB đầu tiên (và cuối cùng) là không gian chết cho bẫy. 0x8123 rơi vào đó một cách độc đáo
ratchet freak

7
Trên thực tế, điều đó không khuyến khích những thói quen xấu và nó không cho phép bạn bỏ qua việc đặt con trỏ thành NULL - đó là toàn bộ lý do họ đang sử dụng 0x8123thay vì 0. Con trỏ vẫn không hợp lệ, nhưng gây ra một ngoại lệ khi cố gắng hủy đăng ký nó (tốt) và nó không vượt qua kiểm tra NULL (cũng tốt, vì đó là lỗi không làm điều đó). Đâu là nơi dành cho những thói quen xấu? Nó thực sự chỉ là một cái gì đó giúp bạn gỡ lỗi.
Luaan

3
Chà, nó không thể đặt cả hai (tất cả) chúng, vì vậy đây là lựa chọn tốt thứ hai. Nếu bạn không thích nó, chỉ cần tắt kiểm tra SDL - Tôi thấy chúng khá hữu ích, đặc biệt là khi gỡ lỗi mã của người khác.
Luaan

30

Bạn thấy các tác dụng phụ của /sdltùy chọn biên dịch. Được bật theo mặc định cho các dự án VS2015, nó cho phép kiểm tra bảo mật bổ sung ngoài các dự án được cung cấp bởi / gs. Sử dụng Project> Properties> C / C ++> General> SDL kiểm tra cài đặt để thay đổi nó.

Trích dẫn từ bài viết MSDN :

  • Thực hiện vệ sinh con trỏ hạn chế. Trong các biểu thức không liên quan đến các hội nghị và trong các loại không có hàm hủy do người dùng định nghĩa, các tham chiếu con trỏ được đặt thành một địa chỉ không hợp lệ sau khi gọi để xóa. Điều này giúp ngăn chặn việc tái sử dụng các tham chiếu con trỏ cũ.

Hãy nhớ rằng việc đặt các con trỏ bị xóa thành NULL là một cách làm không tốt khi bạn sử dụng MSVC. Nó đánh bại sự giúp đỡ bạn nhận được từ cả tùy chọn Debug Heap và / sdl này, bạn không còn có thể phát hiện các cuộc gọi miễn phí / xóa không hợp lệ trong chương trình của mình.


1
Đã xác nhận. Sau khi tắt tính năng này, con trỏ không còn được chuyển hướng. Cảm ơn đã cung cấp các thiết lập thực tế sửa đổi nó!
tjwrona1992

Hans, nó vẫn được coi là thực hành xấu để đặt con trỏ bị xóa thành NULL trong trường hợp bạn có hai con trỏ trỏ đến cùng một vị trí? Khi bạn deletemột, Visual Studio sẽ để con trỏ thứ hai trỏ đến vị trí ban đầu của nó hiện không hợp lệ.
tjwrona1992

1
Khá rõ ràng với tôi loại phép thuật mà bạn mong đợi sẽ xảy ra bằng cách đặt con trỏ thành NULL. Con trỏ kia không phải vậy nên nó không giải quyết được gì, bạn vẫn cần bộ cấp phát gỡ lỗi để tìm lỗi.
Hans Passant

3
VS không dọn sạch con trỏ. Nó làm hỏng chúng. Vì vậy, chương trình của bạn sẽ sụp đổ khi bạn sử dụng chúng anyway. Bộ cấp phát gỡ lỗi thực hiện nhiều điều tương tự với bộ nhớ heap. Vấn đề lớn với NULL, nó không đủ tham nhũng. Nếu không, một chiến lược chung, google "0xdeadbeef".
Hans Passant

1
Đặt con trỏ thành NULL vẫn tốt hơn nhiều so với việc để nó trỏ đến địa chỉ trước đó không hợp lệ. Cố gắng ghi vào một con trỏ NULL sẽ không làm hỏng bất kỳ dữ liệu nào và có thể sẽ làm hỏng chương trình. Cố gắng sử dụng lại con trỏ tại thời điểm đó thậm chí có thể không làm hỏng chương trình, nó chỉ có thể tạo ra kết quả rất khó lường!
tjwrona1992

19

Nó cũng nói rằng con trỏ sẽ tiếp tục trỏ đến cùng một vị trí cho đến khi nó được gán lại hoặc được đặt thành NULL.

Đó chắc chắn là thông tin sai lệch.

Rõ ràng địa chỉ mà con trỏ đang trỏ đến thay đổi khi xóa được gọi!

Tại sao chuyện này đang xảy ra? Điều này có liên quan gì đến Visual Studio không?

Điều này rõ ràng trong các thông số kỹ thuật ngôn ngữ. ptrkhông hợp lệ sau khi gọi đến delete. Sử dụng ptrsau khi nó đã được deleted là nguyên nhân cho hành vi không xác định. Đừng làm điều đó. Môi trường thời gian chạy là miễn phí để làm bất cứ điều gì nó muốn với ptrsau khi gọi đến delete.

Và nếu xóa có thể thay đổi địa chỉ mà nó đang trỏ đến dù sao, tại sao xóa sẽ tự động đặt con trỏ thành NULL thay vì một số địa chỉ ngẫu nhiên ???

Thay đổi giá trị của con trỏ thành bất kỳ giá trị cũ nào nằm trong đặc tả ngôn ngữ. Theo như thay đổi nó thành NULL, tôi sẽ nói, điều đó sẽ rất tệ. Chương trình sẽ hoạt động theo cách lành mạnh hơn nếu giá trị của con trỏ được đặt thành NULL. Tuy nhiên, điều đó sẽ che giấu vấn đề. Khi chương trình được biên dịch với các cài đặt tối ưu hóa khác nhau hoặc được chuyển sang một môi trường khác, vấn đề có thể sẽ xuất hiện trong thời điểm không thuận lợi nhất.


1
Tôi không tin nó trả lời câu hỏi của OP.
SergeyA

Không đồng ý ngay cả sau khi chỉnh sửa. Đặt nó thành NULL sẽ không che giấu vấn đề - trên thực tế, nó sẽ TIẾP TỤC trong nhiều trường hợp hơn là không có. Có một lý do thực hiện bình thường không làm điều này, và lý do là khác nhau.
SergeyA

4
@SergeyA, hầu hết các triển khai không làm điều đó vì hiệu quả. Tuy nhiên, nếu một triển khai quyết định đặt nó, tốt hơn là đặt nó thành một cái gì đó không phải là NULL. Nó sẽ tiết lộ các vấn đề sớm hơn nếu nó được đặt thành NULL. Nó được đặt thành NULL, gọi deletehai lần trên con trỏ sẽ không gây ra vấn đề. Điều đó chắc chắn là không tốt.
R Sahu

Không, không phải là hiệu quả - ít nhất, nó không phải là mối quan tâm chính.
Sergeya

7
@SergeyA Đặt con trỏ thành giá trị không NULLnhưng chắc chắn bên ngoài không gian địa chỉ của quy trình sẽ phơi bày nhiều trường hợp hơn hai lựa chọn thay thế. Để nó lơ lửng sẽ không nhất thiết gây ra lỗi phân tách nếu nó được sử dụng sau khi được giải phóng; thiết lập nó thành NULLsẽ không gây ra segfault nếu nó deletelại xảy ra.
Blacklight Shining

10
delete ptr;
cout << "ptr = " << ptr << endl;

Nói chung, ngay cả việc đọc (như bạn làm ở trên, lưu ý: giá trị này khác với các giá trị của hội nghị) của các con trỏ không hợp lệ (ví dụ con trỏ trở nên không hợp lệ khi bạn deletethực hiện) là hành vi được xác định. Điều này đã được giới thiệu trong CWG # 1438 . Xem thêm tại đây .

Xin lưu ý rằng trước đó việc đọc các giá trị của con trỏ không hợp lệ là hành vi không xác định, vì vậy những gì bạn có ở trên sẽ là hành vi không xác định, có nghĩa là bất cứ điều gì cũng có thể xảy ra.


3
Cũng có liên quan là trích dẫn từ [basic.stc.dynamic.deallocation]: "Nếu đối số được cung cấp cho hàm thỏa thuận trong thư viện chuẩn là một con trỏ không phải là giá trị con trỏ null, thì hàm thỏa thuận sẽ phân bổ lưu trữ được tham chiếu bởi con trỏ, hiển thị không hợp lệ tất cả các con trỏ tham chiếu đến bất kỳ con trỏ nào một phần của bộ lưu trữ được phân bổ "và quy tắc trong [conv.lval](phần 4.1) cho biết việc đọc (lvalue-> chuyển đổi giá trị) bất kỳ giá trị con trỏ không hợp lệ nào là hành vi được xác định theo thực hiện.
Ben Voigt

Ngay cả UB cũng có thể được triển khai theo một cách cụ thể bởi một nhà cung cấp cụ thể sao cho đáng tin cậy, ít nhất là cho trình biên dịch đó. Nếu Microsoft đã quyết định triển khai tính năng khử trùng con trỏ của họ trước CWG # 1438, thì điều đó sẽ không làm cho tính năng đó trở nên đáng tin cậy hơn, và đặc biệt là "không có gì có thể xảy ra" nếu tính năng đó được bật , bất kể những gì tiêu chuẩn nói.
Kyle Strand

@KyleStrand: Về cơ bản, tôi đã đưa ra định nghĩa về UB ( blog.regehr.org/archives/213 ).
giorgim

1
Đối với hầu hết cộng đồng C ++ trên SO, "bất cứ điều gì cũng có thể xảy ra" được thực hiện hoàn toàn theo nghĩa đen . Tôi nghĩ rằng điều này là vô lý . Tôi hiểu định nghĩa của UB, nhưng tôi cũng hiểu rằng trình biên dịch chỉ là phần mềm được thực thi bởi người thực và nếu những người đó triển khai trình biên dịch sao cho nó hoạt động theo cách nhất định, đó là cách trình biên dịch sẽ hoạt động , bất kể tiêu chuẩn nói gì .
Kyle Strand

1

Tôi tin rằng, bạn đang chạy một số loại chế độ gỡ lỗi và VS đang cố gắng đưa con trỏ của bạn đến một vị trí đã biết, do đó, nỗ lực tiếp tục đăng ký lại nó có thể được theo dõi và báo cáo. Hãy thử biên dịch / chạy cùng một chương trình trong chế độ phát hành.

Con trỏ thường không được thay đổi bên trong deletevì mục đích hiệu quả và để tránh đưa ra một ý tưởng sai lầm về an toàn. Đặt con trỏ xóa thành giá trị được xác định trước sẽ không tốt trong hầu hết các tình huống phức tạp, vì con trỏ bị xóa có khả năng chỉ là một trong số nhiều điểm trỏ đến vị trí này.

Như một vấn đề thực tế, tôi càng nghĩ về nó, tôi càng thấy rằng VS có lỗi khi làm như vậy, như thường lệ. Nếu con trỏ là const thì sao? Nó vẫn sẽ thay đổi nó?


Yup, thậm chí con trỏ liên tục được chuyển hướng đến 8123 bí ẩn này!
tjwrona1992

Có một viên đá khác cho VS :) Mới sáng nay có người hỏi tại sao họ nên sử dụng g ++ thay vì VS. Nó đi từ đây.
SergeyA

7
@SergeyA nhưng từ phía bên kia cho rằng con trỏ đã xóa sẽ hiển thị cho bạn bằng segfault mà bạn đã cố gắng hủy bỏ một con trỏ bị xóa nó sẽ không bằng NULL. Trong trường hợp khác, nó sẽ chỉ bị sập nếu trang cũng được giải phóng (điều này rất khó xảy ra). Thất bại nhanh hơn; giải quyết sớm hơn.
ratchet freak

1
@ratchetfreak "Thất bại nhanh, giải quyết sớm hơn" là một câu thần chú rất có giá trị, nhưng "Thất bại nhanh bằng cách phá hủy bằng chứng pháp y quan trọng" không bắt đầu một câu thần chú có giá trị như vậy. Trong những trường hợp đơn giản, nó có thể thuận tiện, nhưng trong những trường hợp phức tạp hơn (những trường hợp chúng ta có xu hướng cần sự giúp đỡ nhất), việc xóa thông tin có giá trị làm giảm các công cụ của tôi có sẵn để giải quyết vấn đề.
Cort Ammon

2
@ tjwrona1992: Theo tôi, Microsoft đang làm điều đúng đắn ở đây. Vệ sinh một con trỏ tốt hơn là không làm gì cả. Và nếu điều này gây ra cho bạn một vấn đề trong việc gỡ lỗi, hãy đặt một điểm dừng trước cuộc gọi xóa xấu. Điều lạ lùng là nếu không có thứ gì đó như thế này thì bạn sẽ không bao giờ phát hiện ra vấn đề. Và nếu bạn có một giải pháp tốt hơn để xác định các lỗi này, thì hãy sử dụng nó và tại sao bạn quan tâm Microsoft làm gì?
Zan Lynx

0

Sau khi xóa con trỏ, bộ nhớ mà nó trỏ tới vẫn có thể hợp lệ. Để hiển thị lỗi này, giá trị con trỏ được đặt thành giá trị rõ ràng. Điều này thực sự giúp quá trình gỡ lỗi. Nếu giá trị được đặt thành NULL, nó có thể không bao giờ xuất hiện dưới dạng lỗi tiềm ẩn trong luồng chương trình. Vì vậy, nó có thể ẩn một lỗi khi bạn kiểm tra sau này NULL.

Một điểm khác là, một số trình tối ưu hóa thời gian chạy có thể kiểm tra giá trị đó và thay đổi kết quả của nó.

Trong thời gian trước đó MS đặt giá trị thành 0xcfffffff.

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.