2008-12-12 39 views
8

Trong Delphi7 tôi mã nàyĐiều gì sẽ xảy ra khi sử dụng một đối tượng sau FreeAndNil?

var MStr: TMemoryStream; 
... 
FreeAndNil(MStr); 
MStr.Size:=0; 

tạo ra một AV: Truy cập vi phạm tại địa chỉ 0041D6D1 trong module 'Project1.exe'. Đọc địa chỉ 00000000. Nhưng ai đó khăng khăng rằng không nên đưa ra bất kỳ ngoại lệ nào, bất kể là gì. Ông cũng nói rằng Delphi 5 của ông thực sự không có ngoại lệ. Anh gọi đây là "lỗi con trỏ cũ". Nói cách khác, ông nói rằng FreeAndNil không thể được sử dụng như trình gỡ lỗi để phát hiện một nỗ lực gấp đôi để giải phóng một đối tượng hoặc sử dụng một đối tượng giải phóng.

Ai có thể khai sáng cho tôi không? Điều này có nên tăng và lỗi (luôn luôn/ngẫu nhiên) hoặc chương trình sẽ chạy trên lỗi này mà không có vấn đề gì?

Cảm ơn


Tôi yêu cầu này bởi vì tôi tin rằng tôi có một "đối tượng miễn phí kép" hay "truy cập lại tự do và" lỗi trong chương trình của tôi. Làm thế nào tôi có thể điền vào bộ nhớ được phân bổ cho một đối tượng với số không SAU KHI tôi giải phóng đối tượng? Tôi muốn theo cách này để phát hiện lỗi, bằng cách nhận và AV. Ban đầu, tôi hy vọng rằng nếu tôi đặt đối tượng thành FreeAndNil, tôi sẽ luôn nhận được AV khi cố gắng truy cập lại nó.

+2

Tôi nghĩ rằng 'ai đó' là mất tích một số khái niệm quan trọng. –

+1

Như tôi đã nói trước đó, FastMM có một tùy chọn thực hiện những gì bạn mô tả. Rob chỉ ra rằng nó sẽ ghi đè lên bộ nhớ với 'số ma thuật' để nó có thể phát hiện loại lỗi này. Hãy nhìn vào nó. Tải xuống toàn bộ FastMM4 và bật ghi nhật ký vào tệp. Tôi đã tìm thấy nó khá hữu ích. – Vegar

+2

FreeAndNil() không nil ra bộ nhớ bị chiếm đóng bởi các trường hợp, nó nils tham chiếu thông qua. – Bevan

Trả lời

9

Từ những gì tôi thấy, mã này sẽ luôn dẫn đến lỗi. FreeAndNil đặt rõ ràng rằng giá trị đã chuyển đến Nil (aka 0), vì vậy bạn hoàn toàn nên nhận được một sự vi phạm truy cập khi cố gắng dereference đối tượng.

21

Việc sử dụng phương pháp hoặc thuộc tính của tham chiếu null luôn là sai, ngay cả khi nó dường như đôi khi hoạt động.

FreeAndNil thực sự không thể được sử dụng để phát hiện các giải thoát kép. An toàn để gọi số FreeAndNil trên biến đã có. Vì nó an toàn, nó không giúp bạn phát hiện bất cứ điều gì.

Đây không phải là lỗi con trỏ cũ. Đây là một lỗi tham chiếu null. Lỗi con trỏ cũ là khi bạn giải phóng một đối tượng nhưng không phải đã xóa tất cả các biến tham chiếu đến nó. Sau đó biến vẫn giữ địa chỉ cũ của đối tượng. Đó là những thứ rất khó phát hiện. Bạn có thể nhận được một lỗi như thế này:

MStr := TMemoryStream.Create; 
MStr.Free; 
MStr.Size := 0; 

Bạn cũng có thể có được một như thế này:

MStr := TMemoryStream.Create; 
OtherStr := MStr; 
FreeAndNil(MStr); 
OtherStr.Size := 0; 

Sử dụng MStr.Size sau khi bạn đã giải phóng đối tượng MStr tham chiếu là một lỗi, và nó sẽ nâng cao một ngoại lệ. Cho dù tăng ngoại lệ hay không phụ thuộc vào việc triển khai. Có lẽ nó sẽ, và có lẽ nó sẽ không. Nó không phải ngẫu nhiên, mặc dù.

Nếu bạn đang tìm kiếm lỗi kép miễn phí, bạn có thể sử dụng trình gỡ lỗi mà FastMM cung cấp, như những người khác đã đề xuất. Nó hoạt động bằng cách không thực sự giải phóng bộ nhớ trở lại hệ điều hành, hoặc thậm chí trở lại hồ bơi bộ nhớ trong của Delphi. Thay vào đó, nó ghi dữ liệu đã biết xấu vào không gian bộ nhớ của đối tượng, vì vậy khi bạn nhìn thấy các giá trị đó, bạn sẽ biết bạn đang đọc từ một cái gì đó mà bạn đã giải phóng. Nó cũng sửa đổi VMT của đối tượng để lần sau bạn gọi một phương thức ảo trên tham chiếu đối tượng đó, bạn sẽ nhận được một ngoại lệ có thể dự đoán được, và nó thậm chí sẽ cho bạn biết đối tượng được giải phóng nào mà bạn đã cố gắng sử dụng. Khi bạn cố gắng giải phóng đối tượng một lần nữa, nó có thể cho bạn biết không chỉ rằng bạn đã giải phóng nó, mà còn là nơi nó được giải phóng lần đầu tiên (với một dấu vết ngăn xếp), và nơi nó được cấp phát.Nó cũng thu thập thông tin đó để báo cáo về rò rỉ bộ nhớ, trong đó bạn giải phóng một đối tượng ít hơn thay vì một lần thay vì nhiều hơn.

Ngoài ra còn có những thói quen bạn có thể sử dụng để tránh vấn đề này đối với mã tương lai:

  • Giảm việc sử dụng các biến toàn cục. Biến toàn cầu có thể được sửa đổi bởi bất kỳ mã nào trong suốt chương trình, buộc bạn phải băn khoăn bất cứ khi nào bạn sử dụng nó, "Giá trị của biến này vẫn hợp lệ hay đã có một số mã khác miễn phí?" Khi bạn giới hạn phạm vi của một biến, bạn giảm số lượng mã bạn phải xem xét trong chương trình của mình khi tìm kiếm lý do biến không có giá trị bạn mong đợi.
  • Hãy rõ ràng về người sở hữu một đối tượng. Khi có hai đoạn mã có quyền truy cập vào cùng một đối tượng, bạn cần biết mã nào trong số các mã đó là sở hữu đối tượng. Họ có thể có một biến khác nhau để tham chiếu đối tượng, nhưng vẫn chỉ có một đối tượng ở đó. Nếu một đoạn mã gọi FreeAndNil trên biến của nó, biến đó vẫn không thay đổi. Nếu mã khác nghĩ rằng nó sở hữu đối tượng, thì bạn đang gặp rắc rối. (Khái niệm về chủ sở hữu này không nhất thiết phải gắn với thuộc tính TComponent.Owner. Không cần phải là đối tượng sở hữu nó; nó có thể là một hệ thống con chung của chương trình của bạn.)
  • Không liên tục tham chiếu đến các đối tượng bạn không sở hữu. Nếu bạn không giữ các tham chiếu lâu dài cho một đối tượng, thì bạn không phải lo lắng về việc liệu các tham chiếu đó có còn hiệu lực hay không. Tham chiếu liên tục duy nhất phải ở trong mã sở hữu đối tượng. Bất kỳ mã nào khác cần sử dụng đối tượng đó sẽ nhận được tham chiếu dưới dạng thông số đầu vào, sử dụng đối tượng và sau đó loại bỏ tham chiếu khi nó trả về kết quả của nó.
5

Nếu bạn đặt con trỏ thành 0, bạn sẽ không thể sử dụng nó nữa. Nhưng nếu bạn có một con trỏ khác đến cùng một đối tượng, bạn có thể sử dụng nó mà không nhận được một AV, bởi vì con trỏ này vẫn trỏ đến địa chỉ đối tượng và không phải là không.

Hơn nữa, giải phóng một đối tượng không xóa bộ nhớ được sử dụng bởi đối tượng đó. Nó chỉ đánh dấu nó là không sử dụng. Đó là lý do bạn muốn có AV. Nếu bộ nhớ giải phóng được cấp phát cho một đối tượng khác, bạn sẽ nhận được một AV, bởi vì nó không còn chứa dữ liệu có vẻ hợp lệ.

FastMM4 có một số cài đặt mà bạn có thể sử dụng trong khi gỡ lỗi, điều đó sẽ phát hiện các điều kiện như vậy. Từ FsatMM4Options.inc:

{Đặt tùy chọn sau đây để kiểm tra rộng rãi tất cả các khối bộ nhớ. Tất cả các khối được đệm với cả đầu trang và đoạn giới thiệu được sử dụng để xác minh tính toàn vẹn của một vùng heap. Khối giải phóng cũng được xóa để đảm bảo rằng chúng không thể sử dụng lại sau khi được giải thoát. Tùy chọn này làm chậm hoạt động bộ nhớ một cách đáng kể và chỉ nên được sử dụng để gỡ lỗi ứng dụng là ghi đè bộ nhớ hoặc sử dụng lại con trỏ được giải phóng. Đặt tùy chọn này sẽ tự động bật CheckHeapForCorruption và vô hiệu hóa ASMVersion. Rất quan trọng: Nếu bạn bật tùy chọn này, ứng dụng của bạn sẽ yêu cầu thư viện FastMM_FullDebugMode.dll. Nếu thư viện này không khả dụng, bạn sẽ gặp lỗi khi khởi động.}
{$ xác định FullDebugMode}

Một quote so với cùng file:

FastMM luôn bắt nỗ lực để giải phóng khối bộ nhớ cùng một hai lần ...

sử dụng Như delphi FastMM từ Delphi 2007 (2006?), Bạn sẽ gặp lỗi nếu bạn cố gắng tăng gấp đôi một đối tượng.

+0

Free và nil cũng giải phóng đối tượng để một con trỏ khác đến nó cũng sẽ thất bại. –

+0

Điều đó có đúng với các phiên bản delphi cũ và trình quản lý bộ nhớ cổ điển hay chỉ cho Delphi 2007+ với FastMM? Điều gì sẽ là mục đích cho bình luận trong FastMM4Options.inc nếu những gì bạn nói là sự thật? – Vegar

+0

Gamecat, rất có thể việc tái sử dụng con trỏ cũ (không phải là con số) sẽ không gây ra bất kỳ ngoại lệ nào (và có thể nó có thể hoạt động tốt.) Tất nhiên, đó là một điều xấu, và tốt hơn hàng ngàn lần nếu nó không thành công , nhưng ít nhất trong Delphi 5 và 7 bạn có thể tái sử dụng con trỏ cũ. –

8

Chỉ cần làm phức tạp vấn đề:

Nếu phương pháp mà bạn gọi là tĩnh phương pháp (không ảo) và nó không gọi bất kỳ phương pháp ảo riêng của mình và cũng không truy cập vào bất kỳ lĩnh vực của đối tượng, bạn sẽ không nhận vi phạm truy cập ngay cả khi tham chiếu đối tượng đã được đặt thành NIL.

Lý do cho điều này là do vi phạm quyền truy cập gây ra bởi dereferencing con trỏ tự (trong trường hợp này là NIL), nhưng điều đó chỉ xảy ra khi truy cập một trường hoặc VMT của đối tượng để gọi một phương thức ảo.

Đây chỉ là một ngoại lệ đối với quy tắc mà bạn không thể gọi các phương thức của tham chiếu đối tượng NIL mà tôi muốn đề cập ở đây.

1

Thomas Mueller: bạn đã thử phương pháp lớp ảo chưa? Một hàm tạo là một loại phương thức ảo nhưng bạn gọi nó là kiểu đối nghịch - không phải là cá thể. Điều này có nghĩa là ngay cả một số phương pháp ảo cụ thể sẽ không gây ra AV trên tham chiếu null: D

Vegar: Bạn không thể đúng hơn! FastMM là công cụ tốt nhất từ ​​trước tới giờ đã giúp tôi theo dõi loại lỗi này.

+0

Gọi một phương thức lớp ảo đối với một biến mẫu sẽ không thành công. Các nhà xây dựng không có gì để làm với nó. –

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