2008-09-15 36 views
16

Có ai biết "kỹ thuật" để khám phá rò rỉ bộ nhớ do con trỏ thông minh gây ra không? Tôi hiện đang làm việc trên một dự án lớn được viết bằng C++ sử dụng nhiều con trỏ thông minh với tính tham chiếu. Rõ ràng chúng tôi có một số rò rỉ bộ nhớ gây ra bởi con trỏ thông minh, mà vẫn được tham chiếu một nơi nào đó trong mã, để bộ nhớ của họ không nhận được free'd. Rất khó để tìm ra dòng mã với tham chiếu "không cần thiết", khiến cho đối tượng tương ứng không được tự do (mặc dù nó không còn sử dụng nữa).Tìm rò rỉ bộ nhớ do con trỏ thông minh gây ra

Tôi tìm thấy một số lời khuyên trên web, đề xuất thu thập các ngăn xếp cuộc gọi của các hoạt động tăng/giảm của bộ đếm tham chiếu. Điều này mang lại cho tôi một gợi ý tốt, đoạn mã nào đã khiến bộ đếm tham chiếu tăng hoặc giảm.

Nhưng những gì tôi cần là một số loại thuật toán nhóm các "ngăn xếp cuộc gọi tăng/giảm" tương ứng lại với nhau. Sau khi loại bỏ các cặp ngăn xếp cuộc gọi này, tôi hy vọng có ít nhất một "tăng ngăn xếp cuộc gọi", hiển thị cho tôi đoạn mã có tham chiếu "không cần thiết", khiến cho đối tượng tương ứng không được giải phóng. Bây giờ nó sẽ không có vấn đề lớn để sửa chữa rò rỉ!

Nhưng có ai có ý tưởng về "thuật toán" có nhóm không?

Phát triển diễn ra theo Windows XP.

(Tôi hy vọng ai đó hiểu, những gì tôi đã cố gắng để giải thích ...)

EDIT: Tôi đang nói về rò rỉ gây ra bởi tham chiếu vòng tròn.

+0

Không chắc chắn tôi hiểu ... bạn có thể tham khảo ngôn ngữ lập trình hoặc nền tảng cụ thể hơn không? Rò rỉ bộ nhớ có thể thay đổi theo cách xử lý của chúng. – zxcv

+0

Con trỏ thông minh thường có nghĩa là C++/STL. –

+0

Có một số sự nhầm lẫn ở đây. Tôi nghĩ rằng nó sẽ có giá trị phân biệt giữa bộ nhớ phân bổ mà không có con trỏ (không nên xảy ra với con trỏ thông minh), rò rỉ gây ra bởi tham chiếu vòng tròn, và các đối tượng có thời gian cuộc sống được mở rộng không cần thiết vì con trỏ được giữ xung quanh. –

Trả lời

15

Lưu ý rằng một nguồn rò rỉ với con trỏ thông minh tài liệu tham khảo-đếm là con trỏ với dependancies tròn. Ví dụ, A có một con trỏ thông minh để B, và B có một con trỏ thông minh để A. Không A cũng không B sẽ bị phá hủy.Bạn sẽ phải tìm, và sau đó phá vỡ các phụ thuộc.

Nếu có thể, hãy sử dụng con trỏ thông minh và sử dụng shared_ptr cho con trỏ được cho là chủ sở hữu dữ liệu và weak_ptr cho con trỏ không được gọi là xóa.

1

Nếu tôi là bạn tôi sẽ mất nhật ký và viết một kịch bản nhanh chóng để làm một cái gì đó như sau (tôi là trong Ruby):

def allocation?(line) 
    # determine if this line is a log line indicating allocation/deallocation 
end 

def unique_stack(line) 
    # return a string that is equal for pairs of allocation/deallocation 
end 

allocations = [] 
file = File.new "the-log.log" 
file.each_line { |line| 
    # custom function to determine if line is an alloc/dealloc 
    if allocation? line 
    # custom function to get unique stack trace where the return value 
    # is the same for a alloc and dealloc 
    allocations[allocations.length] = unique_stack line 
    end 
} 

allocations.sort! 

# go through and remove pairs of allocations that equal, 
# ideally 1 will be remaining.... 
index = 0 

while index < allocations.size - 1 
    if allocations[index] == allocations[index + 1] 
    allocations.delete_at index 
    else 
    index = index + 1 
    end 
end 

allocations.each { |line| 
    puts line 
} 

này về cơ bản đi qua nhật ký và chụp mỗi giao/deallocation và lưu trữ một giá trị duy nhất cho mỗi cặp, sau đó sắp xếp nó và loại bỏ các cặp phù hợp, xem những gì còn lại.

Cập nhật: Xin lỗi vì tất cả các chỉnh sửa trung gian (tôi vô tình đăng tải trước khi tôi đã được thực hiện)

+0

Lưu ý, tôi giả sử bạn đã có nhật ký với dấu vết phân bổ/miễn phí mà bạn nói về câu hỏi của mình, nhưng các câu trả lời khác có thể giúp bạn giải quyết các rò rỉ bộ nhớ nói chung –

6

Cách tôi làm điều đó chỉ đơn giản là: - trên mỗi AddRef() ghi lại cuộc gọi-stack, - phù hợp với phát hành () loại bỏ nó. Bằng cách này ở phần cuối của chương trình tôi còn lại với AddRefs() mà không có maching chí. Không cần phải phù hợp với cặp,

2

Những gì tôi đã làm để giải quyết này là để ghi đè lên malloc/mới & miễn phí/xóa nhà khai thác như vậy mà họ theo dõi trong một cấu trúc dữ liệu càng nhiều càng tốt về các hoạt động của bạn đang thực hiện.

Ví dụ, khi trọng malloc/mới, Bạn có thể tạo một bản ghi địa chỉ của người gọi, số lượng byte yêu cầu, giá trị con trỏ giao trở lại và một ID sequence vì vậy tất cả hồ sơ của bạn có thể được giải mã (Tôi làm không biết nếu bạn đối phó với chủ đề nhưng bạn cần phải đưa vào tài khoản, quá).

Khi viết miễn phí/xóa thường trình, tôi cũng theo dõi địa chỉ của người gọi và thông tin con trỏ.Sau đó, tôi nhìn ngược vào danh sách và cố gắng kết hợp với đối tượng malloc/new bằng cách sử dụng con trỏ làm khóa của tôi. Nếu tôi không tìm thấy nó, hãy giơ cờ đỏ lên.

Nếu bạn có đủ khả năng, bạn có thể nhúng dữ liệu của mình vào ID trình tự để đảm bảo hoàn toàn ai và khi nào cuộc gọi phân bổ được thực hiện. Chìa khóa ở đây là xác định duy nhất mỗi cặp giao dịch nhiều nhất có thể.

Sau đó, bạn sẽ có một thói quen thứ ba hiển thị phân bổ bộ nhớ của bạn/lịch sử deallocation, cùng với các chức năng gọi mỗi giao dịch. (điều này có thể được thực hiện bằng cách phân tích cú pháp bản đồ tượng trưng ra khỏi trình liên kết của bạn). Bạn sẽ biết bao nhiêu bộ nhớ bạn sẽ được phân bổ bất cứ lúc nào và ai đã làm nó.

Nếu bạn không có đủ tài nguyên để thực hiện các giao dịch này (trường hợp điển hình của tôi đối với vi điều khiển 8 bit), bạn có thể xuất thông tin tương tự qua nối tiếp nối tiếp hoặc TCP đến máy khác có đủ tài nguyên.

2

Vì bạn nói rằng bạn đang sử dụng Windows, bạn có thể tận dụng lợi thế của người sử dụng chế độ tiện ích đống đổ của Microsoft, UMDH, mà đi kèm với Debugging Tools for Windows. UMDH tạo các ảnh chụp nhanh về mức sử dụng bộ nhớ của ứng dụng, ghi lại ngăn xếp được sử dụng cho mỗi phân bổ và cho phép bạn so sánh nhiều ảnh chụp nhanh để xem các cuộc gọi nào đến bộ nhớ "bị rò rỉ" cấp phát. Nó cũng dịch các dấu vết ngăn xếp để biểu tượng cho bạn bằng cách sử dụng dbghelp.dll.

Ngoài ra còn có một công cụ khác của Microsoft có tên là "LeakDiag" hỗ trợ nhiều bộ cấp phát bộ nhớ hơn UMDH, nhưng khó tìm thấy và dường như không được duy trì tích cực. Phiên bản mới nhất là ít nhất năm tuổi, nếu tôi nhớ chính xác.

2

Việc tìm kiếm rò rỉ không phải là vấn đề. Trong trường hợp con trỏ thông minh, nó có lẽ sẽ trực tiếp đến một số vị trí chung chung như CreateObject(), được gọi là hàng ngàn thời gian. Đó là vấn đề xác định vị trí nào trong mã không gọi là Release() trên đối tượng được tính lại.

1

Tôi là một fan hâm mộ lớn của Google's Heapchecker - nó sẽ không bắt tất cả các rò rỉ, nhưng nó được hầu hết trong số họ. (Mẹo: Liên kết nó vào tất cả các khoản chưa thanh toán của bạn.)

4

Nếu bạn có thể tái tạo rò rỉ theo cách xác định, một kỹ thuật đơn giản mà bạn thường sử dụng là đánh số tất cả các con trỏ thông minh của bạn. truy cập trong hàm tạo) và báo cáo ID này cùng với sự rò rỉ. Sau đó chạy chương trình một lần nữa, và kích hoạt một DebugBreak() khi con trỏ thông minh với cùng một ID được xây dựng.

Bạn cũng nên xem xét công cụ tuyệt vời này: http://www.codeproject.com/KB/applications/visualleakdetector.aspx

+0

Bạn có thể làm điều này cùng với ngăn xếp ngăn xếp. Điều này sẽ cho phép bạn ghép nối các đầu ra thông qua UID của các ref/derefs – Ray

4

Những gì tôi làm là quấn con trỏ thông minh với một lớp học mà mất CHỨC NĂNGĐƯỜNG DÂY tham số. Tăng số đếm cho hàm và dòng đó mỗi khi hàm tạo được gọi, và giảm số đếm mỗi khi hàm hủy được gọi. sau đó, viết một hàm kết xuất thông tin về hàm/dòng/số đếm. Cho bạn biết nơi mà tất cả các tài liệu tham khảo của bạn được tạo ra

+0

Tôi đã sử dụng kỹ thuật này. Nếu mọi thứ kết thúc từ CreateObj, thì chuyển người gọi của FUNCTION/LINE của CreateObj. (Tất nhiên, làm điều này thông qua các macro. Thậm chí tốt hơn nếu bạn có một chức năng "Ai gọi tôi?") –

+1

@ Krazy/Joe: Bạn có thể xin vui lòng chỉ cho tôi một số ví dụ cho thấy làm thế nào để tìm "Ai gọi tôi?" – anand

+1

Xác định người gọi chỉ là phần đầu tiên của stacktrace, vì vậy http://stackoverflow.com/questions/77005/how-to-generate-a-stacktrace-when-my-gcc-c-app-crashes cung cấp cho bạn những gì bạn cần. Backtrace()/execinfo.h. –

4

Để phát hiện chu kỳ tham chiếu, bạn cần có biểu đồ của tất cả các đối tượng được tính tham chiếu. Một biểu đồ như vậy không dễ xây dựng, nhưng nó có thể được thực hiện.

Tạo toàn cầu set<CRefCounted*> để đăng ký các đối tượng được tính tham chiếu. Điều này sẽ dễ dàng hơn nếu bạn có AddRef() phổ biến - chỉ cần thêm this con trỏ vào tập khi số tham chiếu của đối tượng đi từ 0 đến 1. Tương tự, trong Release() loại bỏ đối tượng khỏi tập khi số tham chiếu đi từ 1 đến 0.

Tiếp theo, cung cấp một số cách để có được tập các đối tượng được tham chiếu từ mỗi CRefCounted*. Nó có thể là một virtual set<CRefCounted*> CRefCounted::get_children() hoặc bất cứ điều gì phù hợp với bạn. Bây giờ bạn có một cách để đi bộ đồ thị.

Cuối cùng, triển khai thuật toán yêu thích của bạn cho cycle detection in a directed graph. Bắt đầu chương trình, tạo một số chu trình và chạy dò tìm chu kỳ. Thưởng thức! :)

+0

Tôi thích cách suy nghĩ của bạn, nhưng tôi không nghĩ rằng có một cách dễ dàng để lấy đối tượng tham chiếu từ một đối tượng: get_children –

+0

@lzprgmr, Có, 'get_children()' phải được mã hóa cho mỗi lớp tham gia bằng tay với kiến thức về các tham chiếu mà lớp duy trì. Đó là ý tôi là "không dễ xây dựng". – Constantin

0

Bước đầu tiên có thể là biết lớp nào đang bị rò rỉ. Khi bạn biết điều đó, bạn có thể tìm thấy ai đang tăng tham chiếu: 1. đặt điểm ngắt trên hàm tạo của lớp được gói bởi shared_ptr. 2. bước vào với trình gỡ lỗi bên trong shared_ptr khi tăng số tham chiếu: xem biến pn-> pi _-> use_count_ Lấy địa chỉ của biến đó bằng cách đánh giá biểu thức (như sau: & this-> pn-> pi_. use_count_), bạn sẽ nhận được một địa chỉ 3. Trong trình gỡ lỗi studio trực quan, hãy đi tới Debug-> Điểm ngắt mới-> Điểm ngắt dữ liệu mới ... Nhập địa chỉ của biến 4. Chạy chương trình. Chương trình của bạn sẽ ngừng mỗi khi một số điểm trong mã đang tăng lên và giảm bộ đếm tham chiếu. Sau đó, bạn cần phải kiểm tra xem chúng có phù hợp không.

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