Compilation Process in C
กระบวนการคอมไพล์ในภาษา C จะเปลี่ยนโค้ดที่มนุษย์อ่านได้ให้อยู่ในรูปแบบที่เครื่องอ่านได้ สำหรับภาษาซี มันเกิดขึ้นก่อนที่โปรแกรมจะเริ่มดำเนินการเพื่อตรวจสอบไวยากรณ์และความหมายของโค้ด
กระบวนการคอมไพล์ในภาษา C ประกอบด้วยสี่ขั้นตอน: 1.การประมวลผลล่วงหน้า 2. การคอมไพล์ 3.การประกอบ 4.การเชื่อมโยง จากนั้นเราเรียกใช้ไฟล์ปฏิบัติการที่ได้รับเพื่อให้ได้ผลลัพธ์บนหน้าจอ
Compilation คืออะไร?
ก่อนดำดิ่งสู่ความหมายดั้งเดิมของ Compilation ลองพิจารณาตัวอย่างที่มีคนกพูดภาษาฮินดีและ คนกต้องการคุยกับขที่รู้แค่ภาษาอังกฤษดังนั้นตอนนี้คนใดคนหนึ่งจึงต้องการล่ามเพื่อแปล คำที่จะสื่อสารกัน กระบวนการนี้เรียกว่าการแปลหรือในแง่ของการเขียนโปรแกรมเรียกว่ากระบวนการ Compilation
กระบวนการคอมไพล์ในภาษา C กำลังแปลงรหัสของมนุษย์ที่เข้าใจได้ให้เป็นรหัสที่เข้าใจได้ของคอมพิวเตอร์ และตรวจสอบไวยากรณ์และความหมายของโค้ดเพื่อระบุข้อผิดพลาดทางไวยากรณ์หรือคำเตือนใดๆ ที่มีอยู่ในโปรแกรม C ของเรา สมมติว่าเราต้องการรันโปรแกรม C ของเราที่เขียนใน IDE (Integrated Development Environment) ในกรณีนั้น จะต้องผ่านขั้นตอนต่างๆ ของ Compilation (การแปล) เพื่อให้กลายเป็นไฟล์ปฏิบัติการที่คอมพิวเตอร์สามารถเข้าใจได้
Compilation ในภาษา C ประกอบด้วย 4 ขั้นตอนคือ
- Preprocessing (การประมวลผลล่วงหน้า)
- Compiling (กำลังรวบรวม)
- Assembling (การประกอบ)
- Linking (การเชื่อมโยง)
The Compilation Process in C
a. Pre-Processing
การประมวลผลล่วงหน้าเป็นขั้นตอนแรกในกระบวนการคอมไพล์ในภาษา C ที่ดำเนินการโดยใช้เครื่องมือตัวประมวลผลล่วงหน้า (โปรแกรมที่เขียนไว้ล่วงหน้าจะเรียกใช้โดยระบบในระหว่างการคอมไพล์) คำสั่งทั้งหมดที่ขึ้นต้นด้วย สัญลักษณ์ #ในโปรแกรม C จะถูกประมวลผลโดยตัวประมวลผลล่วงหน้า และจะแปลงไฟล์โปรแกรมของเราเป็นไฟล์ระดับกลางโดยไม่มีคำสั่ง# ภายใต้งานก่อนการประมวลผลต่อไปนี้จะดำเนินการ:
i. Comments Removal
comment in C ความคิดเห็นในโปรแกรม C จะใช้เพื่อให้ความคิดทั่วไปเกี่ยวกับคำสั่งเฉพาะหรือส่วนหนึ่งของรหัสจริง ๆ ความคิดเห็นเป็นส่วนหนึ่งของรหัสที่ถูกลบออกระหว่างกระบวนการรวบรวมโดยตัวประมวลผลล่วงหน้าเนื่องจากไม่ได้ใช้งานเฉพาะสำหรับเครื่อง . ความคิดเห็นในโปรแกรมด้านล่างจะถูกลบออกจากโปรแกรมเมื่อขั้นตอนก่อนการประมวลผลเสร็จสิ้น
/* This is a
multi-line comment in C */
#include<stdio.h>
int main()
{
// this is a single-line comment in C
return 0;
}
ii. Macros Expansion
Macros มาโครคือค่าคงที่หรือนิพจน์ที่กำหนดโดยใช้ คำสั่ง #defineในภาษา C การเรียกมาโครนำไปสู่การขยายมาโคร ตัวประมวลผลล่วงหน้าจะสร้างไฟล์ระดับกลางโดยที่คำสั่งระดับการประกอบที่เขียนไว้ล่วงหน้าบางส่วนจะแทนที่นิพจน์หรือค่าคงที่ที่กำหนดไว้ (โดยทั่วไปแล้วจะเป็นการจับคู่โทเค็น) เพื่อแยกความแตกต่างระหว่างคำสั่งดั้งเดิมและคำสั่งการประกอบที่เกิดจากการขยายมาโคร เครื่องหมาย’+’จะถูกเพิ่มลงในคำสั่งที่ขยายมาโครทุกอัน
ตัวอย่างมาโคร:
การกำหนดค่า
#define G 9.8
การกำหนดนิพจน์
#define SUM(a,b) (a + b)
iii. File inclusion
การรวมไฟล์ในภาษา C เป็นการเพิ่มไฟล์ อื่น ที่มีโค้ดที่เขียนไว้ล่วงหน้าบางส่วนในโปรแกรม C ของเราในระหว่างการประมวลผลล่วงหน้า ทำได้โดยใช้คำสั่ง #include การรวมไฟล์ระหว่างการประมวลผลล่วงหน้าทำให้เนื้อหาทั้งหมดของชื่อไฟล์ถูกเพิ่มลงในซอร์สโค้ด แทนที่ คำสั่ง #include เพื่อสร้างไฟล์ระดับกลางใหม่
ตัวอย่าง: หากเราต้องใช้ฟังก์ชัน อินพุต/เอาต์พุตพื้นฐาน เช่น printf() และ scanf() ในโปรแกรม C ของเรา เราต้องรวมไฟล์ส่วนหัวเอาต์พุตอินพุตมาตรฐาน ที่กำหนดไว้ ล่วงหน้าเช่น stdio.h
#include <stdio.h>
iv. Conditional Compilation
การคอมไพล์แบบมีเงื่อนไขกำลังทำงานหรือหลีกเลี่ยงบล็อกของโค้ดหลังจากตรวจสอบว่ามีการ กำหนด มาโครหรือไม่ (ค่าคงที่หรือนิพจน์ที่กำหนดโดยใช้ #define ) ตัวประมวลผลล่วงหน้าจะแทนที่คำสั่งการคอมไพล์แบบมีเงื่อนไขทั้งหมดด้วยโค้ดแอสเซมบลีที่กำหนดไว้ล่วงหน้าบางส่วน และส่งไฟล์ที่ขยายใหม่ไปยังคอมไพเลอร์ การคอมไพล์แบบมีเงื่อนไขสามารถทำได้โดยใช้คำสั่งเช่น #ifdef , #endif , #ifndef , #if , #else และ #elif ในโปรแกรม C ตัวอย่าง :
- การพิมพ์ มาโคร AGEหาก กำหนดมาโคร AGE ไว้ มิฉะนั้นจะพิมพ์ Not Definedและสิ้นสุดบล็อกการคอมไพล์แบบมีเงื่อนไขด้วยคำสั่ง #endif
#include <stdio.h>
// if we uncomment the below line, then the program will print AGE in the output.
// #define AGE 18
int main()
{
// if `AGE` is defined then print the `AGE` else print "Not Defined"
#ifdef AGE
printf("Age is %d", AGE);
#else
printf("Not Defined");
#endif
return 0;
}
คำอธิบาย:
ifdef คำสั่งตรวจสอบว่ามีการกำหนดมาโคร AGE หรือไม่ และในขณะที่เราได้แสดงความคิดเห็นคำสั่ง #define บล็อก #ifdef AGE ของโค้ดจะไม่ทำงาน และโฟลว์การควบคุมจะย้ายไปยัง บล็อก #else และไม่ได้กำหนดจะถูกพิมพ์บน หน้าจอเอาต์พุต #endif ช่วยให้มั่นใจว่าบล็อกการคอมไพล์แบบมีเงื่อนไขจะสิ้นสุดที่นั่น
ตอนนี้เรามาดูรูปภาพด้านล่างที่แสดงให้เห็นว่าพรีโปรเซสเซอร์แปลงไฟล์ซอร์สโค้ดของเราเป็นไฟล์ระดับกลางได้อย่างไร ไฟล์ระดับกลางมีนามสกุล.i และเป็นรูปแบบขยายของโปรแกรม C ของเราที่มีเนื้อหาทั้งหมดของไฟล์ส่วนหัว การขยายมาโคร และการคอมไพล์ตามเงื่อนไข
b. Compiling
ขั้นตอนการคอมไพล์ในภาษา C ใช้ซอฟต์แวร์คอมไพเลอร์ ในตัว เพื่อแปลงไฟล์ระดับกลาง ( .i ) เป็นไฟล์ แอสเซมบลี ( .s ) ที่มีคำแนะนำระดับแอสเซมบลี (รหัสระดับต่ำ) เพื่อเพิ่มประสิทธิภาพการทำงานของคอมไพเลอร์โปรแกรมแปลไฟล์ระดับกลางเพื่อสร้างไฟล์แอสเซมบลี
รหัสแอสเซมบลีเป็นภาษาประเภทภาษาอังกฤษง่าย ๆ ที่ใช้เขียนคำสั่งระดับต่ำ (ในโปรแกรมไมโครคอนโทรลเลอร์ เราใช้ภาษาแอสเซมบลี) รหัสโปรแกรมทั้งหมดถูกแยกวิเคราะห์ (การวิเคราะห์ไวยากรณ์) โดยซอฟต์แวร์คอมไพเลอร์ในครั้งเดียว และบอกเราเกี่ยวกับข้อผิดพลาดทางไวยากรณ์หรือคำเตือนที่มีอยู่ในซอร์สโค้ดผ่านหน้าต่างเทอร์มินัล
ภาพด้านล่างแสดงตัวอย่างการทำงานของขั้นตอนการคอมไพล์
c. Assembling
รหัสระดับการประกอบ ( ไฟล์ .s ) ถูกแปลงเป็นรหัสที่เครื่องเข้าใจได้ (ในรูปแบบไบนารี/เลขฐานสิบหก) โดยใช้ แอส เซม เบลอร์ Assembler เป็นโปรแกรมที่เขียนไว้ล่วงหน้าซึ่งแปลรหัสแอสเซมบลีเป็นรหัสเครื่อง ใช้คำสั่งพื้นฐานจากไฟล์โค้ดแอสเซมบลีและแปลงเป็นโค้ดไบนารี/เลขฐานสิบหกเฉพาะสำหรับประเภทเครื่องที่เรียกว่าโค้ดอ็อบเจ็กต์
ไฟล์ที่สร้างขึ้นมีชื่อเดียวกับไฟล์แอสเซมบลีและเรียกว่าไฟล์อ็อบเจ็กต์ที่มีนามสกุล .obj ใน DOS และ .o ใน UNIX OS
ภาพด้านล่างแสดงตัวอย่างการทำงานของขั้นตอนการประกอบ ไฟล์แอสเซมบลี area.s ถูกแปลเป็นอ็อบเจ็กต์ไฟล์ area.o ที่มีชื่อเดียวกัน แต่มีนามสกุลต่างกัน
ตัวอย่าง
โปรแกรม C เพื่อแสดง Hello World! บนหน้าจอเอาท์พุท
// Simple Hello World program in C
#include<stdio.h>
int main()
{
// printf() is a output function which prints
// the passed string in the output console
printf("Hello World!");
return 0;
}
บันทึก:
Hello World เล็ก ๆ นี้ !
โปรแกรมต้องผ่านหลายขั้นตอนของกระบวนการคอมไพล์เพื่อให้ได้ผลลัพธ์บนหน้าจอ
คำอธิบาย:
- ในการคอมไพล์โค้ดข้างต้น ให้ใช้คำสั่งนี้ในเทอร์มินัล : gcc hello.c -o hello
- ขั้นแรก การประมวลผลล่วงหน้าของโปรแกรม C ของเราเริ่มต้นขึ้น ความคิดเห็นจะถูกลบออกจากโปรแกรม เนื่องจากไม่มีคำสั่งมาโครในโปรแกรมนี้ ดังนั้นการขยายมาโครจะไม่เกิดขึ้น นอกจากนี้ เราได้รวม ไฟล์ส่วนหัว stdio.h และระหว่างช่วงก่อน การประมวลผล การประกาศฟังก์ชันอินพุต/เอาต์พุตมาตรฐาน เช่น printf() , scanf() เป็นต้น ถูกเพิ่มในโปรแกรม C ของเรา ในระหว่างขั้นตอนการคอมไพล์โปรแกรมของเรา คำสั่งทั้งหมดจะถูกแปลงเป็นคำสั่งระดับแอสเซมบลีโดยใช้ซอฟต์แวร์คอมไพเลอร์
- คำแนะนำระดับการประกอบสำหรับโปรแกรมด้านบน ( ไฟล์ hello.s ):
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 11, 0 sdk_version 12, 1
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
.cfi_startproc
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
subq $16, %rsp
movl $0, -4(%rbp)
leaq L_.str(%rip), %rdi
movb $0, %al
callq _printf
xorl %eax, %eax
addq $16, %rsp
popq %rbp
retq
.cfi_endproc
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "Hello World!"
.subsections_via_symbols
- คุณสามารถรับ ไฟล์ hello.s ด้านบนได้ โดยใช้คำสั่ง: gcc -S hello.c ในเทอร์มินัล
- ไฟล์ hello.s ถูกแปลงเป็นรหัสไบนารีโดยใช้โปรแกรมแอสเซมเบลอร์และสร้างไฟล์อ็อบเจ็กต์ hello.obj ใน DOS และ hello.o ใน UNIX OS
- ตอนนี้ ตัวเชื่อมโยงเพิ่มคำจำกัดความที่จำเป็นลงในไฟล์อ็อบเจ็กต์โดยใช้ไฟล์ไลบรารี และสร้างไฟล์เรียกทำงาน hello.exe ใน DOS และ hello.out ใน UNIX OS
- เมื่อเราเรียกใช้ hello.exe / hello.out เราจะได้รับ Hello World! เอาต์พุตบนหน้าจอ
Flow Diagram of the Program
ให้เราดูแผนภาพการไหลของโปรแกรมในกระบวนการคอมไพล์ใน C :
- เรามีไฟล์โปรแกรม C ที่มีนามสกุลเป็น .c เช่นไฟล์ hello.c
- ขั้นตอนที่ 1 คือการประมวลผลล่วงหน้าของไฟล์ส่วนหัวคำสั่งทั้งหมดที่ขึ้นต้นด้วย # (สัญลักษณ์แฮช) และความคิดเห็นจะถูกแทนที่/ลบออกระหว่างการประมวลผลล่วงหน้าด้วยความช่วยเหลือของตัวประมวลผลล่วงหน้า มันสร้างไฟล์ระดับกลางที่มี นามสกุลไฟล์ .i เช่นไฟล์ hello.i
- ขั้นตอนที่ 2 คือการรวบรวมไฟล์ hello.i ซอฟต์แวร์คอมไพเลอร์แปลไฟล์ hello.i เป็น hello.s พร้อมคำแนะนำระดับการประกอบ (รหัสระดับต่ำ)
- ขั้นตอนที่ 3 คำแนะนำเกี่ยวกับรหัสระดับชุดประกอบจะถูกแปลงเป็นรหัสที่เครื่องเข้าใจได้ (รูปแบบไบนารี/เลขฐานสิบหก) โดยแอสเซมเบลอร์ ไฟล์ที่สร้างขึ้นเรียกว่าไฟล์อ็อบเจ็กต์ที่มีนามสกุล .obj / .o เช่นไฟล์ hello.obj / hello.o
- ขั้นตอนที่ 4 , Linkerใช้เพื่อเชื่อมโยงไฟล์ไลบรารีกับไฟล์อ็อบเจ็กต์เพื่อกำหนดคำสั่งที่ไม่รู้จัก มันสร้างไฟล์ปฏิบัติการที่มีนามสกุล .exe / .out เช่นไฟล์ hello.exe / hello.out
- ต่อไป เราสามารถเรียกใช้ไฟล์เรียกทำงาน hello.exe / hello.out เพื่อรับเอาต์พุตที่ต้องการบนหน้าต่างเอาต์พุตของเรา นั่นคือ Hello World! .
บทสรุป
- กระบวนการคอมไพล์ในภาษา C เรียกอีกอย่างว่ากระบวนการแปลงรหัสที่เข้าใจได้ของมนุษย์ ( โปรแกรม C ) เป็นรหัสที่เข้าใจได้ของเครื่อง ( Binary Code ))
- กระบวนการคอมไพล์ในภาษาซีประกอบด้วยสี่ขั้นตอน: การประมวลผลล่วงหน้า การคอมไพล์ การประกอบ และการลิงก์
- เครื่องมือตัวประมวลผลล่วงหน้าช่วยในการลบความคิดเห็น การขยายมาโคร การรวมไฟล์ และการรวบรวมตามเงื่อนไข คำสั่งเหล่านี้ดำเนินการในขั้นตอนแรกของกระบวนการคอมไพล์ ซอฟต์แวร์คอมไพเลอร์ช่วยเพิ่มประสิทธิภาพการทำงานของโปรแกรมและแปลไฟล์ระดับกลางเป็นไฟล์แอสเซมบลี
- Assembler ช่วยแปลงไฟล์แอสเซมบลีเป็นไฟล์อ็อบเจ็กต์ที่มีรหัสระดับเครื่อง
- Linker ใช้สำหรับเชื่อมโยงไฟล์ไลบรารีกับไฟล์อ็อบเจ็กต์ เป็นขั้นตอนสุดท้ายในการรวบรวมเพื่อสร้างไฟล์ปฏิบัติการ
credit : https://www.scaler.com/topics/c/