سر #include في C++: <file> مقابل "file" — السرعة والتحسين

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: تفصيلة صغيرة قد تكلفك ثوانٍ من التجميع

\n\n

تخيل: أنت تكتب مشروع C++. العمل يتدفق، الكود ينمو، مجلدات الملفات تتكاثر. وفجأة تكتب مرة أخرى #include. كالعادة — بين أقواس زاوية؟ أم بين علامتي اقتباس؟ وما الفرق في النهاية؟ المترجم ذكي، سيجدها بنفسه.

\n\n

إفساد: سيجدها. لكن ليس دائمًا بسرعة. وليس دائمًا الملف الذي قصدته. السؤال حول الفرق بين #include <filename> و #include "filename" هو أحد أكثر الأسئلة شيوعًا على Stack Overflow (مئات الآلاف من المشاهدات). والأمر لا يتعلق فقط بالسكر النحوي. إنه يتعلق بأداء التجميع، والتحكم في التبعيات، وفي النهاية، بجودة كودك.

\n\n

في هذه المقالة، لن نقتصر على سرد القواعد فقط. سننظر تحت غطاء المعالج الأولي، ونحلل أمثلة واقعية، ونقدم نصائح عملية لتسريع بناء مشروعك. هيا بنا!

\n\n

كيف يعمل المعالج الأولي؟ تشريح توجيه #include

\n\n

قبل أن يرى المترجم كودك، يدخل المعالج الأولي في العمل. مهمته هي تحضير "المصدر" للتجميع. توجيه #include هو أمر: "خذ محتوى الملف X وأدرجه هنا مباشرة، بدلاً من هذا السطر".

\n\n

يبدو الأمر بسيطًا. لكن الشيطان، كالعادة، يكمن في التفاصيل. وتحديدًا في أين يبحث المعالج الأولي عن هذا الملف X.

\n\n

الأقواس الزاوية: #include <filename>

\n

عندما تكتب #include <iostream>، ينطلق المعالج الأولي للبحث حصريًا في المسارات النظامية القياسية. في Linux تكون عادةً /usr/include، /usr/local/include والمسارات التي تم تمريرها للمترجم عبر العلم -I (أو /I في Visual Studio).

\n

النقطة الأساسية: المعالج الأولي لا ينظر إلى الدليل الحالي للمشروع. فقط إلى تلك المجلدات التي تم تحديدها له على أنها "نظامية" أو "للبحث عن التضمينات".

\n

تم تصميم هذا عن قصد: لتجنب الالتقاط العرضي لملف محلي بنفس الاسم بدلاً من المكتبة القياسية.

\n\n

علامات الاقتباس المزدوجة: #include "filename"

\n

مع علامات الاقتباس، القصة أكثر تعقيدًا وإثارة للاهتمام. يقول معيار C++ (القسم 16.2): السلوك محدد حسب التنفيذ. لكن عمليًا، 99% من المترجمات (GCC، Clang، MSVC) تعمل وفق مبدأ واحد:

\n
    \n
  1. أولاً البحث في الدليل الذي يوجد فيه الملف المُضمِّن (عادةً — المجلد الحالي مع الملف المصدر).
  2. \n
  3. ثم البحث في نفس المسارات المستخدمة للأقواس الزاوية (النظامية + -I).
  4. \n
\n

أي أن #include "myheader.h" سيتحقق أولاً من المجلد الذي يحتوي على ملف main.cpp الخاص بك. وجده — ممتاز. لم يجده — ينتقل إلى القائمة النظامية.

\n\n

هل هذه مجرد معرفة أم تحسين حقيقي؟

\n

"وماذا في ذلك؟" — قد تسأل. "أجهزة الكمبيوتر سريعة، ما الفرق في مكان البحث؟"

\n

الفرق موجود، ويتكون من عاملين: سرعة البحث و وضوح النوايا.

\n\n

العامل 1: سرعة البحث عن الملفات

\n

في مشروع صغير (10-20 ملفًا) لن تلاحظ الفرق. لكن في منتج مؤسسي حقيقي أو محرك ألعاب، قد يتجاوز عدد التضمينات عشرات الآلاف. في كل مرة تكتب فيها #include "some_system_lib.h"، يضيع المعالج الأولي وقتًا إضافيًا في التحقق من المجلد الحالي وجميع المجلدات الفرعية (حسب التنفيذ).

\n

مثال من الحياة الواقعية: تخيل أن لديك مشروعًا به 10,000 ملف. 20% منهم يستخدمون علامات الاقتباس للمكتبات القياسية. كل تضمين من هذا القبيل يقوم باستدعاء نظام إضافي stat() أو access() في الدليل الحالي. هذه ميكروثانية، لكنها مجتمعة على مستوى المشروع — ثوانٍ حقيقية من التجميع.

\n\n

استخدام #include <iostream> بدلاً من #include "iostream" يضمن أن يبدأ البحث فورًا من المسارات النظامية، متجاوزًا الدليل المحلي. وهذا يعطي

المدونات

توصيات الكتب