Làm cách nào để phát hiện các tệp #include không cần thiết trong một dự án C ++ lớn?


96

Tôi đang làm việc trên một dự án C ++ lớn trong Visual Studio 2008 và có rất nhiều tệp với các lệnh không cần thiết #include. Đôi khi các #includes chỉ là hiện vật và mọi thứ sẽ biên dịch tốt khi chúng bị xóa và trong các trường hợp khác, các lớp có thể được khai báo phía trước và #include có thể được chuyển vào .cpptệp. Có công cụ nào tốt để phát hiện cả hai trường hợp này không?

Câu trả lời:


50

Mặc dù nó sẽ không hiển thị các tệp bao gồm không cần thiết, nhưng Visual studio có một cài đặt /showIncludes(nhấp chuột phải vào .cpptệp, Properties->C/C++->Advanced) sẽ xuất ra một cây gồm tất cả các tệp được bao gồm tại thời điểm biên dịch. Điều này có thể giúp xác định các tệp không cần phải đưa vào.

Bạn cũng có thể xem qua thành ngữ ma cô để giúp bạn loại bỏ bớt sự phụ thuộc vào tệp tiêu đề để giúp bạn dễ dàng nhìn thấy điểm mấu chốt mà bạn có thể loại bỏ.


1
/ showincludes là tuyệt vời. Làm điều này theo cách thủ công thật khó khăn nếu không có điều đó.
shambolic

30

PC Lint hoạt động khá tốt cho điều này và nó cũng tìm thấy tất cả các vấn đề ngốc nghếch khác cho bạn. Nó có các tùy chọn dòng lệnh có thể được sử dụng để tạo Công cụ bên ngoài trong Visual Studio, nhưng tôi thấy rằng addin Visual Lint dễ làm việc hơn. Ngay cả phiên bản miễn phí của Visual Lint cũng có ích. Nhưng hãy thử PC-Lint. Việc cấu hình nó để nó không đưa ra quá nhiều cảnh báo sẽ mất một chút thời gian, nhưng bạn sẽ ngạc nhiên với những gì nó xuất hiện.


3
Bạn có thể tìm thấy một số hướng dẫn về cách thực hiện việc này với pc-lint tại riverblade.co.uk/…
David Sykes


26

!! TỪ CHỐI !! Tôi làm việc trên một công cụ phân tích tĩnh thương mại (không phải PC Lint). !! TỪ CHỐI !!

Có một số vấn đề với cách tiếp cận đơn giản không phân tích cú pháp:

1) Bộ quá tải:

Có thể một hàm quá tải có các khai báo đến từ các tệp khác nhau. Có thể là việc loại bỏ một tệp tiêu đề dẫn đến một quá tải khác được chọn chứ không phải là lỗi biên dịch! Kết quả sẽ là một sự thay đổi âm thầm về ngữ nghĩa mà có thể rất khó theo dõi sau đó.

2) Chuyên môn mẫu:

Tương tự như ví dụ về quá tải, nếu bạn có một phần hoặc chuyên môn rõ ràng cho một mẫu, bạn muốn tất cả chúng hiển thị khi mẫu được sử dụng. Có thể là các chuyên ngành cho mẫu chính nằm trong các tệp tiêu đề khác nhau. Xóa tiêu đề với chuyên môn sẽ không gây ra lỗi biên dịch, nhưng có thể dẫn đến hành vi không xác định nếu chuyên môn đó đã được chọn. (Xem: Khả năng hiển thị của chuyên môn hóa mẫu của hàm C ++ )

Như được chỉ ra bởi 'msalters', thực hiện phân tích toàn bộ mã cũng cho phép phân tích việc sử dụng lớp. Bằng cách kiểm tra cách một lớp được sử dụng thông qua một đường dẫn cụ thể của tệp, có thể định nghĩa của lớp (và do đó tất cả các yếu tố phụ thuộc của nó) có thể bị loại bỏ hoàn toàn hoặc ít nhất được chuyển đến mức gần với nguồn chính trong bao gồm cây.


@RichardCorden: Phần mềm của bạn (QA C ++) quá đắt.
Xander Tulip

13
@XanderTulip: Thật khó để trả lời điều này mà không kết thúc bằng một màn chào hàng - vì vậy tôi xin lỗi trước. IMHO, những gì bạn phải xem xét là mất bao lâu để một kỹ sư giỏi tìm thấy những thứ như thế này (cũng như nhiều lỗi luồng điều khiển / ngôn ngữ khác) trong bất kỳ dự án có kích thước hợp lý nào. Khi phần mềm thay đổi, tác vụ tương tự cần được lặp lại nhiều lần. Vì vậy, khi bạn tính ra lượng thời gian tiết kiệm được thì chi phí của công cụ có lẽ không đáng kể.
Richard Corden

10

Tôi không biết bất kỳ công cụ nào như vậy, và tôi đã nghĩ về việc viết một công cụ này trong quá khứ, nhưng hóa ra đây là một vấn đề khó giải quyết.

Giả sử tệp nguồn của bạn bao gồm ah và bh; ah chứa #define USE_FEATURE_Xvà công dụng bh #ifdef USE_FEATURE_X. Nếu #include "a.h"bị bình luận, tệp của bạn có thể vẫn được biên dịch, nhưng có thể không làm được những gì bạn mong đợi. Việc phát hiện điều này theo chương trình là không hề nhỏ.

Bất kỳ công cụ nào làm điều này cũng cần biết môi trường xây dựng của bạn. Nếu ah trông giống như:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

Sau đó USE_FEATURE_Xchỉ được định nghĩa nếu WINNTđược định nghĩa, vì vậy công cụ sẽ cần biết những chỉ thị nào được tạo bởi chính trình biên dịch cũng như những chỉ thị nào được chỉ định trong lệnh biên dịch chứ không phải trong tệp tiêu đề.


9

Giống như Timmermans, tôi không quen thuộc với bất kỳ công cụ nào cho việc này. Nhưng tôi đã biết các lập trình viên đã viết tập lệnh Perl (hoặc Python) để thử nhận xét từng dòng bao gồm một và sau đó biên dịch từng tệp.


Dường như bây giờ Eric Raymond đã có một công cụ cho việc này .

Cpplint.py của Google có quy tắc "bao gồm những gì bạn sử dụng" (trong số nhiều quy tắc khác), nhưng theo như tôi có thể nói, không " chỉ bao gồm những gì bạn sử dụng." Mặc dù vậy, nó có thể hữu ích.


Tôi đã phải bật cười khi tôi đọc cái này. Sếp của tôi đã làm điều này trong một trong những dự án của chúng tôi chỉ vào tháng trước. Tiêu đề giảm bao gồm một vài yếu tố.
Don Wakefield

2
codewarrior trên mac đã từng có tập lệnh được tích hợp sẵn để thực hiện việc này, nhận xét, biên dịch, khi có lỗi, hãy bỏ nhận xét, tiếp tục đến cuối #includes. Nó chỉ hoạt động cho #includes ở đầu tệp, nhưng đó thường là vị trí của chúng. Nó không hoàn hảo, nhưng nó giữ mọi thứ ổn định một cách hợp lý.
slycrel

5

Nếu bạn quan tâm đến chủ đề này nói chung, bạn có thể muốn xem Thiết kế phần mềm C ++ Quy mô lớn của Lakos . Nó hơi lỗi thời, nhưng đi sâu vào nhiều vấn đề "thiết kế vật lý" như tìm ra mức tối thiểu tuyệt đối của các tiêu đề cần phải có. Tôi chưa thực sự thấy điều này được thảo luận ở bất kỳ nơi nào khác.


4

Hãy dùng thử Bao gồm Trình quản lý . Nó tích hợp dễ dàng trong Visual Studio và trực quan hóa các đường dẫn bao gồm của bạn, giúp bạn tìm thấy những thứ không cần thiết. Bên trong nó sử dụng Graphviz nhưng có nhiều tính năng thú vị hơn. Và mặc dù nó là một sản phẩm thương mại nhưng nó có giá rất thấp.



3

Nếu các tệp tiêu đề của bạn thường bắt đầu bằng

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#endif

(trái ngược với việc sử dụng #pragma một lần), bạn có thể thay đổi điều đó thành:

#ifndef __SOMEHEADER_H__
#define __SOMEHEADER_H__
// header contents
#else 
#pragma message("Someheader.h superfluously included")
#endif

Và vì trình biên dịch xuất ra tên của tệp cpp đang được biên dịch, điều đó sẽ cho bạn biết ít nhất tệp cpp nào đang khiến tiêu đề được đưa vào nhiều lần.


12
Tôi nghĩ rằng sẽ tốt nếu bao gồm các tiêu đề nhiều lần. Thật tốt khi bao gồm những gì bạn sử dụng và không phụ thuộc vào tệp bao gồm của bạn để làm như vậy. Tôi nghĩ rằng những gì OP muốn là tìm những #includes không thực sự được sử dụng.
Ryan Ginstrom

12
IMO chủ động điều sai phải làm. Tiêu đề nên bao gồm các tiêu đề khác khi chúng sẽ không hoạt động nếu không có chúng. Và khi bạn có A.hB.hđiều đó đều phụ thuộc vào C.hvà bạn bao gồm A.hB.h, bởi vì bạn cần cả hai, bạn sẽ bao gồm C.hhai lần, nhưng điều đó không sao cả, bởi vì trình biên dịch sẽ bỏ qua lần thứ hai và nếu không, bạn phải nhớ luôn bao gồm C.htrước A.hhoặc B.hkết thúc bằng nhiều bao hàm vô ích hơn.
Jan Hudec

5
Nội dung chính xác, đây là một giải pháp tốt để tìm các tiêu đề được đưa vào nhiều lần. Tuy nhiên, câu hỏi ban đầu không được trả lời bởi điều này và tôi không thể tưởng tượng khi nào đây sẽ là một ý tưởng tốt. Tệp cpp nên bao gồm tất cả các tiêu đề mà chúng phụ thuộc vào, ngay cả khi tiêu đề được bao gồm trước một nơi khác. Bạn không muốn dự án của mình có thứ tự biên dịch cụ thể hoặc giả sử một tiêu đề khác sẽ bao gồm tiêu đề bạn cần.
jaypb

3

PC-Lint thực sự có thể làm được điều này. Một cách dễ dàng để thực hiện việc này là định cấu hình nó để chỉ phát hiện các tệp bao gồm không sử dụng và bỏ qua tất cả các vấn đề khác. Điều này khá đơn giản - để chỉ kích hoạt thông báo 766 ("Tệp tiêu đề không được sử dụng trong mô-đun"), chỉ cần bao gồm các tùy chọn -w0 + e766 trên dòng lệnh.

Cách tiếp cận tương tự cũng có thể được sử dụng với các thông báo liên quan như 964 ("Tệp tiêu đề không được sử dụng trực tiếp trong mô-đun") và 966 ("Tệp tiêu đề được bao gồm gián tiếp không được sử dụng trong mô-đun").

FWIW Tôi đã viết chi tiết hơn về vấn đề này trong một bài đăng trên blog vào tuần trước tại http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318 .


2

Nếu bạn đang tìm cách xóa #includecác tệp không cần thiết để giảm thời gian xây dựng, thì thời gian và tiền bạc của bạn có thể được dành tốt hơn cho việc song song hóa quá trình xây dựng của bạn bằng cách sử dụng cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecream , v.v.

Tất nhiên, nếu bạn đã có một quá trình xây dựng song song và bạn vẫn đang cố gắng tăng tốc nó, thì bằng mọi cách, hãy dọn dẹp các #includechỉ thị của bạn và loại bỏ những phụ thuộc không cần thiết đó.


2

Bắt đầu với mỗi tệp bao gồm và đảm bảo rằng mỗi tệp bao gồm chỉ bao gồm những gì cần thiết để tự biên dịch. Bất kỳ tệp bao gồm nào sau đó bị thiếu đối với tệp C ++, đều có thể được thêm vào chính tệp C ++.

Đối với từng tệp bao gồm và tệp nguồn, hãy nhận xét từng tệp bao gồm một và xem nó có biên dịch hay không.

Bạn cũng nên sắp xếp các tệp bao gồm theo thứ tự bảng chữ cái và nếu không thể thực hiện điều này, hãy thêm nhận xét.


2
Tôi không chắc nhận xét này thực tế như thế nào, nếu có liên quan đến một số lượng rất lớn các tệp triển khai.
Sonny

1

Việc thêm một hoặc cả hai #defines sau sẽ loại trừ các tệp tiêu đề thường không cần thiết và có thể cải thiện đáng kể thời gian biên dịch, đặc biệt nếu mã không sử dụng các hàm API của Windows.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

Xem http://support.microsoft.com/kb/166474


1
Không cần cả hai - VC_EXTRALEAN xác định WIN32_LEAN_AND_MEAN
Aidan Ryan

1

Nếu bạn chưa có, việc sử dụng tiêu đề được biên dịch trước để bao gồm mọi thứ mà bạn sẽ không thay đổi (tiêu đề nền tảng, tiêu đề SDK bên ngoài hoặc các phần tĩnh đã hoàn thành của dự án của bạn) sẽ tạo ra sự khác biệt lớn về thời gian xây dựng.

http://msdn.microsoft.com/en-us/library/szfdksca(VS.71).aspx

Ngoài ra, mặc dù có thể là quá muộn đối với dự án của bạn, nhưng việc tổ chức dự án của bạn thành các phần và không gộp tất cả các tiêu đề cục bộ vào một tiêu đề chính lớn là một phương pháp hay, mặc dù phải làm thêm một chút.


Lớn giải thích về tiêu đề biên dịch sẵn: cygnus-software.com/papers/precompiledheaders.html (Không chắc chắn nếu autogenerating tiêu đề biên dịch sẵn được chia trong các phiên bản gần đây của VisualStudio, nhưng nó có giá trị kiểm tra.)
idbrii

1

Nếu bạn muốn làm việc với Eclipse CDT, bạn có thể thử http://includator.com để tối ưu hóa cấu trúc bao gồm của bạn. Tuy nhiên, Bao gồm có thể không biết đủ về các bao gồm được xác định trước của VC ++ và việc thiết lập CDT để sử dụng VC ++ với bao gồm chính xác vẫn chưa được tích hợp sẵn trong CDT.


1

Jetbrains IDE mới nhất, CLion, tự động hiển thị (bằng màu xám) bao gồm không được sử dụng trong tệp hiện tại.

Cũng có thể có danh sách tất cả các bao gồm không sử dụng (và cả các hàm, phương thức, v.v.) từ IDE.


0

Một số câu trả lời hiện có nói rằng nó khó. Điều đó thực sự đúng, bởi vì bạn cần một trình biên dịch đầy đủ để phát hiện các trường hợp mà một khai báo chuyển tiếp sẽ phù hợp. Bạn không thể phân tích cú pháp C ++ mà không biết ý nghĩa của các ký hiệu; ngữ pháp đơn giản là quá mơ hồ cho điều đó. Bạn phải biết liệu một tên nhất định đặt tên cho một lớp (có thể được khai báo trước) hay một biến (không thể). Ngoài ra, bạn cần phải nhận biết được không gian tên.


Bạn chỉ có thể nói "Quyết định #includes nào là cần thiết tương đương với việc giải quyết vấn đề tạm dừng. Chúc may mắn :)" Tất nhiên, bạn có thể sử dụng heuristics, nhưng tôi không biết bất kỳ phần mềm miễn phí nào làm được điều này.
porges 17-08


0

Nếu có một tiêu đề cụ thể mà bạn cho rằng không cần thiết nữa (ví dụ string.h), bạn có thể nhận xét rằng bao gồm sau đó đặt điều này bên dưới tất cả các bao gồm:

#ifdef _STRING_H_
#  error string.h is included indirectly
#endif

Tất nhiên, các tiêu đề giao diện của bạn có thể sử dụng quy ước #define khác để ghi lại việc đưa chúng vào bộ nhớ CPP. Hoặc không có quy ước, trong trường hợp đó, cách tiếp cận này sẽ không hoạt động.

Sau đó xây dựng lại. Có ba khả năng:

  • Nó xây dựng ok. string.h không phải là biên dịch quan trọng và có thể xóa phần bao gồm cho nó.

  • Các chuyến đi #error. string.g đã được đưa vào một cách gián tiếp bằng cách nào đó Bạn vẫn không biết liệu string.h có được yêu cầu hay không. Nếu nó được yêu cầu, bạn nên trực tiếp #include nó (xem bên dưới).

  • Bạn gặp một số lỗi biên dịch khác. string.h là cần thiết và không được bao gồm một cách gián tiếp, vì vậy, bắt đầu bao gồm là chính xác.

Lưu ý rằng tùy thuộc vào việc đưa vào gián tiếp khi .h hoặc .c của bạn trực tiếp sử dụng .h khác gần như chắc chắn là một lỗi: bạn đang hứa rằng mã của bạn sẽ chỉ yêu cầu tiêu đề đó miễn là một số tiêu đề khác mà bạn đang sử dụng yêu cầu, mà có lẽ không phải như ý bạn.

Cảnh báo được đề cập trong các câu trả lời khác về tiêu đề sửa đổi hành vi thay vì khai báo những thứ gây ra lỗi xây dựng cũng áp dụng ở đây.

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.