كيف يمكنني اكتشاف ملفات #include غير الضرورية في مشروع C++ كبير؟

StackOverflow https://stackoverflow.com/questions/74326

سؤال

أنا أعمل على مشروع C++ كبير في Visual Studio 2008، وهناك الكثير من الملفات غير الضرورية #include التوجيهات.في بعض الأحيان #includes هي مجرد قطع أثرية وسيتم تجميع كل شيء بشكل جيد مع إزالتها، وفي حالات أخرى يمكن إعادة توجيه الفئات ويمكن نقل #include إلى .cpp ملف.هل هناك أي أدوات جيدة للكشف عن هاتين الحالتين؟

هل كانت مفيدة؟

المحلول

على الرغم من أنه لن يكشف عن الملفات المضمنة غير الضرورية، إلا أن Visual Studio لديه إعداد /showIncludes (انقر بزر الماوس الأيمن على أ .cpp ملف، Properties->C/C++->Advanced) والتي ستنتج شجرة لجميع الملفات المضمنة في وقت الترجمة.يمكن أن يساعد هذا في تحديد الملفات التي لا ينبغي تضمينها.

يمكنك أيضًا إلقاء نظرة على لغة pimpl للسماح لك بالابتعاد عن عدد أقل من تبعيات ملف الرأس لتسهيل رؤية الأشياء الصعبة التي يمكنك إزالتها.

نصائح أخرى

لينت الكمبيوتر يعمل بشكل جيد جدًا لهذا الغرض، ويجد لك أيضًا جميع أنواع المشاكل الأخرى.يحتوي على خيارات سطر الأوامر التي يمكن استخدامها لإنشاء أدوات خارجية في Visual Studio، لكنني وجدت أن الوبر البصري الوظيفة الإضافية أسهل في العمل معها.حتى الإصدار المجاني من Visual Lint يساعد.لكن امنح PC-Lint فرصة.يستغرق تكوينه بحيث لا يعطيك الكثير من التحذيرات بعض الوقت، ولكنك ستندهش مما سيظهر.

هناك أداة جديدة تعتمد على Clang، تضمين ما تستخدمه, ، والتي تهدف إلى القيام بذلك.

!!تنصل!!أنا أعمل على أداة تحليل ثابتة تجارية (وليس PC Lint).!!تنصل!!

هناك العديد من المشكلات المتعلقة بأسلوب بسيط غير تحليلي:

1) مجموعات الزائد:

من الممكن أن تحتوي الوظيفة المحملة بشكل زائد على إعلانات تأتي من ملفات مختلفة.قد يؤدي إزالة ملف رأس واحد إلى اختيار تحميل زائد مختلف بدلاً من خطأ في الترجمة!وستكون النتيجة تغييرًا صامتًا في الدلالات التي قد يكون من الصعب جدًا تعقبها بعد ذلك.

2) تخصصات القالب:

كما هو الحال في مثال التحميل الزائد، إذا كانت لديك تخصصات جزئية أو صريحة لقالب ما، فأنت تريد أن تكون جميعها مرئية عند استخدام القالب.قد تكون تخصصات القالب الأساسي موجودة في ملفات رأس مختلفة.لن تؤدي إزالة الرأس مع التخصص إلى حدوث خطأ في الترجمة، ولكنها قد تؤدي إلى سلوك غير محدد إذا تم تحديد هذا التخصص.(يرى: رؤية تخصص القالب لوظيفة C++)

كما أشار "msalters"، فإن إجراء تحليل كامل للكود يسمح أيضًا بتحليل استخدام الفصل.من خلال التحقق من كيفية استخدام فئة من خلال مسار معين للملفات، من الممكن إزالة تعريف الفئة (وبالتالي جميع تبعياتها) بالكامل أو على الأقل نقلها إلى مستوى أقرب إلى المصدر الرئيسي في التضمين شجرة.

لا أعرف أيًا من هذه الأدوات، وقد فكرت في كتابة واحدة في الماضي، لكن اتضح أن هذه مشكلة يصعب حلها.

لنفترض أن الملف المصدر يتضمن a.h وb.h؛أ.ح يحتوي #define USE_FEATURE_X ويستخدم ب.ح #ifdef USE_FEATURE_X.لو #include "a.h" بعد التعليق، قد يستمر تجميع ملفك، لكنه قد لا يقوم بما تتوقعه.الكشف عن هذا برمجيا غير تافهة.

مهما كانت الأداة التي تستخدمها، فستحتاج إلى معرفة بيئة البناء الخاصة بك أيضًا.إذا كانت a.h تبدو مثل:

#if defined( WINNT )
   #define USE_FEATURE_X
#endif

ثم USE_FEATURE_X يتم تعريفه فقط إذا WINNT تم تعريفها، لذلك ستحتاج الأداة إلى معرفة التوجيهات التي تم إنشاؤها بواسطة المترجم نفسه بالإضافة إلى التوجيهات المحددة في أمر الترجمة بدلاً من ملف الرأس.

مثل Timmermans، لست على دراية بأي أدوات للقيام بذلك.لكنني أعرف مبرمجين كتبوا برنامج Perl (أو Python) لمحاولة التعليق على كل سطر يتضمن واحدًا تلو الآخر ثم تجميع كل ملف.


يبدو أن الآن إريك ريموند لديه أداة لهذا.

جوجل cpplint.py لديه قاعدة "تضمين ما تستخدمه" (من بين العديد من القواعد الأخرى)، ولكن بقدر ما أستطيع أن أقول، لا يوجد "تضمين" فقط ما تستخدمه." ومع ذلك، يمكن أن يكون مفيدًا.

إذا كنت مهتمًا بهذا الموضوع بشكل عام، فقد ترغب في الاطلاع على كتاب لاكوس تصميم برامج C++ واسعة النطاق.إنه قديم بعض الشيء، ولكنه يتناول الكثير من مشكلات "التصميم المادي" مثل العثور على الحد الأدنى المطلق من الرؤوس التي يجب تضمينها.لم أر حقًا هذا النوع من الأشياء تمت مناقشته في أي مكان آخر.

يعطي تضمين مدير محاولة.إنه يتكامل بسهولة في Visual Studio ويصور مسارات التضمين الخاصة بك مما يساعدك في العثور على الأشياء غير الضرورية.داخليًا يستخدم Graphviz ولكن هناك العديد من الميزات الرائعة.وعلى الرغم من أنه منتج تجاري إلا أن سعره منخفض جدًا.

يمكنك إنشاء رسم بياني يتضمن باستخدام يتضمن C/C++ مراقب تبعيات الملفات, ، وابحث عن العناصر غير الضرورية بصريًا.

إذا كانت ملفات رأسك تبدأ عمومًا بـ

#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 الذي يتسبب في جلب الرأس عدة مرات.

يمكن لـ PC-Lint القيام بذلك بالفعل.إحدى الطرق السهلة للقيام بذلك هي تكوينه لاكتشاف ملفات التضمين غير المستخدمة وتجاهل كافة المشكلات الأخرى.يعد هذا أمرًا واضحًا ومباشرًا - لتمكين الرسالة 766 فقط ("ملف الرأس غير مستخدم في الوحدة النمطية")، ما عليك سوى تضمين الخيارات -w0 +e766 في سطر الأوامر.

يمكن أيضًا استخدام نفس الأسلوب مع الرسائل ذات الصلة مثل 964 ("ملف الرأس غير مستخدم بشكل مباشر في الوحدة النمطية") و966 ("ملف الرأس المضمن بشكل غير مباشر غير المستخدم في الوحدة النمطية").

FWIW لقد كتبت عن هذا بمزيد من التفصيل في منشور مدونة الأسبوع الماضي على http://www.riverblade.co.uk/blog.php?archive=2008_09_01_archive.xml#3575027665614976318.

إذا كنت تتطلع إلى إزالة غير الضرورية #include من أجل تقليل أوقات الإنشاء، قد يكون من الأفضل إنفاق وقتك وأموالك بالتوازي مع عملية الإنشاء باستخدام cl.exe /MP, جعل -ي, زوريكس إنكريدي بيلد, ، ديسكك/بوظة, ، إلخ.

بالطبع، إذا كانت لديك بالفعل عملية بناء متوازية وما زلت تحاول تسريعها، فقم بتنظيف جهازك بكل الوسائل #include التوجيهات وإزالة تلك التبعيات غير الضرورية.

ابدأ بكل ملف تضمين، وتأكد من أن كل ملف تضمين يتضمن فقط ما هو ضروري لتجميع نفسه.يمكن إضافة أي ملفات تضمين مفقودة لملفات C++ إلى ملفات C++ نفسها.

بالنسبة لكل ملف تضمين وملف مصدر، قم بالتعليق على كل ملف تضمين واحدًا تلو الآخر ومعرفة ما إذا كان سيتم تجميعه أم لا.

من الجيد أيضًا فرز ملفات التضمين أبجديًا، وإذا لم يكن ذلك ممكنًا، قم بإضافة تعليق.

ستستبعد إضافة واحدة أو كلاهما من ملفات #Defines التالية ملفات رأس غير ضرورية في كثير من الأحيان وقد تعمل بشكل كبير على تحسين أوقات الترجمة خاصةً إذا كان الرمز الذي لا يستخدم وظائف Windows API.

#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN

يرى http://support.microsoft.com/kb/166474

إذا لم تكن تفعل ذلك بالفعل، فإن استخدام رأس مجمع مسبقًا ليشمل كل شيء لن تغيره (رؤوس النظام الأساسي، أو رؤوس SDK الخارجية، أو الأجزاء الثابتة المكتملة بالفعل من مشروعك) سيحدث فرقًا كبيرًا في أوقات الإنشاء.

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

أيضًا، على الرغم من أن الوقت قد فات لمشروعك، فإن تنظيم مشروعك إلى أقسام وعدم تجميع كل الرؤوس المحلية في رأس رئيسي واحد كبير يعد ممارسة جيدة، على الرغم من أن الأمر يتطلب القليل من العمل الإضافي.

إذا كنت ستعمل مع Eclipse CDT فيمكنك تجربتها http://includator.com لتحسين هيكل التضمين الخاص بك.ومع ذلك، قد لا يعرف Includator ما يكفي عن تضمينات VC++ المحددة مسبقًا ولم يتم تضمين إعداد CDT لاستخدام VC++ مع التضمينات الصحيحة في CDT حتى الآن.

يُظهر أحدث إصدار من Jetbrains IDE، CLion، تلقائيًا (باللون الرمادي) التضمينات غير المستخدمة في الملف الحالي.

من الممكن أيضًا الحصول على قائمة بجميع العناصر غير المستخدمة (وكذلك الوظائف والأساليب وما إلى ذلك) من IDE.

تشير بعض الإجابات الموجودة إلى أن الأمر صعب.هذا صحيح بالفعل، لأنك تحتاج إلى مترجم كامل لاكتشاف الحالات التي يكون فيها الإعلان الأمامي مناسبًا.لا يمكنك تحليل لغة C++ دون معرفة معنى الرموز؛القواعد ببساطة غامضة للغاية لذلك.يجب أن تعرف ما إذا كان اسم معين يسمي فئة (يمكن الإعلان عنها للأمام) أو متغيرًا (لا يمكن).أيضًا، يجب أن تكون على دراية بمساحة الاسم.

ربما يكون ذلك متأخرًا بعض الشيء، لكنني عثرت ذات مرة على برنامج WebKit Perl النصي الذي فعل ما أردته تمامًا.أعتقد أنها ستحتاج إلى بعض التكيف (لست على دراية جيدة بلغة Perl)، ولكن يجب أن تؤدي المهمة:

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

(هذا فرع قديم لأن الجذع لم يعد يحتوي على الملف بعد الآن)

إذا كان هناك رأس معين تعتقد أنه لم يعد هناك حاجة إليه (على سبيل المثال string.h) ، يمكنك التعليق الذي يتضمن ذلك ، ثم وضع هذا ما يقل عن:

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

بالطبع قد تستخدم رؤوس الواجهة الخاصة بك اتفاقية #Define مختلفة لتسجيل إدراجها في ذاكرة CPP.أو لا اتفاقية ، وفي هذه الحالة لن يعمل هذا النهج.

ثم أعد البناء.هناك ثلاثة احتمالات:

  • إنه يبني بشكل جيد.لم يكن string.h ترجمة حرجة ، ويمكن إزالته لتضمينه.

  • رحلات #الخطأ.تم تضمين string.g بشكل غير مباشر بطريقة ما لا تزال لا تعرف ما إذا كان string.h مطلوب.إذا كان ذلك مطلوبًا ، فيجب عليك مباشرة تتكامله (انظر أدناه).

  • تحصل على بعض الأخطاء الأخرى في التجميع.كانت هناك حاجة إلى string.h ولم يتم تضمينها بشكل غير مباشر ، لذلك كان التضمين صحيحًا في البداية.

لاحظ أنه اعتمادًا على التضمين غير المباشر عندما يستخدمك .H أو .C بشكل مباشر. H هو بالتأكيد خطأ:أنت في الواقع وعد بأن التعليمات البرمجية الخاصة بك سوف تتطلب فقط رأسك طالما أن هناك رأسًا آخر تستخدمه يتطلب ذلك ، وهو ما لا تعنيه على الأرجح.

التحذيرات المذكورة في إجابات أخرى حول الرؤوس التي تعدل السلوك بدلاً من إعلان الأشياء التي تسبب فشل البناء هنا أيضًا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top