Phát hiện sự nhầm lẫn của xóa [] so với xóa tại thời gian biên dịch


19

Tôi muốn biết liệu có thể phát hiện deletelỗi được nhận xét bên dưới tại thời điểm biên dịch không? Đặc biệt, tôi muốn nghe về trình biên dịch g ++.

ClassTypeA *abc_ptr = new ClassTypeA[100];  
abc_ptr[10].data_ = 1;  
delete abc_ptr; // error, should be delete []  

7
Bạn không nên gọi xóa bằng tay nào.
Martin York

9
@LokiAstari Bạn có thực sự nghĩ rằng nhận xét đó là hữu ích?
James

5
@James: Vâng. Chìa khóa là "thủ công".
Martin York

Đôi khi để tuân thủ điều này sẽ liên quan đến việc viết lại rất nhiều mã kế thừa
Nick Keighley

Sử dụng std::unique_ptr<ClassTypeA[]>và sau đó bạn không cần.
dùng253751

Câu trả lời:


6

Nói chung, trình biên dịch không thể phát hiện các lỗi như vậy. Ví dụ: Giả sử hàm tạo cho một số lớp phân bổ một số thành viên dữ liệu bằng cách sử dụng new TypeName[], nhưng hàm hủy sử dụng sai deletethay vì delete[]. Nếu hàm tạo và hàm hủy được định nghĩa trong các đơn vị biên dịch riêng biệt, làm thế nào trình biên dịch biết khi biên dịch tệp xác định hàm hủy mà việc sử dụng không phù hợp với tệp được biên dịch riêng xác định hàm tạo?

Đối với các trình biên dịch GNU, thì không. Như đã lưu ý ở trên, nó không thể làm như vậy trong trường hợp chung. Trình biên dịch không phải phát hiện các lỗi mới / xóa không khớp như vậy vì đây là hành vi không xác định. UB là thẻ "thoát khỏi tù miễn phí" của nhà cung cấp trình biên dịch.

Các công cụ như valgrind có thể và phát hiện các loại không phù hợp mới / xóa này, nhưng làm như vậy trong thời gian chạy. Có thể có một công cụ phân tích tĩnh xem xét tất cả các tệp nguồn cuối cùng sẽ được biên dịch để tạo thành một tệp thực thi, nhưng tôi không sử dụng bất kỳ công cụ phân tích tĩnh nào phát hiện loại lỗi này.


Tôi đã sử dụng một công cụ phân tích tĩnh có tên Parasoft , chắc chắn có một quy tắc cho kịch bản cụ thể này. Nó chạy trên tất cả các tệp trong một dự án cụ thể (nếu nó được cấu hình đúng). Điều đó đang được nói, tôi không chắc nó xử lý tốt các tình huống như nhận xét của Pete Kirkham về câu trả lời của Kilian Foth.
Velociraptors

28

Bạn có thể sử dụng các lớp RAII thích hợp để delete. Đây là cách an toàn duy nhất để thực hiện và lỗi này chỉ là một trong số rất, rất nhiều bạn sẽ gặp phải khi deletetự gọi mình.

Luôn sử dụng các lớp để quản lý tài nguyên trọn đời động và hệ thống loại sẽ thực thi hủy tài nguyên chính xác.

Chỉnh sửa: "Điều gì xảy ra nếu bạn đang kiểm tra mã và không thể thay đổi mã?" Bạn đang đụ.


18
-1 bởi vì điều này không thực sự trả lời câu hỏi.
Mason Wheeler

2
Cách duy nhất để phát hiện sự không phù hợp là sử dụng hệ thống loại, bao gồm sử dụng các lớp RAII.
DeadMG

9
... Điều đó thậm chí còn ít ý nghĩa hơn. Việc sử dụng các lớp RAII - một cơ chế thời gian chạy - có liên quan gì đến thông tin hệ thống kiểu tĩnh mà trình biên dịch biết về thời gian biên dịch?
Mason Wheeler

6
@MasonWheeler xem boost :: shared_ptr và boost :: shared_array làm ví dụ. Phá hủy shared_ptr sẽ xóa đối tượng, phá hủy shared_array xóa [] s mảng. Bạn không thể gán shared_array cho shared_ptr, vì vậy - miễn là bạn không xây dựng shared_ptr với một mảng ở vị trí đầu tiên - hệ thống loại ngăn chặn sử dụng xóa sai.
Pete Kirkham

4
Thông thường, một câu trả lời như thế này đáng ghét hơn là hữu ích. Tuy nhiên, trong trường hợp này, nó thực sự đúng. Anh ta đang tìm kiếm trình biên dịch thực thi một lỗi phổ biến và sử dụng RAII đúng cách sẽ ngăn chặn kiểu lỗi này, từ đó cho anh ta chính xác những gì anh ta muốn. +1
riwalk

10

Lỗi đặc biệt này - có. Đây là loại lỗi nói chung: không may, không! Điều đó sẽ liên quan đến việc dự đoán luồng thực thi mà không thực sự thực hiện nó, và điều đó là không thể đối với các chương trình tùy ý. (Đó là lý do tại sao hầu hết các trình biên dịch thậm chí không cố gắng phát hiện các trường hợp đơn giản như ví dụ của bạn.)

Do đó, câu trả lời của DeadMG là câu trả lời thích hợp: đừng cố làm cho đúng bằng cách chú ý - sự chú ý của con người là có thể sai lầm. Sử dụng các phương tiện được cung cấp bằng ngôn ngữ và để máy tính chú ý.


Làm thế nào điều này đòi hỏi phải dự đoán dòng thực hiện? Điều này trông giống như hoàn toàn tĩnh, kiến ​​thức thời gian biên dịch với tôi; hệ thống loại của trình biên dịch biết một mảng và những gì không.
Mason Wheeler

Ngay cả trong sự hiện diện của phôi? Xin lỗi, nếu tôi hiểu sai tôi sẽ xóa câu trả lời.
Kilian Foth

12
@MasonWheeler loại abc_ptr tĩnh là ClassTypeA*do đó bạn có thể chèn một dòng giữa mới và xóa if ( rand() % 2 == 1 ) abc_ptr = new ClassTypeA;Không có gì trong hệ thống loại tĩnh đang hiển thị cho dù abc_ptrtrỏ đến một mảng hoặc một đối tượng động hoặc một phần vào một đối tượng hoặc mảng khác.
Pete Kirkham

...ô đung rôi. Tôi đã quá quen với việc làm việc với các ngôn ngữ với các kiểu mảng thực sự đến nỗi tôi quên mất rằng nó đã bị làm hỏng như thế nào trên đất C. :(
Mason Wheeler

1
@Pete Kirkham, @Mason Wheeler: Tuy nhiên, thời gian chạy sẽ thấy có bao nhiêu đối tượng được lưu trữ tại địa chỉ được chỉ bởi abc_ptr, nếu không làm thế nào nó có thể phân bổ đúng dung lượng bộ nhớ? Vì vậy, thời gian chạy biết có bao nhiêu đối tượng phải được giải quyết.
Giorgio

4

Trường hợp tầm thường mà bạn hiển thị có thể được phát hiện tại thời điểm biên dịch, bởi vì việc khởi tạo và phá hủy đối tượng nằm trong cùng một phạm vi. Nói chung, việc xóa không có trong cùng một phạm vi, hoặc thậm chí cùng một tệp nguồn, như là khởi tạo. Và kiểu con trỏ C ++ không mang thông tin về việc nó tham chiếu một đối tượng duy nhất thuộc loại đó hay mảng, chứ chưa nói đến sơ đồ phân bổ. Vì vậy, không thể chẩn đoán điều này tại thời gian biên dịch nói chung.

Tại sao không chẩn đoán các trường hợp đặc biệt có thể?

Trong C ++, đã có các công cụ xử lý rò rỉ tài nguyên động được gắn với phạm vi, cụ thể là con trỏ thông minh và mảng cấp cao hơn ( std::vector).

Ngay cả khi bạn sử dụng đúng deletehương vị, mã của bạn vẫn không ngoại lệ an toàn. Nếu mã giữa new[]delete[]kết thúc bằng một lối thoát động, việc xóa không bao giờ thực hiện.

Theo như phát hiện trong thời gian chạy, Valgrindcông cụ thực hiện tốt công việc phát hiện điều này vào thời gian chạy. Đồng hồ đeo tay:

==26781== Command: ./a.out
==26781==
==26781== Mismatched free() / delete / delete []
==26781==    at 0x402ACFC: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048498: main (in /home/kaz/test/a.out)
==26781==  Address 0x4324028 is 0 bytes inside a block of size 80 alloc'd
==26781==    at 0x402B454: operator new[](unsigned int) (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==26781==    by 0x8048488: main (in /home/kaz/test/a.out)

Tất nhiên, Valgrind không chạy trên tất cả các nền tảng và không phải lúc nào cũng thực tế hoặc có thể tái tạo tất cả các tình huống trong thời gian chạy theo công cụ.


bạn nói rằng trường hợp tầm thường này có thể được phát hiện tại thời điểm biên dịch. Bạn có thể vui lòng cho tôi biết lệnh biên dịch nào bạn sử dụng để đạt được điều đó không?
SebGR

"Có thể được phát hiện tại thời gian biên dịch" ở đây có nghĩa là nó dễ thực hiện trong trình biên dịch, không phải g ++ có nó. Một trình biên dịch có toàn bộ thời gian tồn tại của mã định danh khi xử lý phạm vi đó và có thể truyền thông tin phân bổ dưới dạng thuộc tính ngữ nghĩa gắn với cú pháp.
Kaz

-3

Một số ví dụ tầm thường về phát hiện tại thời gian biên dịch / phân tích tĩnh-thời gian:

Trên máy chủ RHEL7 với cppcheck 1.77 and 1.49

> cat test.cc
#include <memory>
int main(){char* buf = new char[10];delete buf;}

http://cppcheck.sourceforge.net/

> cppcheck -x c++ test.cc
Checking test.cc ...
[test.cc:2]: (error) Mismatching allocation and deallocation: buf

Với clang++ 3.7.1trên RHEL7

> clang++ --analyze -x c++ test.cc
test.cc:2:37: warning: Memory allocated by 'new[]' should be deallocated by
'delete[]', not 'delete'
int main(){char* buf = new char[10];delete buf;}
                                    ^~~~~~~~~~
1 warning generated.

Máy phân tích tĩnh Clang cũng có thể phát hiện khi std::unique_ptrkhông được thông qua<char[]>

> cat test2.cc
#include <memory>
int main(){std::unique_ptr<char> buf(new char[10]);}

https://clang-analyzer.llvm.org/

> clang++ --analyze -x c++ -std=c++11 test2.cc
In file included from test2.cc:1:
In file included from /opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/memory:81:
/opt/rh/devtoolset-4/root/usr/lib/gcc/x86_64-redhat-linux/5.3.1/
../../../../include/c++/5.3.1/bits/unique_ptr.h:76:2: 
warning: Memory allocated by
      'new[]' should be deallocated by 'delete[]', not 'delete'
        delete __ptr;
        ^~~~~~~~~~~~
1 warning generated.

Cập nhật bên dưới với một liên kết đến công việc đã thêm điều này vào tiếng kêu, các bài kiểm tra và một lỗi tôi tìm thấy.

Điều này đã được thêm vào clang với review.llvm.org/D4661 - "Phát hiện sự không khớp 'mới' và 'xóa' sử dụng" .

Các thử nghiệm đang trong thử nghiệm / Phân tích / Không phù hợpDeallocator-checker-test.mm

Tôi đã tìm thấy lỗi mở này - bug.llvm.org/show_orms.cgi?id=24819


Không ai nghi ngờ rằng bạn có thể tìm thấy một bộ phân tích tĩnh phát hiện một cách sử dụng sai cụ thể , thay vào đó một phát hiện tất cả các cách sử dụng sai (và hy vọng rằng không có lỗi sử dụng đúng )
Caleth
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.