ฉันจะรู้ได้อย่างไรว่าส่วนใดในรหัสไม่เคยใช้?


312

ฉันมีรหัส C ++ ดั้งเดิมที่ฉันควรจะเอารหัสที่ไม่ได้ใช้ออก ปัญหาคือรหัสฐานมีขนาดใหญ่

ฉันจะทราบได้อย่างไรว่ารหัสใดไม่เคยถูกเรียกใช้ / ไม่เคยใช้?


4
ฉันคิดว่าภาษาการสืบค้นรหัสจะช่วยให้คุณเห็นโครงการของคุณโดยรวมดีขึ้น ฉันไม่แน่ใจเกี่ยวกับโลก c ++ แต่ดูเหมือนว่าจะมีcppdepend.com (นี่ไม่ฟรี) ซึ่งดูดีพอ อาจเป็นแบบนี้ได้ฟรี อีกอย่างคือก่อนที่จะทำการ refactoring สิ่งที่ควรทำคือการทดสอบยูนิตถ้าคุณไม่มีตอนนี้ ด้วยการทดสอบหน่วยสิ่งที่คุณสามารถทำได้คือมีเครื่องมือครอบคลุมรหัสโปรไฟล์ของคุณซึ่งมันจะช่วยในการลบรหัสที่ตายแล้วถ้าคุณไม่สามารถครอบคลุมรหัสนั้นได้
Biswanath

3
ตรวจสอบข้อมูลอ้างอิงที่นี่: en.wikipedia.org/wiki/Unreachable_code
Martin York

6
ฉันพบหัวข้อที่คล้ายกัน stackoverflow.com/questions/229069/…
UmmaGumma

3
ใช่สิ่งหนึ่งที่ตลกของ C ++ คือการลบฟังก์ชั่น "ไม่ได้ใช้" อาจยังคงเปลี่ยนผลลัพธ์ของโปรแกรม
MSalters

1
@MSalters: นั่นเป็นสิ่งที่น่าสนใจ ... สำหรับกรณีที่เราต้องพูดถึงฟังก์ชั่นในชุดโอเวอร์โหลดที่ถูกเลือกสำหรับการโทรที่กำหนดถูกต้อง? สำหรับความรู้ของฉันหากมี 2 ฟังก์ชั่นที่มีชื่อf()และการเรียกเพื่อf()แก้ไขอย่างไม่น่าสงสัยในอันดับที่ 1 มันเป็นไปไม่ได้ที่จะทำการแก้ไขการโทรนั้นไปที่อันดับ 2 โดยเพิ่มฟังก์ชั่นที่ 3 ชื่อf()- "แย่ที่สุด "โดยการเพิ่มฟังก์ชั่นที่ 3 คือการทำให้การเรียกคลุมเครือและทำให้โปรแกรมไม่สามารถคอมไพล์ได้ จะรัก (= ตกใจ) เพื่อดูตัวอย่าง
j_random_hacker

คำตอบ:


197

รหัสที่ไม่ได้ใช้มีสองประเภท:

  • โลคัลหนึ่งนั่นคือในบางฟังก์ชันพา ธ หรือตัวแปรบางอย่างไม่ได้ใช้ (หรือใช้ แต่ไม่มีความหมายเหมือนเขียน แต่ไม่เคยอ่าน)
  • Global หนึ่ง: ฟังก์ชั่นที่ไม่เคยเรียกใช้วัตถุระดับโลกที่ไม่เคยเข้าถึง

สำหรับชนิดแรกคอมไพเลอร์ที่ดีสามารถช่วย:

  • -Wunused(GCC, Clang ) ควรเตือนเกี่ยวกับตัวแปรที่ไม่ได้ใช้ตัววิเคราะห์ที่ไม่ได้ใช้ของ Clang ได้ถูกเพิ่มขึ้นเพื่อเตือนเกี่ยวกับตัวแปรที่ไม่เคยอ่าน (แม้ว่าจะใช้แล้ว)
  • -Wunreachable-code(GCC ที่เก่ากว่าถูกลบออกในปี 2010 ) ควรเตือนเกี่ยวกับบล็อกในพื้นที่ที่ไม่เคยเข้าถึง (มันเกิดขึ้นกับผลตอบแทนหรือเงื่อนไขที่ประเมินเป็นจริงเสมอ)
  • ไม่มีตัวเลือกที่ฉันรู้ว่าควรเตือนเกี่ยวกับcatchบล็อกที่ไม่ได้ใช้งานเพราะคอมไพเลอร์โดยทั่วไปไม่สามารถพิสูจน์ได้ว่าจะไม่มีข้อยกเว้นเกิดขึ้น

สำหรับประเภทที่สองมันยากกว่ามาก คงที่ต้องใช้การวิเคราะห์โปรแกรมทั้งหมดและแม้ว่าการเพิ่มประสิทธิภาพเวลาเชื่อมโยงอาจลบรหัสที่ตายแล้ว แต่ในทางปฏิบัติโปรแกรมได้รับการเปลี่ยนแปลงอย่างมากในเวลาที่มีการดำเนินการ

ดังนั้นจึงมีสองวิธี:

  • ทฤษฎีหนึ่งคือการใช้ตัววิเคราะห์แบบคงที่ ชิ้นส่วนของซอฟต์แวร์ที่จะตรวจสอบรหัสทั้งหมดในครั้งเดียวอย่างละเอียดและค้นหาเส้นทางการไหลทั้งหมด ในทางปฏิบัติฉันไม่รู้ว่าจะใช้งานได้ที่นี่
  • แนวทางปฏิบัติคือใช้การเรียนรู้แบบฮิวริสติก: ใช้เครื่องมือครอบคลุมรหัส (ในกลุ่ม GNU gcov) โปรดทราบว่าควรส่งค่าสถานะเฉพาะระหว่างการรวบรวมเพื่อให้ทำงานได้อย่างถูกต้อง) คุณเรียกใช้เครื่องมือครอบคลุมรหัสด้วยชุดอินพุตที่หลากหลาย (การทดสอบหน่วยของคุณหรือการทดสอบการไม่ถดถอย) รหัสที่ไม่จำเป็นต้องอยู่ภายในรหัสที่ยังไม่ได้เข้าถึง ... และเพื่อให้คุณสามารถเริ่มจากที่นี่

หากคุณสนใจเรื่องนี้มากและมีเวลาและความชอบในการทำงานเครื่องมือด้วยตัวเองฉันขอแนะนำให้ใช้ไลบรารี Clang เพื่อสร้างเครื่องมือดังกล่าว

  1. ใช้ไลบรารี Clang เพื่อรับ AST (แผนผังไวยากรณ์นามธรรม)
  2. ทำการวิเคราะห์ด้วยเครื่องหมาย - และ - กวาดจากจุดเริ่มต้นเป็นต้นไป

เนื่องจากเสียงดังกราวจะแยกรหัสสำหรับคุณและทำการแก้ปัญหาการโอเวอร์โหลดคุณจึงไม่ต้องจัดการกับกฎภาษา C ++ และคุณจะสามารถจดจ่อกับปัญหาได้

อย่างไรก็ตามเทคนิคประเภทนี้ไม่สามารถระบุการแทนที่เสมือนที่ไม่ได้ใช้เนื่องจากอาจถูกเรียกใช้โดยรหัสบุคคลที่สามที่คุณไม่สามารถให้เหตุผลได้


7
ดีมาก +1 ฉันชอบที่คุณแยกความแตกต่างระหว่างรหัสที่สามารถกำหนดแบบคงที่ไม่เคยทำงานภายใต้สถานการณ์ใด ๆ และรหัสที่ไม่ได้ทำงานในการเรียกใช้เฉพาะ แต่อาจทำได้ อดีตเป็นสิ่งสำคัญที่ฉันคิดว่าและเมื่อคุณพูดว่าการวิเคราะห์ความสามารถในการเข้าถึงได้โดยใช้ AST ของโปรแกรมทั้งหมดเป็นวิธีที่จะได้มา (การป้องกันไม่ให้foo()ถูกทำเครื่องหมายว่า "เรียกว่า" เมื่อปรากฏเฉพาะในif (0) { foo(); }โบนัสจะต้องใช้สมาร์ทพิเศษ)
j_random_hacker

@j_random_hacker: บางทีการใช้ CFG (Control-Flow Graph) น่าจะดีกว่าที่ฉันคิด (ขอบคุณตัวอย่างของคุณ) ฉันรู้ว่าเสียงดังกังวานที่จะพูดเกี่ยวกับการเปรียบเทียบเชิงเปรียบเทียบเช่นเดียวกับที่คุณพูดถึงและใช้ CFG ซึ่งเราอาจเห็นรหัสที่ตายแล้วตั้งแต่ต้น
Matthieu M.

@ Matthieu: ใช่ CFG คือสิ่งที่ฉันหมายถึงด้วยแทนที่จะเป็น AST :) สิ่งที่ฉันหมายถึงคือ: กราฟิคที่จุดยอดเป็นฟังก์ชันและมีขอบจากฟังก์ชัน x ถึงฟังก์ชัน y เมื่อใดก็ตามที่ x สามารถเรียก y ได้ (และด้วยคุณสมบัติที่สำคัญที่ฟังก์ชั่นการโอเวอร์โหลดจะถูกแสดงโดยจุดยอดที่แตกต่างกัน - เสียงเหมือนเสียงดังกรังทำสำหรับคุณว้า!)
j_random_hacker

1
@j_random_hacker: อันที่จริง CFG นั้นซับซ้อนกว่า digraph ง่าย ๆ เพราะมันแสดงถึงรหัสทั้งหมดที่จะถูกเรียกใช้ในบล็อกที่มีลิงก์จากบล็อกหนึ่งไปยังอีกบล็อกหนึ่งตามเงื่อนไขที่กำหนด ข้อได้เปรียบหลักคือมันเหมาะกับการตัดเลเยอร์โค้ดที่สามารถกำหนดแบบตายตัว (สร้างบล็อกที่ไม่สามารถเข้าถึงซึ่งสามารถระบุได้) ดังนั้นมันจะดีกว่าถ้าใช้ CFG มากกว่า AST เพื่อสร้าง digraph ที่คุณ พูดคุยเกี่ยวกับ ... ฉันคิดว่า :)
Matthieu เอ็ม

1
@j_random_hacker: จริง ๆ แล้ว AST ของ Clang ทำให้ทุกอย่างชัดเจน (หรือเกือบ ... ) เพราะมันมีไว้สำหรับทำงานกับรหัสไม่ใช่เพื่อการรวบรวมเท่านั้น มีการถกเถียงกันจริงในขณะนี้เนื่องจากเห็นได้ชัดว่ามีปัญหากับรายการตัวเริ่มต้นที่การแปลงโดยนัยดังกล่าวไม่ปรากฏใน AST แต่ฉันคิดว่ามันจะได้รับการแก้ไข
Matthieu M.

35

สำหรับกรณีของฟังก์ชั่นทั้งหมดที่ไม่ได้ใช้งาน (และตัวแปรส่วนกลางที่ไม่ได้ใช้) GCC สามารถทำงานส่วนใหญ่ให้คุณได้โดยที่คุณต้องใช้ GCC และ GNU ld

เมื่อรวบรวมแหล่งที่มาใช้-ffunction-sectionsและจากนั้นเมื่อเชื่อมโยงการใช้งาน-fdata-sections -Wl,--gc-sections,--print-gc-sectionsตัวเชื่อมโยงจะแสดงรายการฟังก์ชั่นทั้งหมดที่สามารถลบออกได้เพราะไม่เคยถูกเรียกและวงกลมทั้งหมดที่ไม่เคยถูกอ้างอิง

(แน่นอนคุณสามารถข้าม--print-gc-sectionsส่วนและปล่อยให้ตัวเชื่อมโยงลบฟังก์ชั่นอย่างเงียบ ๆ แต่เก็บไว้ในแหล่งที่มา)

หมายเหตุ:นี่จะเป็นการค้นหาฟังก์ชั่นที่ไม่ได้ใช้อย่างสมบูรณ์เท่านั้นมันจะไม่ทำอะไรเกี่ยวกับโค้ดที่ตายแล้วภายในฟังก์ชั่น ฟังก์ชั่นที่เรียกใช้จากรหัสตายในฟังก์ชั่นถ่ายทอดสดจะถูกเก็บไว้รอบ ๆ

คุณลักษณะเฉพาะบางอย่างของ C ++ จะทำให้เกิดปัญหาโดยเฉพาะ:

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

ในทั้งสองกรณีสิ่งที่ใช้โดยฟังก์ชั่นเสมือนหรือตัวสร้างตัวแปรทั่วโลกจะต้องมีการเก็บรอบ

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


คำแนะนำการปฏิบัติที่ยอดเยี่ยม เพียงแค่รับรายการฟังก์ชั่นที่ทราบว่าไม่ควรใช้ที่ใดก็ได้ (แม้ว่าคุณจะพูดว่ารายการนี้ไม่สมบูรณ์) จะได้รับผลไม้แขวนลอยจำนวนมากที่ฉันคิด
j_random_hacker

ฉันไม่คิดว่าสิ่งนี้จะใช้ได้กับเทมเพลตที่ไม่มีการตรวจสอบเลย
Jakub Klinkovský

25

ถ้าคุณใช้ g ++ คุณสามารถใช้แฟล็กนี้ได้ -Wunused

เอกสารอ้างอิง:

เตือนเมื่อใดก็ตามที่ไม่ได้ใช้ตัวแปรนอกเหนือจากการประกาศเมื่อใดก็ตามที่ฟังก์ชั่นจะประกาศคงที่ แต่ไม่เคยกำหนดไว้เมื่อใดก็ตามที่มีการประกาศฉลาก แต่ไม่ได้ใช้และเมื่อใดก็ตามที่คำสั่งคำนวณผลลัพธ์ที่ไม่ได้ใช้อย่างชัดเจน

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

แก้ไข : นี่คือธงที่มีประโยชน์อื่น ๆ-Wunreachable-code ตามเอกสาร:

ตัวเลือกนี้มีไว้เพื่อเตือนเมื่อคอมไพเลอร์ตรวจพบว่าอย่างน้อยบรรทัดซอร์สโค้ดทั้งหมดจะไม่ถูกเรียกใช้เนื่องจากเงื่อนไขบางอย่างไม่เคยพอใจหรือเป็นเพราะหลังจากโพรซีเดอร์ที่ไม่ส่งคืน

อัปเดต : ฉันพบหัวข้อที่คล้ายกันการตรวจหา Dead Code ในโปรเจ็กต์ C / C ++ ดั้งเดิม


4
สิ่งนี้จะไม่จับส่วนหัวของฟังก์ชั่นต้นแบบที่ไม่เคยถูกเรียก หรือวิธีการเรียนสาธารณะที่ไม่ได้รับการเรียก มันสามารถตรวจสอบได้ว่ามีการใช้ตัวแปรที่กำหนดขอบเขตไว้ภายในขอบเขตนั้นหรือไม่
Falmarri

@Falmarri ฉันไม่เคยใช้ธงนี้ ฉันพยายามคิดออกเองว่ารหัสอะไรที่ฉันสามารถหาเจอได้
UmmaGumma

-Wunusedเตือนเกี่ยวกับตัวแปรที่ถูกประกาศ (หรือประกาศและกำหนดในครั้งเดียว) แต่จริงๆแล้วไม่เคยใช้ ค่อนข้างน่ารำคาญกับผู้คุมที่มีขอบเขตโดยวิธี: p มีการทดลองใช้งานใน Clang เพื่อให้เตือนตัวแปรที่ไม่ลบเลือนซึ่งเขียนถึง แต่ไม่เคยอ่านจาก (โดย Ted Kremenek) -Wunreachable-codeเตือนเกี่ยวกับรหัสภายในฟังก์ชั่นที่ไม่สามารถมาถึงก็สามารถเป็นรหัสอยู่หลังจากthrowหรือreturnคำสั่งหรือรหัสในสาขาที่ไม่เคยถ่าย (ซึ่งเกิดขึ้นในกรณีของการเปรียบเทียบซ้ำ) ยกตัวอย่างเช่น
Matthieu M.

18

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

คุณสามารถลองให้โอกาสเครื่องมือครอบคลุมรหัสโอเพนซอร์ซนี้: TestCocoon - เครื่องมือครอบคลุมรหัสสำหรับ C / C ++ และ C #


7
คีย์ที่นี่คือ "ขณะกำลังทำงาน" - หากข้อมูลอินพุตของคุณไม่ใช้เส้นทางรหัสบางอย่างที่เส้นทางนั้นจะไม่ได้รับการยอมรับตามที่ใช้จะเป็นอย่างไร
sharptooth

1
ถูกต้อง. หากไม่มีการเรียกใช้รหัสจะไม่มีทางทราบว่าเส้นใดไม่ถึง ฉันสงสัยว่ามันยากแค่ไหนในการตั้งค่าการทดสอบหน่วยเพื่อเลียนแบบการวิ่งปกติสองสามครั้ง
Carlos V

1
@drishish ฉันคิดว่ารหัสที่ไม่ได้ใช้ส่วนใหญ่นั้นต้องหา linker ไม่ใช่ compiler
UmmaGumma

1
@drhirsch True คอมไพเลอร์สามารถดูแลโค้ดบางอย่างที่ไม่สามารถเข้าถึงได้เช่นฟังก์ชั่นที่ประกาศ แต่ไม่ได้เรียกใช้และการประเมินการลัดวงจร แต่โค้ดที่ขึ้นอยู่กับการกระทำของผู้ใช้หรือตัวแปรเวลา
Carlos V

1
@golcarcol ตกลงเรามามีฟังก์ชั่น void func()ใน a.cpp ซึ่งใช้ใน b.cpp คอมไพเลอร์สามารถตรวจสอบได้อย่างไร, func () นั้นถูกใช้ในโปรแกรม? มันเป็นงาน linkers
UmmaGumma

15

คำตอบที่แท้จริงคือ: คุณไม่สามารถรู้แน่ชัด

อย่างน้อยสำหรับกรณีที่ไม่น่าสนใจคุณไม่สามารถแน่ใจได้ว่าคุณได้รับมาทั้งหมด ลองพิจารณาสิ่งต่อไปนี้จากบทความของ Wikipedia เกี่ยวกับรหัสที่เข้าไม่ถึง :

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

ในฐานะที่เป็นวิกิพีเดียบันทึกอย่างถูกต้องคอมไพเลอร์ที่ฉลาดอาจจับสิ่งนี้ แต่ให้พิจารณาการดัดแปลง:

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

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

คอมไพเลอร์จะจับสิ่งนี้หรือไม่? อาจจะ. แต่การทำเช่นนั้นจะต้องทำมากกว่าวิ่งsqrtเทียบกับค่าสเกลาร์คงที่ มันจะต้องคิดออกว่า(double)yจะเป็นจำนวนเต็ม (ง่าย) แล้วเข้าใจช่วงทางคณิตศาสตร์ของsqrtชุดจำนวนเต็ม (ยาก) คอมไพเลอร์ที่ซับซ้อนมากอาจสามารถทำสิ่งนี้สำหรับsqrtฟังก์ชั่นหรือสำหรับทุกฟังก์ชั่นในmath.hหรือสำหรับฟังก์ชั่นคงที่ซึ่งโดเมนสามารถหาได้ สิ่งนี้มีความซับซ้อนมากและความซับซ้อนนั้นไร้ขอบเขต คุณสามารถเพิ่มเลเยอร์ของความซับซ้อนในคอมไพเลอร์ของคุณได้ แต่จะมีวิธีการแอบดูในโค้ดบางอย่างที่ไม่สามารถเข้าถึงได้สำหรับชุดอินพุตที่กำหนด

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

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

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


1
จริงและอย่าปล่อยรหัสตายใน! หากคุณลบคุณสมบัติฆ่ารหัสตาย การออกจากที่นั่น "ในกรณี" เพียงทำให้เกิดการขยายตัวที่ (ตามที่คุณกล่าวถึง) เป็นการยากที่จะหาในภายหลัง ให้การควบคุมเวอร์ชันทำหน้าที่กักตุนยา
การแข่งขัน Lightness ใน Orbit

12

ฉันไม่ได้ใช้ด้วยตนเอง แต่cppcheckอ้างว่าค้นหาฟังก์ชันที่ไม่ได้ใช้ อาจไม่สามารถแก้ปัญหาได้ทั้งหมด แต่อาจเป็นการเริ่มต้น


ใช่มันสามารถค้นหาท้องถิ่นไม่อ้างอิงตัวแปรและฟังก์ชั่น
Chugaister

ใช่ใช้cppcheck --enable=unusedFunction --language=c++ .เพื่อค้นหาฟังก์ชั่นที่ไม่ได้ใช้เหล่านี้
Jason Harris

9

คุณอาจลองใช้PC-ผ้าสำลี / FlexeLint จาก Gimple ซอฟแวร์ มันอ้างว่า

ค้นหามาโครที่ไม่ได้ใช้คลาสของ typedef สมาชิกการประกาศ ฯลฯ ตลอดทั้งโครงการ

ฉันใช้มันเพื่อการวิเคราะห์แบบสแตติกและพบว่ามันดีมาก แต่ฉันต้องยอมรับว่าฉันไม่ได้ใช้มันเพื่อหารหัสตายโดยเฉพาะ


5

วิธีการปกติของฉันในการค้นหาสิ่งที่ไม่ได้ใช้คือ

  1. ตรวจสอบให้แน่ใจว่าระบบการสร้างจัดการการติดตามการพึ่งพาได้อย่างถูกต้อง
  2. ตั้งค่าจอภาพที่สองโดยมีหน้าต่างเทอร์มินัลแบบเต็มหน้าจอเรียกใช้งานสร้างซ้ำและแสดงผลหน้าจอแรกของเอาต์พุต watch "make 2>&1"มีแนวโน้มที่จะทำเคล็ดลับใน Unix
  3. เรียกใช้การดำเนินการค้นหาและแทนที่บนแผนผังต้นทางทั้งหมดโดยเพิ่ม "//?" ที่จุดเริ่มต้นของทุกบรรทัด
  4. แก้ไขข้อผิดพลาดแรกที่แฟล็กโดยคอมไพเลอร์โดยลบ "//?" ในบรรทัดที่สอดคล้องกัน
  5. ทำซ้ำจนกว่าจะไม่มีข้อผิดพลาด

นี่เป็นกระบวนการที่ค่อนข้างยาว แต่ก็ให้ผลลัพธ์ที่ดี


2
มีบุญ แต่ใช้แรงงานมาก นอกจากนี้คุณต้องทำให้แน่ใจว่าจะไม่ใส่เครื่องหมายข้อคิดเห็นเกินพิกัดทั้งหมดของฟังก์ชั่นในเวลาเดียวกัน - หากมีมากกว่าหนึ่งการใช้งานการยกเลิกการใส่ข้อคิดเห็นที่ไม่ต้องการอาจทำให้การรวบรวมสำเร็จ แต่ส่งผลให้เกิดพฤติกรรมของโปรแกรมที่ไม่ถูกต้อง ใช้ฟังก์ชั่น)
j_random_hacker

ฉันเพียงแค่ประกาศข้อคิดเห็นในขั้นตอนแรก (เกินพิกัดทั้งหมด) และในการทำซ้ำครั้งถัดไปจากนั้นดูว่าคำจำกัดความใดหายไป ด้วยวิธีนี้ฉันสามารถดูว่ามีการใช้งานเกินพิกัดเท่าใด
Simon Richter

@Simon: ที่น่าสนใจในความคิดเห็นเกี่ยวกับคำถามหลัก MSalters ชี้ให้เห็นว่าแม้การมี / ไม่มีการประกาศสำหรับฟังก์ชั่นที่ไม่เคยเรียกว่าสามารถส่งผลกระทบต่อซึ่งอีก 2 ฟังก์ชั่นที่พบโดยการแก้ไขเกินพิกัด เป็นที่ยอมรับว่าต้องมีการตั้งค่าที่แปลกประหลาดและมีการวางแผนอย่างมากดังนั้นจึงไม่น่าเป็นปัญหาในทางปฏิบัติ
j_random_hacker

4

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

ถัดไปคือการทำเครื่องหมายฟังก์ชั่นสาธารณะที่เหลือทั้งหมดและทำการโทรกราฟเพื่อหาความสัมพันธ์ระหว่างคลาส จากต้นไม้ต้นนี้ลองคิดดูว่ากิ่งไหนของกิ่งที่ดูเหมือนจะสามารถเล็มได้

ข้อดีของวิธีนี้คือคุณสามารถทำตามโมดูลได้ดังนั้นจึงง่ายต่อการส่งผ่านสิ่งที่ไม่ควรทำโดยไม่ต้องใช้เวลานานเมื่อคุณมีรหัสฐานที่แตกหัก


3

หากคุณอยู่บน Linux คุณอาจต้องการดูcallgrindเครื่องมือวิเคราะห์โปรแกรม C / C ++ ที่เป็นส่วนหนึ่งของvalgrindชุดเครื่องมือซึ่งมีเครื่องมือที่ตรวจสอบการรั่วไหลของหน่วยความจำและข้อผิดพลาดอื่น ๆ ของหน่วยความจำ (ซึ่งคุณควรใช้ด้วย) มันวิเคราะห์อินสแตนซ์ที่กำลังทำงานของโปรแกรมของคุณและสร้างข้อมูลเกี่ยวกับกราฟการโทรและเกี่ยวกับต้นทุนประสิทธิภาพของโหนดบนกราฟการโทร โดยปกติจะใช้สำหรับการวิเคราะห์ประสิทธิภาพ แต่ยังสร้างกราฟการโทรสำหรับแอปพลิเคชันของคุณเพื่อให้คุณสามารถดูว่ามีการเรียกใช้ฟังก์ชันใดบ้างรวมถึงผู้โทร

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


3

ฉันไม่ได้ใช้เครื่องมือใด ๆ ที่ทำสิ่งนั้นจริงๆ ... แต่เท่าที่ฉันเห็นในคำตอบทั้งหมดไม่มีใครเคยพูดว่าปัญหานี้ไม่สามารถคำนวณได้

สิ่งนี้ฉันหมายถึงอะไร ว่าปัญหานี้ไม่สามารถแก้ไขได้ด้วยอัลกอริทึมใด ๆ ที่เคยมีในคอมพิวเตอร์ ทฤษฎีนี้ (ซึ่งไม่มีอัลกอริธึมดังกล่าว) เป็นข้อพิสูจน์ของปัญหาการหยุดชะงักของทัวริง

เครื่องมือทั้งหมดที่คุณจะใช้ไม่ใช่อัลกอริธึม แต่การวิเคราะห์พฤติกรรม (ไม่ใช่อัลกอริธึมที่แน่นอน) พวกเขาจะไม่ให้รหัสทั้งหมดที่ไม่ได้ใช้กับคุณ


1
ฉันคิดว่า OP ต้องการหาฟังก์ชั่นที่ไม่ได้เรียกจากที่ใดเป็นหลักซึ่งแน่นอนว่าไม่สามารถคำนวณได้ - นักเชื่อมโยงที่ทันสมัยส่วนใหญ่สามารถทำได้! มันเป็นเพียงเรื่องของการดึงข้อมูลนั้นออกมาด้วยความเจ็บปวดและความน่าเบื่อหน่ายน้อยที่สุด
j_random_hacker

คุณพูดถูกฉันไม่เห็นความคิดเห็นสุดท้ายของคำถามหลัก โดยวิธีการอาจมีฟังก์ชั่นที่อ้างถึงในรหัสที่ไม่ได้ใช้จริง สิ่งเหล่านั้นอาจไม่ถูกตรวจจับ
geekazoid

2

วิธีหนึ่งคือใช้ดีบักเกอร์และคุณสมบัติคอมไพเลอร์ในการกำจัดรหัสเครื่องที่ไม่ได้ใช้ในระหว่างการรวบรวม

เมื่อมีการยกเลิกรหัสเครื่องบางส่วนโปรแกรมดีบั๊กจะไม่ยอมให้คุณใส่ breakpojnt ลงในบรรทัดของซอร์สโค้ดที่เกี่ยวข้อง ดังนั้นคุณวางเบรกพอยต์ทุกที่และเริ่มโปรแกรมและตรวจสอบเบรกพอยต์ - ซึ่งอยู่ในสถานะ "ไม่โหลดรหัสสำหรับแหล่งที่มานี้" สอดคล้องกับรหัสที่ตัดออก - ไม่ว่ารหัสนั้นจะไม่ถูกเรียกหรือไม่ได้รับการอินไลน์ การวิเคราะห์เพื่อค้นหาสิ่งที่สองที่เกิดขึ้น

อย่างน้อยนั่นเป็นวิธีการทำงานใน Visual Studio และฉันเดาว่าชุดเครื่องมืออื่น ๆ ก็สามารถทำได้เช่นกัน

นั่นเป็นงานจำนวนมาก แต่ฉันเดาได้เร็วกว่าการวิเคราะห์โค้ดทั้งหมดด้วยตนเอง


4
ฉันคิดว่าคำถามของ OP เกี่ยวกับวิธีการค้นหาเซตย่อยที่เล็กกว่าและจัดการได้มากกว่าของซอร์สโค้ดไม่มากทำให้แน่ใจว่าไบนารีที่คอมไพล์นั้นมีประสิทธิภาพ
j_random_hacker

@j_random_hacker ฉันได้ให้มัน - และเปิดการกำจัดรหัสสามารถใช้สำหรับการติดตามกลับไปยังซอร์สโค้ดเดิม
sharptooth

คุณต้องคอมไพล์แฟล็กเฉพาะบน Visual Studio เพื่อให้บรรลุหรือไม่ และมันจะทำงานเฉพาะในโหมดการเปิดตัวหรือจะทำงานในการแก้ปัญหาด้วยหรือไม่
Naveen

สิ่งที่เกี่ยวกับสายที่ใช้ แต่เพิ่มประสิทธิภาพโดยคอมไพเลอร์?
Itamar Katz

@ Naveen: ใน Visual C ++ 9 คุณต้องเปิดการปรับให้เหมาะสมและใช้ / OPT: ICF
sharptooth

2

CppDependเป็นเครื่องมือเชิงพาณิชย์ที่สามารถตรวจจับชนิดวิธีการและฟิลด์ที่ไม่ได้ใช้และทำอะไรได้อีกมากมาย สามารถใช้ได้กับ Windows และ Linux (แต่ปัจจุบันยังไม่รองรับ 64 บิต) และมาพร้อมกับการทดลอง 2 สัปดาห์

คำเตือน: ฉันไม่ได้ทำงานที่นั่น แต่ฉันเป็นเจ้าของใบอนุญาตสำหรับเครื่องมือนี้ (เช่นเดียวกับNDependซึ่งเป็นทางเลือกที่มีประสิทธิภาพมากขึ้นสำหรับรหัส. NET)

สำหรับผู้ที่มีความอยากรู้อยากเห็นต่อไปนี้เป็นตัวอย่างในตัว (ปรับแต่งได้) กฎสำหรับการตรวจหาวิธีการตายเขียนใน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] }

อัปเดต: มีการเพิ่มการรองรับ 64 บิตสำหรับ Linux ในเวอร์ชัน 3.1
Roman Boiko

1

ขึ้นอยู่กับแพลตฟอร์มที่คุณใช้ในการสร้างแอปพลิเคชันของคุณ

ตัวอย่างเช่นหากคุณใช้ Visual Studio คุณสามารถใช้เครื่องมือเช่น. NET ANTS Profilerซึ่งสามารถแยกวิเคราะห์และกำหนดรหัสของคุณได้ ด้วยวิธีนี้คุณควรรู้ได้อย่างรวดเร็วว่าส่วนใดของรหัสที่คุณใช้ Eclipse ยังมีปลั๊กอินที่เทียบเท่า

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

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


1
.net ANTS Profiler ดูเหมือนว่าสำหรับ C # - คุณแน่ใจหรือว่ามันใช้ได้กับ C ++ ด้วย?
j_random_hacker

@j_random_hacker: ตราบเท่าที่ฉันรู้มันทำงานกับรหัสที่จัดการ ดังนั้น. net ANTS จะไม่สามารถวิเคราะห์รหัส C ++ 'มาตรฐาน' (เช่นคอมไพล์ด้วย gcc, ... )
AUS

0

ฉันไม่คิดว่ามันจะสามารถทำได้โดยอัตโนมัติ

แม้จะมีเครื่องมือครอบคลุมรหัสคุณต้องให้ข้อมูลอินพุตที่เพียงพอในการเรียกใช้

อาจเป็นเครื่องมือวิเคราะห์แบบคงที่ที่ซับซ้อนและมีราคาสูงเช่นจากคอมไพเลอร์ของ CoverityหรือLLVMอาจช่วยได้

แต่ฉันไม่แน่ใจและฉันต้องการตรวจสอบรหัสด้วยตนเอง

ปรับปรุง

ดี .. แค่ลบตัวแปรที่ไม่ได้ใช้ออกไปฟังก์ชันที่ไม่ได้ใช้ก็ไม่ยาก

ปรับปรุง

หลังจากอ่านคำตอบและความคิดเห็นอื่น ๆ ฉันมั่นใจมากขึ้นว่าไม่สามารถทำได้

คุณต้องทราบรหัสเพื่อให้มีมาตรการครอบคลุมโค้ดที่มีความหมายและหากคุณรู้ว่าการแก้ไขด้วยตนเองจำนวนมากจะเร็วกว่าการเตรียม / เรียกใช้ / ตรวจสอบผลลัพธ์การครอบคลุม


2
ถ้อยคำของคำตอบของคุณทำให้เข้าใจผิดไม่มีอะไรที่เกี่ยวกับ LLVM ... ฟรี!
Matthieu M.

การแก้ไขด้วยตนเองจะไม่ช่วยให้คุณมีตัวแปรเวลาทำงานที่ผ่านการผ่านลอจิกสาขาในโปรแกรมของคุณ จะเกิดอะไรขึ้นหากรหัสของคุณไม่ตรงตามเกณฑ์ที่กำหนดดังนั้นจึงเป็นไปตามเส้นทางเดียวกันเสมอ
Carlos V

0

ฉันมีเพื่อนถามคำถามนี้กับฉันในวันนี้และฉันมองไปรอบ ๆ ในการพัฒนา Clang ที่มีแนวโน้มเช่นASTMatcher s และStatic Analyzerที่อาจมีทัศนวิสัยที่เพียงพอในการทำงานในระหว่างการรวบรวมเพื่อกำหนดส่วนของรหัสที่ตายแล้ว พบสิ่งนี้:

https://blog.flameeyes.eu/2008/01/today-how-to-identify-unused-exported-functions-and-variables

มันเป็นคำอธิบายที่สมบูรณ์เกี่ยวกับวิธีใช้ธง GCC สองสามตัวที่ดูเหมือนว่าจะได้รับการออกแบบมาเพื่อจุดประสงค์ในการระบุสัญลักษณ์ที่ไม่มีการอ้างอิง!


0

ปัญหาทั่วไปของถ้าฟังก์ชั่นบางอย่างจะเรียกว่าเป็น NP-Complete คุณไม่สามารถทราบล่วงหน้าด้วยวิธีทั่วไปหากมีการเรียกใช้ฟังก์ชันบางอย่างเนื่องจากคุณจะไม่รู้ว่าเครื่องทัวริงจะหยุดหรือไม่ คุณจะได้รับหากมีเส้นทาง (สแตติก) ที่เปลี่ยนจาก main () ไปยังฟังก์ชันที่คุณเขียน แต่ไม่รับประกันว่าจะถูกเรียกใช้


-3

ถ้าคุณใช้ g ++ คุณสามารถใช้แฟล็กนี้ -Wunused

เอกสารอ้างอิง:

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

แก้ไข: นี่คือธงที่มีประโยชน์อื่น ๆ -Wunreachable-code ตามเอกสาร:

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
ข้อมูลที่แน่นอนนี้ถูกกล่าวถึงแล้วในคำตอบที่ได้รับความนิยมสูงสุดในปัจจุบัน โปรดอ่านคำตอบที่มีอยู่เพื่อหลีกเลี่ยงการทำซ้ำโดยไม่จำเป็น
j_random_hacker

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