Professional Documents
Culture Documents
Complies With GCC and Gnu Make
Complies With GCC and Gnu Make
I. GCC
gcc (GNU Compiler Collection) là tập hợp các chương trình dịch dùng để biên
dịch các ngôn ngữ khác nhau. gcc là chương trình dịch chính thức của hệ thống GNU
bao gồm hệ điều hành giống-UNIX, Linux, Mac OS và một số hệ điều hành khác. Quá
trình biên dịch sẽ phải đi qua một chuỗi các bước để tạo ra file thực thi cuối cùng. Các
bước trung gian đó là kết quả của các tool khác nhau được gọi bên trong gcc để hoàn
thành quá trình dịch source code.
Toàn bộ quá trình dịch bằng GCC được chia ra làm các bước sau:
Preprocessing
Compilation
Assembler
Linking
Bài viết này sẽ sử dụng file hello.c sau làm ví dụ:
#include <stdio.h>
#define STRING "Hello World"
Kết quả ta được output là file hello.i chứa source code đã được mở rộng bao gồm các
macro. Theo quy ước file mở rộng này sẽ có phần đuôi là .i đối với C và .ii đối với C+
+.
Note: Mặc định các file output của quá trình tiền xử lý này sẽ không được lưu vào đĩa
cứng trừ khi ta dùng gcc với option -save-temps.
Chúng ta có thể trực tiếp thực hiện quá trình tiền xử lý với gcc bằng cách dùng cờ “-
E”.
File output sẽ có dạng như sau. Vì file studio.h quá lớn nên mình xin phép bỏ qua
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3
# 1 "/usr/include/_ansi.h" 1 3
# 1 "/usr/include/sys/config.h" 1 3
# 14 "/usr/include/sys/config.h" 3
# 25 "/usr/include/sys/config.h" 3
# 44 "/usr/include/sys/config.h" 3
# 40 "/usr/include/stdio.h" 2 3
# 1 "/usr/include/sys/reent.h" 1 3
int main(void){
Vì chương trình yêu cầu file stdio.h, từ đó kéo theo yêu cầu thêm các file khác nữa. Vì
vậy, bộ tiền xử lý tạo ra các ghi chú về file và dòng trong file nơi thực hiện request file
để thực hiện các bước tiếp theo. Do vậy, các dòng,
# 40 "/usr/include/stdio.h" 2 3
# 1 "/usr/include/sys/reent.h" 1 3
Với gcc, file output được xác định với option -o. File hello.o chính là file mã máy
của hello.c.
hoặc dùng option “-c” của gcc để convert:
4. Linker
Bước cuối cùng của quá trình biên dịch là tạo ra một file thực thi duy nhất bằng cách
link các file object. Một file object và file thực thi có nhiều định dạng như ELF
(Executable and Linking Format) và COFF (Common Object-File Format). Ví dụ,
ELF được sử dụng trên các máy Linux, COFF được sử dụng trên máy Windows.
Trong thực tế, một file thực thi gọi đến nhiều hàm bên ngoài từ hệ thống và các thư
viện C. Linker sẽ xử lý tất cả các thành phần phụ thuộc và cung cấp địa chỉ của các
hàm được gọi.
Linker cũng thực hiện một số nhiệm vụ khác. Nó kết hợp chương trình của chúng ta
với một số tác vụ chuẩn cần thiết giúp chương trình có thể chạy. Ví dụ như thiết lập
môi trường chạy, truyền các tham số dòng lệnh và các biến môi trường. Ngoài ra, còn
cần có code chạy ở cuối chương trình, nơi kết quả được retrun. Do đó khối lượng code
là không hề nhỏ.
Linker trong gcc có thể được gọi như dưới đây:
Nó sẽ link file object hello.o với các thư viện chuẩn của C và tạo ra file thực thi a.out.
II. GNU MAKE
1. Makefile là gì?
Makefile là một file dạng script chứa các thông tin:
- Cấu trúc project (file, sự phụ thuộc)
- Các lệnh để tạo file
Lệnh make sẽ đọc nội dung Makefile, hiểu kiến trúc của project và thực
thi các lệnh
2. Cấu trúc project
Cấu trúc và sự phụ thuộc của project có thể được biểu diễn bằng một
DAG (Directed Acyclic Graph)
Thí dụ:
- Chương trình chứa 3 file: main.c, sum.c, sum.h
- File sum.h được dùng bởi cả 2 file main.c và sum.c
- File thực thi là sum
sum.h
#ifndef SUM_H_
#define SUM_H_
#include <stdio.h>
int sum(int a, int b);
#endif /* SUM_H_ */
sum.c
#include "sum.h"
main.c
#include <stdio.h>
#include "sum.h"
int x;
x= sum(1, 2);
printf("x = %d \n", x);
return 1;
}
Makefile
sum: main.o sum.o
gcc -o sum main.o sum.o
main.o: main.c sum.h
gcc -c main.c
sum.o: sum.c sum.h
gcc -c sum.c
Target
Vậy make thực hiện theo nguyên tắc / thứ tự như sau:
+ Tạo ra các file object trước (main.o, sum.o)
+ Tạo ra chương trình nhị phân cuối cùng từ các file object đã được tạo ra trước đó
(sum)
[khoatn@localhost ~]$
[khoatn@localhost ~]$ cd /home/khoatn/Github/eslinuxprogramming/Makefile
[khoatn@localhost Makefile]$
[khoatn@localhost Makefile]$ make
gcc -c main.c
gcc -c sum.c
gcc -o sum main.o sum.o
[khoatn@localhost Makefile]$
[khoatn@localhost Makefile]$
[khoatn@localhost Makefile]$ ./sum
x = 3
[khoatn@localhost Makefile]$
[khoatn@localhost Makefile]$
3. Nguyên lý biên dịch lại của Makefile
Việc compile lại project dựa vào hai yếu tố:
+ Thời gian chỉnh sửa (date modified)
+ Cây phụ thuộc trong Makefile
Có nghĩa là một khi Dependency thay đổi thì Target tương ứng cũng phải
được compile lại.
Theo Makefile như trong ví dụ trên:
+ Nếu chỉnh sủa sum.h thì main.o và sum.o phải được tạo lại, mặt
khác main.o & sum.o lại là dependency của sum nên sum sẽ được tạo lại
Khi viết Makefile tránh gộp tất cả lại làm một như dưới đây, vì khi đó
nếu một trong các file source code thay đổi thì cũng phải compile lại
tất cả các file khác, điều đó làm mất nhiều thời gian:
Makefile
sum: main.c sum.c sum.h
${CC} -o sum main.c sum.c sum.h
4. Khuôn dạng đầy đủ của Makefile
#include <stdio.h>
#include "sum.h"
int x;
x= sum(1, 2);
#ifdef DEBUG
printf("x = %d \n", x);
#endif
return 1;
}
Makefile
TARGET=sum
HDRS+= sum.h
CSRCS+= main.c sum.c
CPPSRCS+=
OBJSDIR=./build
OBJS:= $(patsubst %.cpp, $(OBJSDIR)/%.o, $(CPPSRCS))
OBJS+= $(patsubst %.c, $(OBJSDIR)/%.o, $(CSRCS))
CC:= gcc
CXX:= g++
all: ${TARGET}
${TARGET} : $(OBJS)
@echo " [LINK] $@"
@mkdir -p $(shell dirname $@)
@$(CXX) $(OBJS) -o $@ $(LDFLAGS)
$(OBJSDIR)/%.o: %.c $(HDRS)
@echo " [CC] $@"
@mkdir -p $(shell dirname $@)
@$(CC) -c $< -o $@ $(CFLAGS)
install:
cp -rf ${TARGET} /usr/local/bin
clean:
rm -rf ${OBJSDIR}/*.o
rm -rf ${TARGET}