رحلة الكود من c/c++ إلي لغة الألة

ahmed x86
0

عندما تبداء فى برمجة برنامج وانت جديد فى البرمجة فإن اول اختيار دائما هو c++

وهذا لعدة اسباب منها انها لغة فيها معظظم مفاهيم البرمجة

ولكن عندما كنت تضغط على زر run هل فكرت يوما ماذا يحدث خلف الكواليس؟

في هذا المقال نشرح كيف يتحول كلام مفهوم فى لغة البشر الى نبضات كهرائية يفهمها المعالج


لكن قبل ان نبداء

لنفهم كيف يعمل الكومبايلر حتى تفهم المقال

1. المعالج التمهيدي (Pre-Processor)

هذه هي مرحلة "التنظيف والتحضير". هنا يبحث البرنامج عن كل سطر يبدأ بعلامة #.

  • إذا وجد #include <iostream>، يقوم بنسخ محتوى ملف المكتبة وضعه داخل كودك.
  • إذا وجد #define (ماكرو)، يقوم باستبدال الكلمات بالقيم المحددة لها.
  • النتيجة: ملف كود ضخم جداً وجاهز للترجمة الفعلية.

2. المترجم (Compiler)

هنا يبدأ الذكاء الحقيقي. يأخذ الكود "المُحضّر" ويحوله إلى لغة الـ Assembly (التي عرضناها في المقال).

  • في هذه المرحلة، يتم التأكد من صحة القواعد (Syntax Check).
  • يتم عمل "تحسينات" (Optimizations) لجعل الكود يعمل بأسرع شكل ممكن على المعالج.

3. المُجمع (Assembler)

المعالج لا يفهم كلمات مثل mov أو push التي نراها في الـ Assembly، هو يفهم فقط أرقاماً ثنائية (0 و 1).

  • وظيفة الـ Assembler هي تحويل شفرة الأسمبلي إلى Machine Code (لغة الآلة).
  • النتيجة: ملف يُسمى Object File (غالباً ينتهي بـ .o)؛ وهو ملف يحتوي على لغة آلة لكنه لا يزال غير قابل للتشغيل بمفرده.

4. الرابط (Linker)

هذه هي المحطة الأخيرة. برنامجك الآن عبارة عن "قطع غيار" (ملفات .o الخاصة بك + مكتبات اللغة مثل cout).

  • الـ Linker يقوم بجمع كل هذه القطع وربطها ببعضها البعض.
  • يقوم بتحديد العناوين الذاكرية الصحيحة لكل دالة.
  • النتيجة: يخرج لك الملف التنفيذي النهائي (مثل main أو main.exe) الذي يمكنك تشغيله.

فى البداية لنكتب كود c++ بسيط جدا

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

هذا الكود بسيط وكلنا نعرفه لكن كيف علميا يحول زر run هذا الكلام للغة الكمبيوتر
الزر يكمل فى الكومبايلر
وهو برنامج غالبا يكون cli حتي يكون سهل دمجه فى ادوات اخري
لو فتحت تيرمنال فى نفس الملجلد وكتبت

g++ "main.cpp" -o "main"

ستجد ان ظهر ملف جديد لو كنت windows سيكون اسمه main.exe ولو كنت linux او mac سيظهر اسمه main
لتشغيل البرنامج نكتب

./"main"

او لو ويندوز

./"main.exe"

ستظهر النتيجة

Hello, World!

لان هذا الملف مكتوب بلغة الالة حيث قام g++ وهو حزمة تابعة للgcc بتحويل الكود الى لغة الالة
لنفهم الكود
ان g++ ترمز الى برنامج نوعه كومبايلر مهمته ان يحول الكود الي لغة الالة لكن يجب ان يعرف ماهو اسم الملف وماذا يفعل
اذن كتبنا g++ "main.cpp" -o وهذا يرمز الى انه سيعمل على الملف ويخرجه binary
واذا اكملنا فانه يحتاج اسم للملف لذالك وضعنا له نفس الاسم
صار

g++ "main.cpp" -o "main"

لكن بالطبع له وظائف اكثر من هذى
فااا مثلا

g++ -S -masm=intel -fverbose-asm "main.cpp"

يقوم هنا بإخراج الملف كملف بلغة assembly مباشرة تحديدا لتعليمات intel
وتكون النتيجة

    .file    "main.cpp"
    .intel_syntax noprefix
# GNU C++17 (GCC) version 15.2.1 20260209 (x86_64-pc-linux-gnu)
#    compiled by GNU C version 15.2.1 20260209, GMP version 6.3.0, MPFR version 4.2.2, MPC version 1.3.1, isl version isl-0.27-GMP

# warning: MPC header version 1.3.1 differs from library version 1.4.0.
# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
# options passed: -masm=intel -mtune=generic -march=x86-64
    .text
#APP
    .globl _ZSt21ios_base_library_initv
    .section    .rodata
.LC0:
    .string    "Hello, World!"
#NO_APP
    .text
    .globl    main
    .type    main, @function
main:
.LFB1976:
    .cfi_startproc
    push    rbp    #
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov    rbp, rsp    #,
    .cfi_def_cfa_register 6
# main.cpp:5:     cout << "Hello, World!" << endl;
    lea    rdx, .LC0[rip]    # tmp101,
    lea    rax, _ZSt4cout[rip]    # tmp102,
    mov    rsi, rdx    #, tmp101
    mov    rdi, rax    #, tmp102
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT    #
# main.cpp:5:     cout << "Hello, World!" << endl;
    mov    rdx, QWORD PTR _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL[rip]    # tmp104,
    mov    rsi, rdx    #, tmp103
    mov    rdi, rax    #, _1
    call    _ZNSolsEPFRSoS_E@PLT    #
# main.cpp:6:     return 0;
    mov    eax, 0    # _6,
# main.cpp:7: }
    pop    rbp    #
    .cfi_def_cfa 7, 8
    ret    
    .cfi_endproc
.LFE1976:
    .size    main, .-main
    .ident    "GCC: (GNU) 15.2.1 20260209"
    .section    .note.GNU-stack,"",@progbits

وهناك ايضا ان يكون بتعليمات AT&T
الامر سوف يكون هكذا

g++ -S "main.cpp"

لكن النتيجة

    .file    "main.cpp"
    .text
#APP
    .globl _ZSt21ios_base_library_initv
    .section    .rodata
.LC0:
    .string    "Hello, World!"
#NO_APP
    .text
    .globl    main
    .type    main, @function
main:
.LFB1976:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rdx
    leaq    _ZSt4cout(%rip), %rax
    movq    %rdx, %rsi
    movq    %rax, %rdi
    call    _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
    movq    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
    movq    %rdx, %rsi
    movq    %rax, %rdi
    call    _ZNSolsEPFRSoS_E@PLT
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1976:
    .size    main, .-main
    .ident    "GCC: (GNU) 15.2.1 20260209"
    .section    .note.GNU-stack,"",@progbits

طب ما رائيك بتحويل الكود هذا الى لغة الالة
لنبداء بكود intel

as main.s -o main.o

هذا حول الكود الى أشلاء
نحتاج نخليها لنظام التشغيل

g++ main.o -o main

ولو حاولنا تشغيل البرنامج ستكون النتيجة

Hello, World!

لنجرب تعليمات AT&T
لتحويل كود assembly AT&T هذا نكتب

as main.s -o main.o

ثم

g++ main.o -o main

ولو حاولنا تشغيل البرنامج ايضا ب ./"main"
ستكون النتيجة المتوقعة هي

Hello, World!

اذا كان هذا مع لغة c++ باستخدام g++ فإن الامر مع لغة c بإستخادم gcc يعمل بنفس الطريقة تقريبا

لنفرض انك كتبت كود اسمه index.c يحتوي على

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

وارادته تجميعه
فقط تكتب

gcc "index.c" -o "index"

وسيظهر ملف جديد بإسم index
لتشغيله تكتب

./"index"

ستظهر النتيجة

Hello, World!

و اذا اتحويله للغة assembly بتعليمات intel
فقط ستكتب gcc -S -masm=intel -fverbose-asm "index.c"
سيظهر ملف index.s يحتوي على

    .file    "index.c"
    .intel_syntax noprefix
# GNU C23 (GCC) version 15.2.1 20260209 (x86_64-pc-linux-gnu)
#    compiled by GNU C version 15.2.1 20260209, GMP version 6.3.0, MPFR version 4.2.2, MPC version 1.3.1, isl version isl-0.27-GMP

# warning: MPC header version 1.3.1 differs from library version 1.4.0.
# GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
# options passed: -masm=intel -mtune=generic -march=x86-64
    .text
    .section    .rodata
.LC0:
    .string    "Hello, World!"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    push    rbp    #
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    mov    rbp, rsp    #,
    .cfi_def_cfa_register 6
# index.c:4:     printf("Hello, World!\n");
    lea    rax, .LC0[rip]    # tmp100,
    mov    rdi, rax    #, tmp100
    call    puts@PLT    #
# index.c:5:     return 0;
    mov    eax, 0    # _3,
# index.c:6: }
    pop    rbp    #
    .cfi_def_cfa 7, 8
    ret    
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 15.2.1 20260209"
    .section    .note.GNU-stack,"",@progbits

ونفس الكلام مثل كود c++
ولو اردت بتعليمات AT&T تكتب gcc -S "index.c"
سيظهر ملف index.s
يحتوي على

    .file    "index.c"
    .text
    .section    .rodata
.LC0:
    .string    "Hello, World!"
    .text
    .globl    main
    .type    main, @function
main:
.LFB0:
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    leaq    .LC0(%rip), %rax
    movq    %rax, %rdi
    call    puts@PLT
    movl    $0, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size    main, .-main
    .ident    "GCC: (GNU) 15.2.1 20260209"
    .section    .note.GNU-stack,"",@progbits

ونفس الامر كذالك للربط
اذا اعجبك هذا بالطبع سيعجبك كومبايلر اسمه tcc تحدثنا عنه فى المقال (هذا)


⚡ الوحش الصغير: TCC (Tiny C Compiler)

إذا كان GCC هو الشاحنة العملاقة التي تحمل كل شيء، فإن TCC هو الدراجة النارية النفاثة. هذا الكومبايلر ليس فقط صغيراً (حجمه أقل من 1 ميجابايت)، بل هو أسرع بمئات المرات في عملية التجميع (Compilation).

لماذا قد تهتم بـ TCC؟
لأنه صُمم ليكون بسيطاً وسريعاً لدرجة تجعلك تستخدم لغة C كأنها لغة نصية (Scripting Language).

1. ميزة التشغيل الفوري (Run on the fly)

في GCC كنا نمر بمراحل: Compile ثم Link ثم Run. في TCC يمكنك عمل كل ذلك في أمر واحد دون إنتاج ملف تنفيذي حتى:

tcc -run index.c

بمجرد الضغط على Enter، ستظهر لك النتيجة فوراً. هو يقوم بعملية الـ Compilation في الذاكرة (RAM) ويشغل الكود مباشرة.

2. السرعة الخرافية

بينما يقوم GCC بعمليات تحسين (Optimization) معقدة تأخذ وقتاً، TCC يركز على تحويل الكود للغة الآلة بأقصر طريق ممكن. جرب أن تقوم بتجميع مشروع كبير بـ GCC ثم بـ TCC، ستنبهر بالفرق في الوقت.

3. استخدام C كـ Script

تخيل أنك تستطيع كتابة "Shebang" في بداية ملف الـ C الخاص بك ليصبح ملفاً تنفيذياً مباشراً في لينكس:

#!/usr/bin/env tcc -run
#include <stdio.h>
int main() {
    printf("I am a C script!\n");
    return 0;
}

بعد حفظ الملف وإعطائه صلاحية التنفيذ (chmod +x index.c)، يمكنك تشغيله بكتابة ./index.c فقط!

إرسال تعليق

0 تعليقات

إرسال تعليق (0)

#buttons=(اوافق) #days=(20)

موقعنا يستخدم ملفات تعريف الارتباط (Cookies) لتحسين تجربتك. تحقق الآن
Ok, Go it!