Làm thế nào tôi có thể biết phần nào trong mã không bao giờ được sử dụng?


312

Tôi có mã C ++ kế thừa mà tôi phải xóa mã không sử dụng. Vấn đề là cơ sở mã lớn.

Làm thế nào tôi có thể tìm ra mã nào không bao giờ được gọi / không bao giờ được sử dụng?


4
Tôi nghĩ rằng một ngôn ngữ truy vấn mã sẽ cung cấp cho bạn cái nhìn tốt hơn về toàn bộ dự án của bạn. Tôi không chắc chắn về thế giới c ++ nhưng dường như có cppdepend.com (cái này không miễn phí), có vẻ đủ tốt. Có thể là một cái gì đó như thế này có thể có sẵn miễn phí. Một điều khác là, trước khi thực hiện bất kỳ thao tác tái cấu trúc nào, việc cần làm sẽ là kiểm tra đơn vị nếu bạn không có ngay bây giờ. Với các bài kiểm tra đơn vị, những gì bạn có thể làm là yêu cầu các công cụ bảo hiểm mã của bạn lập hồ sơ mã của bạn, điều này sẽ giúp loại bỏ mã chết nếu bạn không thể bao gồm mã đó.
Biswanath

3
Kiểm tra tài liệu tham khảo tại đây: en.wikipedia.org/wiki/Unreachable_code
Martin York

6
Tôi tìm thấy chủ đề tương tự. stackoverflow.com/questions/229069/
Mạnh

3
Yup, một trong những điều thú vị của C ++ là việc loại bỏ các chức năng "không sử dụng" vẫn có thể làm thay đổi kết quả của một chương trình.
MSalters

1
@MSalters: Đó là một điều thú vị ... Trong trường hợp đó, chúng ta sẽ phải nói về chức năng nào trong bộ quá tải được chọn cho một cuộc gọi nhất định, đúng không? Theo hiểu biết của tôi, nếu có cả hai chức năng được đặt tên f()và một cuộc gọi để f()giải quyết rõ ràng đến chức năng thứ nhất, thì không thể thực hiện cuộc gọi đó thành quyết định thứ 2 chỉ bằng cách thêm chức năng thứ 3 có tên f()- "điều tồi tệ nhất bạn có thể làm "Bằng cách thêm chức năng thứ 3 đó là làm cho cuộc gọi trở nên mơ hồ và do đó ngăn chương trình biên dịch. Rất thích (= kinh hoàng) để xem một ví dụ.
j_random_hacker

Câu trả lời:


197

Có hai loại mã không được sử dụng:

  • một hàm cục bộ, nghĩa là, trong một số hàm, một số đường dẫn hoặc biến không được sử dụng (hoặc được sử dụng nhưng không có ý nghĩa, như được viết nhưng không bao giờ được đọc)
  • cái toàn cầu: các hàm không bao giờ được gọi, các đối tượng toàn cầu không bao giờ được truy cập

Đối với loại đầu tiên, một trình biên dịch tốt có thể giúp:

  • -Wunused(GCC, Clang ) nên cảnh báo về các biến không sử dụng, phân tích không sử dụng Clang thậm chí đã được tăng lên để cảnh báo về các biến không bao giờ được đọc (ngay cả khi được sử dụng).
  • -Wunreachable-code(GCC cũ hơn, đã bị xóa trong năm 2010 ) sẽ cảnh báo về các khối cục bộ không bao giờ được truy cập (điều này xảy ra với lợi nhuận sớm hoặc các điều kiện luôn được đánh giá là đúng)
  • không có tùy chọn nào tôi biết để cảnh báo về catchcác khối không sử dụng , vì trình biên dịch nói chung không thể chứng minh rằng sẽ không có ngoại lệ nào được ném ra.

Đối với loại thứ hai, nó khó khăn hơn nhiều. Về mặt tĩnh, nó yêu cầu phân tích toàn bộ chương trình và mặc dù tối ưu hóa thời gian liên kết có thể thực sự loại bỏ mã chết, nhưng trên thực tế, chương trình đã được chuyển đổi rất nhiều vào thời điểm nó được thực hiện đến mức gần như không thể truyền đạt thông tin có ý nghĩa cho người dùng.

Do đó, có hai cách tiếp cận:

  • Lý thuyết là sử dụng máy phân tích tĩnh. Một phần mềm sẽ kiểm tra toàn bộ mã cùng một lúc rất chi tiết và tìm thấy tất cả các đường dẫn dòng chảy. Trong thực tế tôi không biết bất kỳ điều gì sẽ làm việc ở đây.
  • Cách thực dụng là sử dụng một heuristic: sử dụng một công cụ bao phủ mã (trong chuỗi GNU gcov. Lưu ý rằng các cờ cụ thể phải được truyền trong quá trình biên dịch để nó hoạt động chính xác). Bạn chạy công cụ bao phủ mã với một bộ đầu vào đa dạng tốt (kiểm tra đơn vị hoặc kiểm tra không hồi quy), mã chết nhất thiết phải nằm trong mã chưa được xử lý ... và vì vậy bạn có thể bắt đầu từ đây.

Nếu bạn cực kỳ quan tâm đến chủ đề này, và có thời gian và thiên hướng thực sự tự mình tìm ra một công cụ, tôi khuyên bạn nên sử dụng các thư viện Clang để xây dựng một công cụ như vậy.

  1. Sử dụng thư viện Clang để lấy AST (cây cú pháp trừu tượng)
  2. Thực hiện phân tích đánh dấu và quét từ các điểm nhập cảnh trở đi

Vì Clang sẽ phân tích mã cho bạn và thực hiện giải quyết quá tải, bạn sẽ không phải đối phó với các quy tắc ngôn ngữ C ++ và bạn sẽ có thể tập trung vào vấn đề trong tay.

Tuy nhiên loại kỹ thuật này không thể xác định các phần ghi đè ảo không được sử dụng, vì chúng có thể được gọi bằng mã của bên thứ ba mà bạn không thể lý do.


7
Rất đẹp, +1. Tôi thích rằng bạn phân biệt giữa mã có thể được xác định tĩnh để không bao giờ chạy trong bất kỳ trường hợp nào và mã không chạy trong một lần chạy cụ thể, nhưng có khả năng có thể. Cái trước là cái quan trọng tôi nghĩ, và như bạn nói, một phân tích khả năng tiếp cận bằng cách sử dụng AST của toàn bộ chương trình là cách để có được nó. (Ngăn chặn foo()việc được đánh dấu là "được gọi" khi nó chỉ xuất hiện if (0) { foo(); }sẽ là một phần thưởng nhưng yêu cầu thêm thông minh.)
j_random_hacker

@j_random_hacker: có lẽ việc sử dụng CFG (Biểu đồ luồng điều khiển) sẽ tốt hơn khi tôi nghĩ về nó (nhờ vào ví dụ của bạn). Tôi biết rằng Clang rất muốn nhận xét về các so sánh tautological như so sánh bạn đã đề cập và do đó sử dụng CFG, chúng ta có thể sớm phát hiện ra mã chết.
Matthieu M.

@Matthieu: Vâng có lẽ CFG cũng là điều tôi muốn nói, thay vì AST :) Ý tôi là: một sơ đồ trong đó các đỉnh là các hàm và có một cạnh từ hàm x đến hàm y bất cứ khi nào x có thể gọi y. (Và với tính chất quan trọng mà các chức năng quá tải đều được đại diện bởi các đỉnh khác nhau - âm thanh như Clang nào đó cho bạn, phew!)
j_random_hacker

1
@j_random_hacker: thực ra CFG phức tạp hơn một máy đào đơn giản, vì nó đại diện cho tất cả các mã được thực thi trong các khối với các liên kết từ khối này sang khối khác dựa trên các câu lệnh có điều kiện. Ưu điểm chính là nó phù hợp một cách tự nhiên với mã cắt tỉa có thể được xác định là đã chết (nó tạo ra các khối không thể truy cập được có thể xác định được), vì vậy sẽ tốt hơn khi khai thác CFG so với AST để xây dựng sơ đồ nói về ... tôi nghĩ :)
Matthieu M.

1
@j_random_hacker: thật ra AST của Clang, nó làm cho mọi thứ rõ ràng (hoặc gần như ...) vì nó có nghĩa là để làm việc với mã, không chỉ đơn thuần để biên dịch nó. Hiện tại thực sự có một cuộc thảo luận vì rõ ràng có một vấn đề với danh sách trình khởi tạo trong đó một chuyển đổi ngầm định như vậy không xuất hiện trong AST, nhưng tôi đoán nó sẽ được sửa.
Matthieu M.

35

Đối với trường hợp toàn bộ các hàm không được sử dụng (và các biến toàn cục không được sử dụng), GCC thực sự có thể thực hiện hầu hết các công việc cho bạn với điều kiện bạn đang sử dụng GCC và GNU ld.

Khi biên dịch nguồn, sử dụng -ffunction-sections-fdata-sectionssau đó khi liên kết sử dụng -Wl,--gc-sections,--print-gc-sections. Trình liên kết bây giờ sẽ liệt kê tất cả các chức năng có thể bị xóa vì chúng không bao giờ được gọi và tất cả các toàn cầu không bao giờ được tham chiếu.

(Tất nhiên, bạn cũng có thể bỏ qua --print-gc-sectionsphần này và để cho trình liên kết loại bỏ các chức năng một cách im lặng, nhưng giữ chúng trong nguồn.)

Lưu ý: điều này sẽ chỉ tìm thấy các hàm hoàn chỉnh không được sử dụng, nó sẽ không làm gì với mã chết trong các hàm. Các hàm được gọi từ mã chết trong các hàm sống cũng sẽ được giữ xung quanh.

Một số tính năng cụ thể của C ++ cũng sẽ gây ra sự cố, cụ thể là:

  • Chức năng ảo. Không biết lớp con nào tồn tại và lớp nào thực sự được khởi tạo trong thời gian chạy, bạn không thể biết chức năng ảo nào bạn cần tồn tại trong chương trình cuối cùng. Trình liên kết không có đủ thông tin về điều đó nên sẽ phải giữ tất cả chúng xung quanh.
  • Globals với các nhà xây dựng, và các nhà xây dựng của họ. Nói chung, trình liên kết không thể biết rằng hàm tạo cho toàn cầu không có tác dụng phụ, vì vậy nó phải chạy nó. Rõ ràng điều này có nghĩa là bản thân toàn cầu cũng cần phải được giữ.

Trong cả hai trường hợp, mọi thứ được sử dụng bởi hàm ảo hoặc hàm tạo biến toàn cục cũng phải được giữ xung quanh.

Một cảnh báo bổ sung là nếu bạn đang xây dựng thư viện dùng chung, các cài đặt mặc định trong GCC sẽ xuất mọi chức năng trong thư viện dùng chung, khiến nó bị "sử dụng" theo như trình liên kết. Để khắc phục rằng bạn cần đặt mặc định thành ẩn biểu tượng thay vì xuất (sử dụng ví dụ -fvisibility=hidden), sau đó chọn rõ ràng các hàm được xuất mà bạn cần xuất.


Lời khuyên thực tế tuyệt vời. Chỉ cần nhận được một danh sách các chức năng được biết là không được sử dụng ở bất cứ đâu (ngay cả khi, như bạn nói, danh sách này không đầy đủ) sẽ nhận được rất nhiều trái cây treo thấp tôi nghĩ.
j_random_hacker

Tôi không nghĩ rằng bất kỳ điều này hoạt động cho các mẫu không có căn cứ .
Jakub Klinkovský

25

Vâng, nếu bạn sử dụng g ++, bạn có thể sử dụng cờ này -Wunused

Theo tài liệu:

Cảnh báo bất cứ khi nào một biến không được sử dụng ngoài khai báo của nó, bất cứ khi nào một hàm được khai báo tĩnh nhưng không bao giờ được xác định, bất cứ khi nào một nhãn được khai báo nhưng không được sử dụng và bất cứ khi nào một câu lệnh tính toán một kết quả không được sử dụng rõ ràng.

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Chỉnh sửa : Đây là cờ hữu ích khác -Wunreachable-code Theo tài liệu:

Tùy chọn này nhằm cảnh báo khi trình biên dịch phát hiện ra rằng ít nhất toàn bộ dòng mã nguồn sẽ không bao giờ được thực thi, bởi vì một số điều kiện không bao giờ được thỏa mãn hoặc bởi vì đó là sau một thủ tục không bao giờ trả về.

Cập nhật : Tôi tìm thấy chủ đề tương tự Phát hiện mã chết trong dự án C / C ++ cũ


4
Điều này sẽ không bắt được các tiêu đề mà các chức năng nguyên mẫu không bao giờ được gọi. Hoặc các phương thức lớp công khai không được gọi. Nó chỉ có thể kiểm tra nếu các biến phạm vi cục bộ được sử dụng trong phạm vi đó.
Falmarri

@Falmarri Tôi chưa bao giờ sử dụng cờ này. Tôi đang cố gắng tìm ra loại mã chết nào tôi có thể tìm thấy với nó.
UmmaGumma

-Wunusedcảnh báo về các biến được khai báo (hoặc khai báo và xác định trong một lần) nhưng thực sự không bao giờ được sử dụng. Nhân tiện, khá khó chịu với những người bảo vệ có phạm vi: p Có một triển khai thử nghiệm ở Clang, nó cũng cảnh báo về các biến không bay hơi được ghi vào nhưng không bao giờ được đọc từ (bởi Ted Kremenek). -Wunreachable-codecảnh báo về mã trong một chức năng không thể đạt được, đó có thể là mã nằm sau một throwhoặc một returncâu lệnh hoặc mã trong một nhánh không bao giờ được thực hiện (ví dụ xảy ra trong trường hợp so sánh tautological).
Matthieu M.

18

Tôi nghĩ rằng bạn đang tìm kiếm một công cụ bảo hiểm mã . Một công cụ bao phủ mã sẽ phân tích mã của bạn khi nó đang chạy và nó sẽ cho bạn biết dòng mã nào đã được thực thi và bao nhiêu lần, cũng như dòng nào không.

Bạn có thể thử cho công cụ bảo hiểm mã nguồn mở này một cơ hội: TestCocoon - công cụ bao phủ mã cho C / C ++ và C #.


7
Chìa khóa ở đây là "vì nó đang chạy" - nếu dữ liệu đầu vào của bạn không thực hiện một số đường dẫn mã mà đường dẫn đó sẽ không được nhận dạng là được sử dụng, phải không?
sharptooth

1
Đúng rồi. Không chạy mã, không có cách nào để biết dòng nào không đạt được. Tôi tự hỏi sẽ khó đến mức nào khi thiết lập một số Bài kiểm tra đơn vị để mô phỏng một vài lần chạy bình thường.
Carlos V

1
@drhishch Tôi nghĩ rằng, hầu hết các mã không được sử dụng như vậy phải tìm trình liên kết và không phải trình biên dịch.
UmmaGumma

1
@drhirsch Đúng, trình biên dịch có thể xử lý một số mã không thể truy cập được, chẳng hạn như các hàm được khai báo nhưng không được gọi và một số đánh giá ngắn mạch, nhưng còn mã phụ thuộc vào hành động của người dùng hay chạy biến thời gian thì sao?
Carlos V

1
@golcarcol Ok, chúng ta hãy có chức năng void func()trong a.cpp, được sử dụng trong b.cpp. Làm thế nào trình biên dịch có thể kiểm tra, func () được sử dụng trong chương trình? Đó là công việc liên kết.
UmmaGumma

15

Câu trả lời thực sự ở đây là: Bạn không bao giờ có thể thực sự biết chắc chắn.

Ít nhất, đối với các trường hợp không cần thiết, bạn không thể chắc chắn mình đã nhận được tất cả. Hãy xem xét những điều sau đây từ bài viết của Wikipedia về mã không thể truy cập :

double x = sqrt(2);
if (x > 5)
{
  doStuff();
}

Như Wikipedia lưu ý chính xác, một trình biên dịch thông minh có thể có thể bắt được một cái gì đó như thế này. Nhưng hãy xem xét một sửa đổi:

int y;
cin >> y;
double x = sqrt((double)y);

if (x != 0 && x < 1)
{
  doStuff();
}

Trình biên dịch sẽ nắm bắt điều này? Có lẽ. Nhưng để làm điều đó, nó sẽ cần phải làm nhiều hơn là chạy sqrtvới giá trị vô hướng không đổi. Nó sẽ phải tìm ra rằng (double)ysẽ luôn luôn là một số nguyên (dễ), và sau đó hiểu phạm vi toán học của sqrttập hợp các số nguyên (cứng). Một trình biên dịch rất tinh vi có thể có thể thực hiện điều này cho sqrthàm hoặc cho mọi hàm trong math.h hoặc cho bất kỳ hàm đầu vào cố định nào có miền mà nó có thể tìm ra. Điều này trở nên rất, rất phức tạp và sự phức tạp về cơ bản là vô hạn. Bạn có thể tiếp tục thêm các lớp tinh vi vào trình biên dịch của mình, nhưng sẽ luôn có cách để lẻn vào một số mã sẽ không thể truy cập được đối với bất kỳ bộ đầu vào cụ thể nào.

Và sau đó là các bộ đầu vào đơn giản là không bao giờ được nhập. Đầu vào sẽ không có ý nghĩa trong cuộc sống thực hoặc bị chặn bởi logic xác thực ở nơi khác. Không có cách nào để trình biên dịch biết về những điều đó.

Kết quả cuối cùng của việc này là trong khi các công cụ phần mềm mà những người khác đã đề cập là cực kỳ hữu ích, bạn sẽ không bao giờ biết chắc chắn rằng bạn đã nắm bắt mọi thứ trừ khi bạn đi qua mã theo cách thủ công sau đó. Ngay cả sau đó, bạn sẽ không bao giờ chắc chắn rằng bạn đã không bỏ lỡ bất cứ điều gì.

Giải pháp thực sự duy nhất, IMHO, là thận trọng nhất có thể, sử dụng tự động hóa theo ý của bạn, tái cấu trúc nơi bạn có thể và liên tục tìm cách cải thiện mã của mình. Tất nhiên, dù sao thì đó cũng là một ý tưởng tốt.


1
Đúng và không để lại mã chết! Nếu bạn loại bỏ một tính năng, giết mã chết. Để nó ở đó "chỉ trong trường hợp" chỉ gây ra sự phình to mà (như bạn đã thảo luận) rất khó tìm thấy sau này. Hãy để kiểm soát phiên bản làm việc tích trữ cho ya.
Các cuộc đua nhẹ nhàng trong quỹ đạo

12

Tôi đã không sử dụng nó cho mình, nhưng cppcheck , tuyên bố để tìm các chức năng không sử dụng. Nó có thể sẽ không giải quyết được vấn đề hoàn chỉnh nhưng nó có thể là một sự khởi đầu.


Có, nó có thể tìm thấy các biến và hàm không tham chiếu cục bộ.
Chugaister

Yep sử dụng cppcheck --enable=unusedFunction --language=c++ .để tìm các chức năng không sử dụng.
Jason Harris

9

Bạn có thể thử sử dụng PC-lint / FlexeLint từ Phần mềm Gimple . Nó tuyên bố

tìm các macro không sử dụng, typedef's, các lớp, thành viên, khai báo, v.v. trên toàn bộ dự án

Tôi đã sử dụng nó để phân tích tĩnh và thấy nó rất tốt nhưng tôi phải thừa nhận rằng tôi đã không sử dụng nó để tìm mã chết cụ thể.


5

Cách tiếp cận thông thường của tôi để tìm những thứ không sử dụng là

  1. đảm bảo hệ thống xây dựng xử lý theo dõi phụ thuộc chính xác
  2. thiết lập màn hình thứ hai, với cửa sổ đầu cuối toàn màn hình, chạy các bản dựng lặp lại và hiển thị màn hình đầu ra đầu tiên. watch "make 2>&1"có xu hướng thực hiện các thủ thuật trên Unix.
  3. chạy một hoạt động tìm và thay thế trên toàn bộ cây nguồn, thêm "//?" vào đầu mỗi dòng
  4. sửa lỗi đầu tiên được gắn cờ bởi trình biên dịch, bằng cách xóa "//?" trong các dòng tương ứng.
  5. Lặp lại cho đến khi không còn lỗi.

Đây là một quá trình hơi dài, nhưng nó cho kết quả tốt.


2
Có công, nhưng rất tốn công. Ngoài ra, bạn phải đảm bảo bỏ ghi chú tất cả các tình trạng quá tải của một chức năng cùng một lúc - nếu có nhiều hơn một áp dụng, việc bỏ ghi chú ít ưu tiên có thể cho phép biên dịch thành công nhưng dẫn đến hành vi chương trình không chính xác (và ý tưởng không chính xác chức năng được sử dụng).
j_random_hacker

Tôi chỉ khai báo không ghi chú trong bước đầu tiên (tất cả các quá tải), và trong lần lặp tiếp theo sau đó xem định nghĩa nào bị thiếu; Bằng cách đó, tôi có thể thấy những gì quá tải thực sự được sử dụng.
Simon Richter

@Simon: Thật thú vị khi bình luận về câu hỏi chính, MSalters chỉ ra rằng ngay cả sự hiện diện / vắng mặt của một tuyên bố cho một chức năng không bao giờ được gọi có thể ảnh hưởng đến chức năng của 2 chức năng khác được tìm thấy bởi độ phân giải quá tải. Phải thừa nhận rằng điều này đòi hỏi một thiết lập cực kỳ kỳ quái và giả tạo, vì vậy nó không có khả năng là một vấn đề trong thực tế.
j_random_hacker

4

Đánh dấu càng nhiều hàm và biến công khai là riêng tư hoặc được bảo vệ mà không gây ra lỗi biên dịch, trong khi thực hiện việc này, hãy thử cấu trúc lại mã. Bằng cách đặt các chức năng riêng tư và ở một mức độ nào đó được bảo vệ, bạn đã giảm khu vực tìm kiếm của mình vì các chức năng riêng tư chỉ có thể được gọi từ cùng một lớp (trừ khi có macro ngu ngốc hoặc các thủ thuật khác để tránh hạn chế truy cập và nếu đó là trường hợp tôi khuyên bạn nên tìm một công việc mới). Sẽ dễ dàng hơn nhiều để xác định rằng bạn không cần một chức năng riêng tư vì chỉ có lớp bạn đang làm việc có thể gọi chức năng này. Phương pháp này dễ dàng hơn nếu cơ sở mã của bạn có các lớp nhỏ và được ghép lỏng lẻo. Nếu cơ sở mã của bạn không có các lớp nhỏ hoặc có khớp nối rất chặt chẽ, tôi khuyên bạn nên làm sạch chúng trước.

Tiếp theo sẽ là đánh dấu tất cả các hàm công khai còn lại và tạo một biểu đồ cuộc gọi để tìm ra mối quan hệ giữa các lớp. Từ cây này, cố gắng tìm ra phần nào của nhánh trông giống như nó có thể được cắt tỉa.

Ưu điểm của phương pháp này là bạn có thể thực hiện trên cơ sở từng mô-đun, do đó thật dễ dàng để tiếp tục vượt qua mức không đáng tin cậy của bạn mà không có thời gian lớn khi bạn bị hỏng mã cơ sở.


3

Nếu bạn đang dùng Linux, bạn có thể muốn xem xét callgrind, một công cụ phân tích chương trình C / C ++ là một phần của valgrindbộ phần mềm, cũng chứa các công cụ kiểm tra rò rỉ bộ nhớ và các lỗi bộ nhớ khác (bạn cũng nên sử dụng). Nó phân tích một phiên bản đang chạy của chương trình của bạn và tạo dữ liệu về biểu đồ cuộc gọi của nó và về chi phí hiệu suất của các nút trên biểu đồ cuộc gọi. Nó thường được sử dụng để phân tích hiệu suất, nhưng nó cũng tạo ra một biểu đồ cuộc gọi cho các ứng dụng của bạn, vì vậy bạn có thể thấy các chức năng nào được gọi, cũng như người gọi của chúng.

Điều này rõ ràng là bổ sung cho các phương thức tĩnh được đề cập ở nơi khác trên trang và nó sẽ chỉ hữu ích trong việc loại bỏ hoàn toàn các lớp, phương thức và hàm không sử dụng - nó cũng không giúp tìm mã chết trong các phương thức thực sự được gọi.


3

Tôi thực sự đã không sử dụng bất kỳ công cụ nào làm một việc như vậy ... Nhưng, theo như tôi đã thấy trong tất cả các câu trả lời, không ai từng nói rằng vấn đề này là không thể giải quyết được.

Ý của tôi là gì? Rằng vấn đề này không thể được giải quyết bằng bất kỳ thuật toán nào trên máy tính. Định lý này (rằng một thuật toán như vậy không tồn tại) là một hệ quả của Vấn đề Ngừng của Turing.

Tất cả các công cụ bạn sẽ sử dụng không phải là thuật toán mà là thuật toán (tức là không phải là thuật toán chính xác). Họ sẽ không cung cấp cho bạn chính xác tất cả các mã không được sử dụng.


1
Tôi nghĩ rằng OP chủ yếu muốn tìm các chức năng không được gọi từ bất cứ đâu, điều này chắc chắn không thể tính được - hầu hết các trình liên kết hiện đại đều có thể làm được! Đó chỉ là vấn đề trích xuất thông tin đó với ít đau đớn và vất vả nhất.
j_random_hacker

Bạn nói đúng, tôi không thấy bình luận cuối cùng cho câu hỏi chính. Theo các cách, có thể có các hàm được gọi trong mã không thực sự được sử dụng. Đó là những thứ có thể không được phát hiện.
geekazoid

2

Một cách là sử dụng trình gỡ lỗi và tính năng trình biên dịch để loại bỏ mã máy không sử dụng trong quá trình biên dịch.

Khi một số mã máy được loại bỏ, trình gỡ lỗi sẽ không cho phép bạn đặt breakpojnt trên dòng mã nguồn tương ứng. Vì vậy, bạn đặt các điểm dừng ở mọi nơi và khởi động chương trình và kiểm tra các điểm dừng - những điểm ở trạng thái "không tải mã cho nguồn này" tương ứng với mã bị loại bỏ - hoặc mã đó không bao giờ được gọi hoặc nó đã được đặt vào trong và bạn phải thực hiện một số mức tối thiểu phân tích để tìm ra cái nào trong hai cái đó đã xảy ra

Ít nhất đó là cách nó hoạt động trong Visual Studio và tôi đoán các bộ công cụ khác cũng có thể làm điều đó.

Đó là rất nhiều công việc, nhưng tôi đoán nhanh hơn so với việc phân tích thủ công tất cả các mã.


4
Tôi nghĩ rằng câu hỏi của OP là về cách tìm một tập hợp con mã nguồn nhỏ hơn, dễ quản lý hơn, không quá nhiều để đảm bảo rằng tệp nhị phân được biên dịch có hiệu quả.
j_random_hacker

@j_random_hacker Tôi đã cho nó một mặc dù - và hóa ra việc loại bỏ mã thậm chí có thể được sử dụng để theo dõi trở lại mã nguồn ban đầu.
sharptooth

Bạn có một số cờ biên dịch cụ thể trên visual studio để đạt được nó không? và nó chỉ hoạt động trong chế độ phát hành hay nó cũng sẽ hoạt động trong gỡ lỗi?
Naveen

Điều gì về các dòng được sử dụng nhưng được tối ưu hóa bởi trình biên dịch?
Itamar Katz

@Naveen: Trong Visual C ++ 9, bạn phải bật tối ưu hóa và sử dụng / OPT: ICF
sharptooth

2

CppDepend là một công cụ thương mại có thể phát hiện các loại, phương thức và trường không sử dụng và làm được nhiều hơn thế. Nó có sẵn cho Windows và Linux (nhưng hiện tại không có hỗ trợ 64 bit) và đi kèm bản dùng thử 2 tuần.

Tuyên bố miễn trừ trách nhiệm: Tôi không làm việc ở đó, nhưng tôi sở hữu giấy phép cho công cụ này (cũng như NDepend , đây là một thay thế mạnh mẽ hơn cho mã .NET).

Đối với những người tò mò, đây là một ví dụ quy tắc tích hợp (có thể tùy chỉnh) để phát hiện các phương thức chết, được viết bằng CQLinq :

// <Name>Potentially dead Methods</Name>
warnif count > 0
// Filter procedure for methods that should'nt be considered as dead
let canMethodBeConsideredAsDeadProc = new Func<IMethod, bool>(
    m => !m.IsPublic &&       // Public methods might be used by client applications of your Projects.
         !m.IsEntryPoint &&            // Main() method is not used by-design.
         !m.IsClassConstructor &&      
         !m.IsVirtual &&               // Only check for non virtual method that are not seen as used in IL.
         !(m.IsConstructor &&          // Don't take account of protected ctor that might be call by a derived ctors.
           m.IsProtected) &&
         !m.IsGeneratedByCompiler
)

// Get methods unused
let methodsUnused = 
   from m in JustMyCode.Methods where 
   m.NbMethodsCallingMe == 0 && 
   canMethodBeConsideredAsDeadProc(m)
   select m

// Dead methods = methods used only by unused methods (recursive)
let deadMethodsMetric = methodsUnused.FillIterative(
   methods => // Unique loop, just to let a chance to build the hashset.
              from o in new[] { new object() }
              // Use a hashet to make Intersect calls much faster!
              let hashset = methods.ToHashSet()
              from m in codeBase.Application.Methods.UsedByAny(methods).Except(methods)
              where canMethodBeConsideredAsDeadProc(m) &&
                    // Select methods called only by methods already considered as dead
                    hashset.Intersect(m.MethodsCallingMe).Count() == m.NbMethodsCallingMe
              select m)

from m in JustMyCode.Methods.Intersect(deadMethodsMetric.DefinitionDomain)
select new { m, m.MethodsCallingMe, depth = deadMethodsMetric[m] }

Cập nhật: Hỗ trợ 64 bit cho Linux đã được thêm vào trong phiên bản 3.1.
Roman Boiko

1

Nó phụ thuộc vào nền tảng bạn sử dụng để tạo ứng dụng của bạn.

Ví dụ: nếu bạn sử dụng Visual Studio, bạn có thể sử dụng một công cụ như .NET ANTS Profiler có khả năng phân tích cú pháp và hồ sơ mã của bạn. Bằng cách này, bạn sẽ nhanh chóng biết phần nào trong mã của bạn thực sự được sử dụng. Eclipse cũng có các plugin tương đương.

Mặt khác, nếu bạn cần biết chức năng nào của ứng dụng thực sự được sử dụng bởi người dùng cuối của bạn và nếu bạn có thể giải phóng ứng dụng của mình một cách dễ dàng, bạn có thể sử dụng tệp nhật ký để kiểm toán.

Đối với mỗi chức năng chính, bạn có thể theo dõi việc sử dụng nó và sau vài ngày / tuần chỉ cần lấy tệp nhật ký đó và xem xét nó.


1
.net ANTS Profiler trông giống như C # - bạn có chắc nó cũng hoạt động cho C ++ không?
j_random_hacker

@j_random_hacker: miễn là tôi biết, Nó hoạt động với mã được quản lý. Vì vậy, .net ANTS chắc chắn sẽ không thể phân tích mã C ++ 'tiêu chuẩn' (tức là được biên dịch bằng gcc, ...).
AUS

0

Tôi không nghĩ rằng nó có thể được thực hiện tự động.

Ngay cả với các công cụ bao phủ mã, bạn cần cung cấp đủ dữ liệu đầu vào để chạy.

Có thể là công cụ phân tích tĩnh có giá rất cao và phức tạp, chẳng hạn như từ trình biên dịch Coverity hoặc LLVM có thể giúp ích.

Nhưng tôi không chắc chắn và tôi thích xem lại mã thủ công.

CẬP NHẬT

Chà .. chỉ loại bỏ các biến không sử dụng, mặc dù chức năng không sử dụng không khó.

CẬP NHẬT

Sau khi đọc các câu trả lời và nhận xét khác, tôi tin chắc rằng nó không thể được thực hiện.

Bạn phải biết mã để có biện pháp bao phủ mã có ý nghĩa và nếu bạn biết rằng chỉnh sửa thủ công sẽ nhanh hơn so với chuẩn bị / chạy / xem lại kết quả bảo hiểm.


2
từ ngữ trong câu trả lời của bạn là sai lệch, không có gì khó chịu về LLVM ... nó miễn phí!
Matthieu M.

chỉnh sửa thủ công sẽ không giúp bạn với các biến thời gian chạy vượt qua các nhánh logic trong chương trình của bạn. Điều gì xảy ra nếu mã của bạn không bao giờ đáp ứng một tiêu chí nhất định và do đó luôn đi theo cùng một đường dẫn?
Carlos V

0

Tôi đã có một người bạn hỏi tôi câu hỏi này ngày hôm nay và tôi đã xem xét một số phát triển Clang đầy hứa hẹn, ví dụ như ASTMatcherBộ phân tích tĩnh có thể có đủ khả năng hiển thị trong quá trình biên dịch để xác định các phần mã chết, nhưng sau đó tôi tìm thấy điều này:

https://blog.flameeyes.eu/2008/01/today-how-to-identify-unuse-exported-fifts-and-variabled

Đây là một mô tả đầy đủ về cách sử dụng một vài cờ GCC dường như được thiết kế cho mục đích xác định các biểu tượng không được ước tính!


0

Vấn đề chung nếu một số chức năng sẽ được gọi là NP-Complete. Bạn không thể biết trước một cách chung chung nếu một số chức năng sẽ được gọi như bạn sẽ không biết nếu một máy Turing sẽ dừng lại. Bạn có thể nhận được nếu có một số đường dẫn (tĩnh) đi từ hàm chính () đến hàm bạn đã viết, nhưng điều đó không đảm bảo cho bạn rằng nó sẽ được gọi.


-3

Chà, nếu bạn sử dụng g ++, bạn có thể sử dụng cờ này -Wunuse

Theo tài liệu:

Warn whenever a variable is unused aside from its declaration, whenever a function is declared static but never defined, whenever a label is declared but not used, and whenever a statement computes a result that is explicitly not used.

http://docs.freebsd.org/info/gcc/gcc.info.Warning_Options.html

Chỉnh sửa: Đây là cờ hữu ích khác -Wunreachable-code Theo tài liệu:

This option is intended to warn when the compiler detects that at least a whole line of source code will never be executed, because some condition is never satisfied or because it is after a procedure that never returns.

6
Thông tin chính xác này đã được đề cập trong câu trả lời hàng đầu hiện nay. Xin vui lòng đọc câu trả lời hiện có để tránh trùng lặp không cần thiết.
j_random_hacker

1
Bây giờ bạn có thể kiếm được huy hiệu ngang hàng của bạn!
Andrew Grimm
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.