ฉันจะตรวจจับไฟล์ #include ที่ไม่จำเป็นในโปรเจ็กต์ C ++ ขนาดใหญ่ได้อย่างไร


96

ฉันกำลังทำงานกับโปรเจ็กต์ C ++ ขนาดใหญ่ใน Visual Studio 2008 และมีไฟล์จำนวนมากที่มี#includeคำสั่งที่ไม่จำเป็น บางครั้ง#includes เป็นเพียงอาร์ติแฟกต์และทุกอย่างจะคอมไพล์ได้ดีเมื่อลบออกและในกรณีอื่น ๆ คลาสอาจถูกประกาศไปข้างหน้าและ #include สามารถย้ายไปยัง.cppไฟล์ได้ มีเครื่องมือที่ดีในการตรวจจับทั้งสองกรณีนี้หรือไม่?

คำตอบ:


50

แม้ว่าจะไม่เปิดเผยไฟล์ที่ไม่จำเป็น แต่ Visual studio ก็มีการตั้งค่า/showIncludes(คลิกขวาที่.cppไฟล์Properties->C/C++->Advanced) ที่จะส่งออกโครงสร้างของไฟล์ที่รวมทั้งหมดในเวลาคอมไพล์ สิ่งนี้สามารถช่วยในการระบุไฟล์ที่ไม่จำเป็นต้องรวมไว้

นอกจากนี้คุณยังสามารถดูสำนวน pimpl เพื่อให้คุณหลีกเลี่ยงการพึ่งพาไฟล์ส่วนหัวน้อยลงเพื่อให้ง่ายต่อการมองเห็นปมที่คุณสามารถลบได้


1
/ showincludes เยี่ยมมาก การทำเช่นนี้ด้วยตนเองเป็นเรื่องที่น่ากลัวหากไม่มีสิ่งนั้น
shambolic

30

PC Lintทำงานได้ดีสำหรับสิ่งนี้และพบปัญหาโง่ ๆ ทุกประเภทสำหรับคุณเช่นกัน มีตัวเลือกบรรทัดคำสั่งที่สามารถใช้เพื่อสร้างเครื่องมือภายนอกใน Visual Studio แต่ฉันพบว่าส่วนเสริมVisual Lintนั้นใช้งานได้ง่ายกว่า แม้แต่ Visual Lint เวอร์ชันฟรีก็ช่วยได้ แต่ให้ PC-Lint ยิง การกำหนดค่าเพื่อไม่ให้คำเตือนคุณมากเกินไปต้องใช้เวลาสักหน่อย แต่คุณจะประหลาดใจกับสิ่งที่เกิดขึ้น


3
คำแนะนำบางประการเกี่ยวกับวิธีการใช้งาน PC-lint มีอยู่ที่riverblade.co.uk/…
David Sykes

29

มีเครื่องมือใหม่ที่ใช้ Clang ซึ่งรวมถึงสิ่งที่คุณใช้ซึ่งมีจุดมุ่งหมายเพื่อทำสิ่งนี้


โปรดทราบว่าสิ่งนี้ไม่ได้สร้างด้วย clang 3.5
kynan

4 มิถุนายน 2015 iwyu 0.4 ได้รับการเผยแพร่ มันเข้ากันได้กับ LLVM + เสียงดังกราว 3.6 ตามหน้าบ้าน iwyu
Erik Sjölund

26

!! การปฏิเสธความรับผิด !! ฉันทำงานกับเครื่องมือวิเคราะห์แบบคงที่เชิงพาณิชย์ (ไม่ใช่ PC Lint) !! การปฏิเสธความรับผิด !!

มีปัญหาหลายประการเกี่ยวกับวิธีการไม่แยกวิเคราะห์ง่ายๆ:

1) ชุดโอเวอร์โหลด:

เป็นไปได้ว่าฟังก์ชันที่โอเวอร์โหลดมีการประกาศที่มาจากไฟล์ต่างกัน อาจเป็นไปได้ว่าการลบไฟล์ส่วนหัวหนึ่งไฟล์ส่งผลให้มีการเลือกโอเวอร์โหลดต่างกันแทนที่จะเป็นข้อผิดพลาดในการคอมไพล์! ผลลัพธ์ที่ได้จะเป็นการเปลี่ยนแปลงความหมายอย่างเงียบ ๆ ซึ่งอาจเป็นเรื่องยากมากที่จะติดตามในภายหลัง

2) ความเชี่ยวชาญของเทมเพลต:

เช่นเดียวกับตัวอย่างการโอเวอร์โหลดหากคุณมีความเชี่ยวชาญเฉพาะบางส่วนหรืออย่างชัดเจนสำหรับเทมเพลตคุณต้องการให้ทุกอย่างสามารถมองเห็นได้เมื่อใช้เทมเพลต อาจเป็นไปได้ว่าความเชี่ยวชาญพิเศษสำหรับเทมเพลตหลักอยู่ในไฟล์ส่วนหัวที่แตกต่างกัน การลบส่วนหัวด้วยความเชี่ยวชาญพิเศษจะไม่ทำให้เกิดข้อผิดพลาดในการคอมไพล์ แต่อาจส่งผลให้เกิดลักษณะการทำงานที่ไม่ได้กำหนดไว้หากจะเลือกความเชี่ยวชาญนั้น (ดู: การมองเห็นความเชี่ยวชาญพิเศษของเทมเพลตของฟังก์ชัน C ++ )

ตามที่ระบุโดย 'msalters' การวิเคราะห์โค้ดทั้งหมดยังช่วยให้สามารถวิเคราะห์การใช้คลาสได้ ด้วยการตรวจสอบวิธีการใช้คลาสผ่านพา ธ เฉพาะของไฟล์เป็นไปได้ว่าคำจำกัดความของคลาส (และดังนั้นการอ้างอิงทั้งหมด) สามารถลบออกได้ทั้งหมดหรืออย่างน้อยก็ย้ายไปยังระดับที่ใกล้กับซอร์สหลักในการรวม ต้นไม้.


@RichardCorden: ซอฟต์แวร์ของคุณ (QA C ++) แพงเกินไป
Xander Tulip

13
@XanderTulip: เป็นการยากที่จะตอบสนองต่อสิ่งนี้โดยไม่ต้องลงเอยด้วยการเสนอขาย - ดังนั้นฉันต้องขออภัยล่วงหน้า IMHO สิ่งที่คุณต้องพิจารณาคือวิศวกรที่ดีจะต้องใช้เวลานานแค่ไหนในการค้นหาสิ่งต่างๆเช่นนี้ (ตลอดจนข้อบกพร่องด้านภาษา / การควบคุมอื่น ๆ อีกมากมาย) ในโครงการที่มีขนาดเหมาะสม เนื่องจากซอฟต์แวร์เปลี่ยนงานเดียวกันจึงต้องทำซ้ำแล้วซ้ำอีก ดังนั้นเมื่อคุณคำนวณระยะเวลาที่ประหยัดได้แล้วค่าใช้จ่ายของเครื่องมืออาจไม่สำคัญ
Richard Corden

10

ฉันไม่รู้จักเครื่องมือดังกล่าวและฉันเคยคิดที่จะเขียนมันในอดีต แต่ปรากฎว่านี่เป็นปัญหาที่ยากที่จะแก้ไข

บอกว่าไฟล์ต้นฉบับของคุณมี ah และ bh; อามีและการใช้#define USE_FEATURE_X BH #ifdef USE_FEATURE_Xหาก#include "a.h"มีการแสดงความคิดเห็นไฟล์ของคุณอาจยังคงรวบรวม แต่อาจไม่เป็นไปตามที่คุณคาดหวัง ตรวจจับสิ่งนี้โดยทางโปรแกรมไม่ได้เป็นเรื่องเล็กน้อย

เครื่องมือนี้จะต้องรู้สภาพแวดล้อมการสร้างของคุณด้วยเช่นกัน ถ้าอาดูเหมือน:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

จากนั้นUSE_FEATURE_XจะถูกกำหนดWINNTก็ต่อเมื่อมีการกำหนดดังนั้นเครื่องมือจะต้องทราบว่าคำสั่งใดที่สร้างโดยคอมไพลเลอร์เองรวมถึงคำสั่งใดที่ระบุไว้ในคำสั่งคอมไพล์แทนที่จะอยู่ในไฟล์ส่วนหัว


9

เช่นเดียวกับ Timmermans ฉันไม่คุ้นเคยกับเครื่องมือใด ๆ สำหรับสิ่งนี้ แต่ฉันรู้จักโปรแกรมเมอร์ที่เขียนสคริปต์ Perl (หรือ Python) เพื่อลองแสดงความคิดเห็นทีละบรรทัดรวมทีละรายการแล้วรวบรวมแต่ละไฟล์


ดูเหมือนว่าตอนนี้ Eric Raymond มีเครื่องมือสำหรับสิ่งนี้มีเครื่องมือสำหรับการนี้

cpplint.pyของ Google มีกฎ "รวมสิ่งที่คุณใช้" (และอื่น ๆ อีกมากมาย) แต่เท่าที่ฉันสามารถบอกได้ว่าไม่ "รวมเฉพาะสิ่งที่คุณใช้เท่านั้น" ถึงกระนั้นมันก็มีประโยชน์


ฉันต้องหัวเราะเมื่ออ่านเรื่องนี้ เจ้านายของฉันทำสิ่งนี้อย่างมากในโครงการหนึ่งของเราเมื่อเดือนที่แล้ว ส่วนหัวที่ลดลงประกอบด้วยปัจจัยสองสามประการ
Don Wakefield

2
codewarrior บน mac เคยมีสคริปต์ในตัวเพื่อทำสิ่งนี้แสดงความคิดเห็นรวบรวมเมื่อเกิดข้อผิดพลาดในการยกเลิกการแสดงความคิดเห็นไปที่จุดสิ้นสุดของ #includes ใช้งานได้กับ #includes ที่ด้านบนของไฟล์เท่านั้น แต่โดยปกติแล้วจะอยู่ที่ใด มันไม่สมบูรณ์แบบ แต่มันทำให้สิ่งต่างๆมีเหตุผล
slycrel

5

หากคุณกำลังสนใจในหัวข้อนี้โดยทั่วไปคุณอาจต้องการที่จะตรวจสอบ Lakos' ชั่ง C ++ การออกแบบซอฟแวร์ที่มีขนาดใหญ่ มันดูล้าสมัยไปหน่อย แต่มีปัญหา "การออกแบบทางกายภาพ" มากมายเช่นการค้นหาส่วนหัวขั้นต่ำที่แน่นอนที่ต้องรวมไว้ ฉันไม่เคยเห็นเรื่องแบบนี้พูดถึงที่อื่นเลย


4

ให้รวมถึงผู้จัดการลอง ผสานรวมอย่างง่ายดายใน Visual Studio และแสดงเส้นทางการรวมของคุณซึ่งช่วยให้คุณค้นหาสิ่งที่ไม่จำเป็น ภายในใช้ Graphviz แต่ยังมีคุณสมบัติที่ยอดเยี่ยมอีกมากมาย และถึงแม้ว่าจะเป็นผลิตภัณฑ์เชิงพาณิชย์ แต่ก็มีราคาที่ต่ำมาก



3

หากไฟล์ส่วนหัวของคุณโดยทั่วไปเริ่มต้นด้วย

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

(ตรงข้ามกับการใช้ #pragma ครั้งเดียว) คุณสามารถเปลี่ยนเป็น:

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

และเนื่องจากคอมไพลเลอร์ส่งออกชื่อของไฟล์ cpp ที่กำลังคอมไพล์นั่นจะทำให้คุณรู้ว่าอย่างน้อยไฟล์ cpp ใดที่ทำให้ส่วนหัวถูกนำมาหลายครั้ง


12
ฉันคิดว่าเป็นการดีที่จะรวมส่วนหัวหลาย ๆ ครั้ง เป็นการดีที่จะรวมสิ่งที่คุณใช้และไม่ขึ้นอยู่กับไฟล์รวมของคุณในการทำเช่นนั้น ผมคิดว่าสิ่งที่ OP ต้องการคือการหา # รวมที่ไม่ได้ใช้จริง
Ryan Ginstrom

12
IMO ทำสิ่งผิดอย่างแข็งขัน ส่วนหัวควรมีส่วนหัวอื่น ๆ เมื่อไม่สามารถใช้งานได้หากไม่มี และเมื่อคุณมีA.hและB.hขึ้นอยู่กับC.hและคุณรวมA.hและB.hเนื่องจากคุณต้องการทั้งสองอย่างคุณจะรวมC.hสองครั้ง แต่ไม่เป็นไรเพราะคอมไพเลอร์จะข้ามไปในครั้งที่สองและถ้าคุณไม่ทำคุณจะต้องจำไว้ เพื่อรวมไว้C.hก่อนA.hหรือB.hจบลงด้วยการรวมที่ไร้ประโยชน์มากขึ้น
ม.ค. Hudec

5
เนื้อหามีความถูกต้องนี่เป็นทางออกที่ดีสำหรับการค้นหาส่วนหัวที่รวมหลายครั้ง อย่างไรก็ตามคำถามเดิมไม่ได้รับคำตอบจากสิ่งนี้และฉันนึกไม่ออกว่าเมื่อไหร่จะเป็นความคิดที่ดี ไฟล์ Cpp ควรมีส่วนหัวทั้งหมดที่ขึ้นอยู่แม้ว่าส่วนหัวจะรวมอยู่ก่อนที่อื่นก็ตาม คุณไม่ต้องการให้โปรเจ็กต์ของคุณมีคำสั่งคอมไพล์เฉพาะหรือสมมติว่าส่วนหัวอื่นจะรวมสิ่งที่คุณต้องการ
jaypb

3

PC-Lint สามารถทำได้แน่นอน วิธีง่ายๆวิธีหนึ่งในการทำเช่นนี้คือกำหนดค่าให้ตรวจหาไฟล์รวมที่ไม่ได้ใช้และไม่ต้องสนใจปัญหาอื่น ๆ ทั้งหมด สิ่งนี้ค่อนข้างตรงไปตรงมา - เพื่อเปิดใช้งานข้อความ 766 ("ไฟล์ส่วนหัวไม่ได้ใช้ในโมดูล") เพียงแค่ใส่ตัวเลือก -w0 + e766 ในบรรทัดคำสั่ง

นอกจากนี้ยังสามารถใช้แนวทางเดียวกันกับข้อความที่เกี่ยวข้องเช่น 964 ("ไฟล์ส่วนหัวไม่ได้ใช้โดยตรงในโมดูล") และ 966 ("ไฟล์ส่วนหัวที่รวมโดยอ้อมไม่ได้ใช้ในโมดูล")

FWIW ผมเขียนเกี่ยวกับเรื่องนี้ในรายละเอียดในบล็อกโพสต์เมื่อสัปดาห์ที่http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318


2

หากคุณต้องการลบ#includeไฟล์ที่ไม่จำเป็นเพื่อลดเวลาในการสร้างอาจใช้เวลาและเงินของคุณควบคู่ไปกับกระบวนการสร้างโดยใช้cl.exe / MP , make -j , Xoreax IncrediBuild , distcc / icecreamเป็นต้น

แน่นอนว่าหากคุณมีขั้นตอนการสร้างแบบขนานอยู่แล้วและคุณยังคงพยายามเร่งความเร็วอยู่ให้ล้าง#includeคำสั่งของคุณและลบการอ้างอิงที่ไม่จำเป็นออกไป


2

เริ่มต้นด้วยไฟล์รวมแต่ละไฟล์และตรวจสอบให้แน่ใจว่าแต่ละไฟล์รวมเฉพาะสิ่งที่จำเป็นในการคอมไพล์เอง ไฟล์รวมใด ๆ ที่หายไปสำหรับไฟล์ C ++ สามารถเพิ่มลงในไฟล์ C ++ ได้เอง

สำหรับไฟล์รวมและไฟล์ต้นทางแต่ละไฟล์ให้คอมเมนต์แต่ละไฟล์รวมทีละไฟล์และดูว่าคอมไพล์หรือไม่

นอกจากนี้ยังควรจัดเรียงไฟล์รวมตามตัวอักษรและในกรณีที่ไม่สามารถทำได้ให้เพิ่มความคิดเห็น


2
ฉันไม่แน่ใจว่าความคิดเห็นนี้ใช้งานได้จริงเพียงใดหากเกี่ยวข้องกับไฟล์การนำไปใช้งานจำนวนมาก
Sonny

1

การเพิ่ม #defines อย่างใดอย่างหนึ่งหรือทั้งสองอย่างต่อไปนี้จะยกเว้นไฟล์ส่วนหัวที่มักไม่จำเป็นและอาจช่วยปรับปรุงเวลาในการคอมไพล์ได้อย่างมากโดยเฉพาะอย่างยิ่งหากโค้ดที่ไม่ได้ใช้ฟังก์ชัน Windows API

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

โปรดดูhttp://support.microsoft.com/kb/166474


1
ไม่จำเป็นต้องใช้ทั้งคู่ - VC_EXTRALEAN กำหนด WIN32_LEAN_AND_MEAN
Aidan Ryan

1

หากคุณยังไม่ได้ใช้งานการใช้ส่วนหัวที่คอมไพล์ไว้ล่วงหน้าเพื่อรวมทุกสิ่งที่คุณจะไม่เปลี่ยนแปลง (ส่วนหัวของแพลตฟอร์มส่วนหัว SDK ภายนอกหรือชิ้นส่วนที่สร้างเสร็จแล้วแบบคงที่ของโครงการของคุณ) จะสร้างความแตกต่างอย่างมากในเวลาสร้าง

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

นอกจากนี้แม้ว่าอาจจะสายเกินไปสำหรับโครงการของคุณ แต่การจัดระเบียบโครงการของคุณเป็นส่วน ๆ และไม่รวมส่วนหัวในท้องถิ่นทั้งหมดไว้ในส่วนหัวหลักขนาดใหญ่เพียงรายการเดียวเป็นแนวทางปฏิบัติที่ดีแม้ว่าจะต้องใช้เวลาเพิ่มเล็กน้อย


คำอธิบายที่ยอดเยี่ยมของส่วนหัวที่คอมไพล์ไว้ล่วงหน้า: cygnus-software.com/papers/precompiledheaders.html (ไม่แน่ใจว่าการสร้างส่วนหัวที่คอมไพล์ไว้ล่วงหน้าโดยอัตโนมัตินั้นเสียใน VisualStudio เวอร์ชันล่าสุดหรือไม่ แต่ควรตรวจสอบ)
idbrii

1

หากคุณจะทำงานกับ Eclipse CDT คุณสามารถลองใช้http://includator.comเพื่อปรับโครงสร้างการรวมของคุณให้เหมาะสม อย่างไรก็ตามตัวรวมอาจไม่ทราบเพียงพอเกี่ยวกับการรวมที่กำหนดไว้ล่วงหน้าของ VC ++ และการตั้งค่า CDT เพื่อใช้ VC ++ ที่มีการรวมที่ถูกต้องยังไม่ได้รวมอยู่ใน CDT


1

Jetbrains IDE รุ่นล่าสุด CLion จะแสดงโดยอัตโนมัติ (เป็นสีเทา) รวมที่ไม่ได้ใช้ในไฟล์ปัจจุบัน

นอกจากนี้ยังเป็นไปได้ที่จะมีรายการของสิ่งที่ไม่ได้ใช้ทั้งหมด (และฟังก์ชันวิธีการ ฯลฯ ... ) จาก IDE


0

คำตอบที่มีอยู่บางส่วนระบุว่ามันยาก นั่นเป็นเรื่องจริงเพราะคุณต้องมีคอมไพเลอร์เต็มรูปแบบเพื่อตรวจจับกรณีที่การประกาศล่วงหน้าจะเหมาะสม คุณไม่สามารถแยกวิเคราะห์ C ++ โดยไม่รู้ว่าสัญลักษณ์นั้นหมายถึงอะไร ไวยากรณ์นั้นคลุมเครือเกินไปสำหรับสิ่งนั้น คุณต้องทราบว่าชื่อใดชื่อหนึ่งตั้งชื่อคลาส (อาจประกาศไปข้างหน้า) หรือตัวแปร (ไม่ได้) นอกจากนี้คุณต้องตระหนักถึงเนมสเปซ


คุณสามารถพูดได้ว่า "การตัดสินใจว่า #includes ใดจำเป็นเท่ากับการแก้ปัญหาการหยุดชะงักขอให้โชคดี :)" แน่นอนคุณสามารถใช้ฮิวริสติกส์ได้ แต่ฉันไม่รู้ว่ามีซอฟต์แวร์ฟรีใดที่ทำเช่นนี้ได้
porges

0

อาจจะช้าไปหน่อย แต่ฉันเคยพบสคริปต์ WebKit perl ที่ทำในสิ่งที่คุณต้องการ จะต้องมีการปรับตัวบางอย่างที่ฉันเชื่อ (ฉันไม่ถนัดเรื่อง perl) แต่ควรทำเคล็ดลับ:

http://trac.webkit.org/browser/branches/old/safari-3-2-branch/WebKitTools/Scripts/find-extra-includes

(เป็นสาขาเก่าเพราะ trunk ไม่มีไฟล์อีกต่อไป)


0

หากมีส่วนหัวเฉพาะที่คุณคิดว่าไม่จำเป็นอีกต่อไป (พูดว่า string.h) คุณสามารถแสดงความคิดเห็นจากนั้นใส่สิ่งนี้ไว้ด้านล่างรวมทั้งหมด:

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

แน่นอนว่าส่วนหัวของอินเทอร์เฟซของคุณอาจใช้หลักการ #define อื่นเพื่อบันทึกการรวมไว้ในหน่วยความจำ CPP หรือไม่มีแบบแผนซึ่งในกรณีนี้วิธีนี้จะไม่ได้ผล

จากนั้นสร้างใหม่ มีความเป็นไปได้สามประการ:

  • มันโอเค string.h ไม่สำคัญในการคอมไพล์และการรวมสำหรับมันสามารถลบออกได้

  • ทริป #error string.g ถูกรวมทางอ้อมอย่างใดคุณยังไม่รู้ว่าต้องใช้ string.h หรือไม่ หากจำเป็นคุณควร # รวมไว้โดยตรง (ดูด้านล่าง)

  • คุณได้รับข้อผิดพลาดในการคอมไพล์อื่น ๆ จำเป็นต้องใช้ string.h และไม่ได้รวมไว้ในทางอ้อมดังนั้นการรวมจึงถูกต้องที่จะเริ่มต้นด้วย

โปรดทราบว่าขึ้นอยู่กับการรวมทางอ้อมเมื่อ. h หรือ. c ของคุณใช้อื่นโดยตรง. h เกือบจะเป็นข้อบกพร่อง: คุณมีผลโดยสัญญาว่าโค้ดของคุณจะต้องใช้เฉพาะส่วนหัวนั้นตราบเท่าที่ส่วนหัวอื่น ๆ ที่คุณใช้ต้องการ ซึ่งอาจไม่ใช่สิ่งที่คุณหมายถึง

คำเตือนที่กล่าวถึงในคำตอบอื่น ๆ เกี่ยวกับส่วนหัวที่ปรับเปลี่ยนพฤติกรรมแทนที่จะประกาศสิ่งที่ก่อให้เกิดความล้มเหลวในการสร้างก็ใช้ได้เช่นกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.