الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

ربط ملفات السورس والهيدر

مغلقمُجاب
بدأه المـقـدام في 23 فبراير 2014 · 3 رد · 1,046 مشاهدة · في الأسئلة المجابة
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ورحمة الله وبركاته


 


 


كيف يقوم الكمبيلر بربط ملفات cpp & .h


حيث في أغلب الأحيان يكون هناك ملفات لتعريف دوال الكلاسات


وأيضا يكون ملف لـ MAIN  وجميعهم يستدعوا header file  واحد ؟؟؟


 


 


 


:huh:  :huh:  :huh:  :huh:  :huh:

1

 إن المعلم والطبيب كلاهمــــا ... لا ينصحـان إذا همـا لم يكرمـا 


فاصبر لدائـك إن أهنت طبيبه ... واصبــر لجهلك إن جفوت معلما

#2 أفضل إجابة

ملفات الـheader يأتي دورها في مرحلة التحظير للمعالجة preprocessing، لو كان عندك ثلاث ملفات file1.cpp و file2.cpp و header.h كالتالي:
 

/* header.h */
#ifndef __HEADER_H__
#define __HEADER_H__

void foo(int x);

#endif
/* file1.cpp */
#include "header.h"

int main(int argc, char **argv)
{
  foo(123);
  return 0;
}
/* file2.cpp */
#include <iostream>
#include "header.h"

void foo(int x)
{
  std::cout << "foo(" << x << ")\n";
}

الملف file1.cpp أدرج الملف الرأسي header.h، أثناء هذه المرحلة سيعالج المصرف لغة الـpreprocessing، الأوامر المبدوءة بـ#، سيدرج محتويات الملف المضمن في #include ويستبدل #define بما يقابلها، لينتج في الأخير ملف كهذا (يختلف بين مصرف وآخر):
 

# 1 "file1.cpp"
# 1 "<command-line>"
# 1 "file1.cpp"

# 1 "header.h" 1




void foo(int x);  <- محتوى الملف الرأسي أدرج في نفس الملف
# 3 "file1.cpp" 2

int main(int argc, char **argv)
{
  foo(123);
  return 0;
}

يمكن عرض ناتج هذه المرحلة في gcc باستخدام الخيار E:

g++ -E file1.cpp

لاحظ أن void foo(int x); أدرجت الآن في file2.cpp، باقي تلك الرموز يستخدمها المصرف في المراحل التالية، مثلاً لو كان هناك خطأ سيعرف موقعه ورقم سطره. هنا انتهت مرحلة التحظير للمعالجة وانتهى دور الملفات الرأسية.

 

 

بعد هذه المرحلة تبدأ مرحلة البناء وهي أكثر المراحل تعقيد تجد تفاصيلها في كتب بناء المصرفات، في نهاية هذه المرحلة تحول الوحدة (كل ملف cpp مستقل) إلى تعليمات أسمبلي للمعالج المستهدف، الملف file1.cpp سيتحول إلى:

    .file    "file1.cpp"
    .intel_syntax noprefix
    .def    ___main;    .scl    2;    .type    32;    .endef
    .text
    .globl    _main
    .def    _main;    .scl    2;    .type    32;    .endef
_main:
LFB0:
    .cfi_startproc
    push    ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    mov    ebp, esp
    .cfi_def_cfa_register 5
    and    esp, -16
    sub    esp, 16
    call    ___main
    mov    DWORD PTR [esp], 123    <- تمرير المتغير للدالة
    call    __Z3fooi            <- استدعاءها، الإسم الغريب لتمكين خاصية زيادة التحميل
    mov    eax, 0
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE0:
    .ident    "GCC: (GNU) 4.8.1"
    .def    __Z3fooi;    .scl    2;    .type    32;    .endef

يمكن اظهاره باستخدام الخيار S ويعطيك ملف اسمه file1.S به تعاليمات أسمبلي بصيغة at&t (ويمكنك أيضاً إضافة masm=intel للإظهار التعليمات بصيغة أنتل، كما في الملف السابق):

g++ -S -masm=intel file1.cpp

هنا انتهى دول المصرف.

 

وبدأ دور المجمع (في gcc اسمه gnu assembler واسم الملف as) الذي يحول تعليمات الأسمبلي تلك إلى مايقابها في لغة الآلة (الأرقام التي يفسرها المعالج)، مثلاً التعليمة mov ebx, 123 والتي تنقل الرقم 123 إلى المسجل ebx ستتحول إلى 0xbb 0x7b 0x00 0x00 0x00، فـ0xbb يفهمه المعالج بأنه نقل رقم 32 بت إلى المسجل ebx و 0x7b 0x00 0x00 0x00 رقم هو123 بطول 32 بت (هكس).

 

المجمع سيضع تلك التعليمات داخل مف كائن object file بالإمتداد .o يضم تلك التعليمات التي حولها المجمع للغة الآلة وبعض المعلومات الأخرى،  يمكن القفز لتلك المرحلة عن طريق الخيار c:

g++ -c file2.cpp

لاحظ أنه حتى الآن يتم بناء الملفات file1.cpp و file2.cpp بشكل مستقل عن الآخر، هذا مفيد جداً عند بناء برنامج كبير، فيمكنك مثلاً بناء file1.cpp والحصول على ملف الـobject، طالما أنك لم تغير في file2.cpp فلست بحاجة لإعادة بناءه في كل مرة.

 

بعد هذه المرحلة سينتج الملف file2.o، الخطوات تنطبق أيضاً على file1.cpp، لينتج file2.o، الآن تبدأ مرحلة الربط linking وفيها يلتقي ملفي الـobject الذين نتجا من file1.cpp و file2.cpp، هنا يقوم الـlinker بالتحقق من مواقع الدوال (file1.cpp تستدعي foo والموجودة في الوحدة file2.cpp، الـlinker يتولى التأكد من هذا الشيء)، وكذلك يتولى مسؤولية الربط الثابت أو الديناميكي بالدوال الموجودة في مكتبات خارجية، هذه المرحلة الأخيرة والتي ينتج فيها الملف التنتفيذي للنظام المستهدف.

 

المصرف وبيئات التطوير تخفي كثير من هذه التفاصيل.

 

انظر أيضاً لهذه المقالات:

 

[++C] وحدات اللغة

[++C] مفاهيم أساسية

تم تعديل هذه المشاركة بواسطة Mr.B في 24 فبراير 2014 في 16:46

4
#3

ماشاء الله اخ Mr.B

اقتباس

 

لاحظ أنه حتى الآن يتم بناء الملفات file1.cpp و file2.cpp بشكل مستقل عن الآخر، هذا مفيد جداً عند بناء برنامج كبير، فيمكنك مثلاً بناء file1.cpp والحصول على ملف الـobject، طالما أنك لم تغير في file2.cpp فلست بحاجة لإعادة بناءه في كل مرة.

هل تقصد ان المصرف سيقوم بهذا اوتوماتيكيا . اي انه لن يقوم بترجمة وحدة اذا كانت مترجمة من قبل ؟ .

#4

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

لو كان لديك ملف كبير اسمه large.cpp ولاتغير عليه، لاحاجة لأن تعيد بناءه في كل مرة، يمكنك بناءه مرة واحدة وتوليد ملف object عن طريق الخيار c في gcc:

g++ -Wall -c large.cpp

الآن يمكنك أن تعمل linking فقط مع الملف الناتج large.o، وهذا لايأخذ وقت:

g++ -Wall file.cpp large.o

تم تعديل هذه المشاركة بواسطة Mr.B في 25 فبراير 2014 في 15:35

1

هذا الموضوع مغلق.

مواضيع مشابهة