Tải bản đầy đủ (.doc) (23 trang)

Báo Cáo Bài Tập Lớn_2.Doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (187.43 KB, 23 trang )

BÁO CÁO BÀI TẬP LỚN
MÔN: NGUYÊN LÝ HỆ ĐIỀU HÀNH

ĐỀ TÀI: TÌM HIỂU LẬP TRÌNH C TRÊN LINUX
Nhóm thực hiện : Trần Minh Phương,
Bùi Long Hưng,
Luân Quốc Vinh,
Nguyễn Duy Hiếu.

A.Mục lục
B.Lời mở đầu

Hiện nay, như chúng ta đã biết, lập trình phần mềm đang là một mảng rất lớn và quan trọng
trong ngành cơng nghệ thong tin trên tồn thế giới, và quốc gia đang đi đầu trong lĩnh vực này là
Ấn Độ. Với yêu cầu ngày càng cao của thị trường, người lập trình viên cũng cần có trình độ ngày
càng cao. Các ngôn ngữ được sử dụng cũng ngày càng đa dạng để đáp ứng nhu cầu đó. Chúng ta
đã được nghe tới các ngơn ngữ lập trình như C, C++, C#, Java … Đó là các ngơn ngữ lập trình
rất mạnh hiện nay đang được sử dụng phổ biến.
Trên thế giới hiện nay, người sử dụng máy tính được tiếp cận với những hệ điều hành khác nhau,
nổi bật trong đó là hai hệ điều hành lớn nhất là Linux và Windows. Windows nổi bật với tính
năng dễ sử dụng, phổ biến, thích hợp với nhiều đối tượng. Trong khi đó, Linux nổi bật với tính
bảo mật rất cáo. Sử dụng mã nguồn mở nên bạn có thể có được một hệ điều hành của riêng mình.
Linux phù hợp với những hệ thống lớn vì nó có tính bảo mật rất cao.
Tổng quan về bài báo cáo
Phạm vi vấn đề tìm hiểu : Tìm hiểu chung nhất xung quanh việc lập trình ngơn ngữ C trên hệ
điều hành.Cách viết một chương trình C trên HĐH Linux. Cách biên dịch chương trình và cài
đặt.
Các vấn đề liên quan : tìm hiểu về hệ điều hành linux, biết sơ lược về hệ điều hành linux cũng
như những điều cơ bản về linux, đặc biết là về shell. Ngoài việc biên dịch code trực tiếp bằng
shell, có thể dung các IDE để biên dịch, một trong số đó là Codeblock.


C. Tổng quan

Trong phần báo cáo này chỉ tìm hiểu những vấn đề xung quanh vịêc lập trình C trên hệ điều
hành Linux :
 Tìm hiểu sơ qua về hệ điều hành Linux
 tổng quan về viết chương trình C, các cơng cụ sử dụng
 Cách biên dịch một chương trình
(Phần báo cáo này không đi sâu vào các vấn đề như cách viết lệnh hay lập trình chi chiết một
chương trình mà chỉ tìm hiểu những gì chung nhất về vấn đề này. Tìm hiểu các bước làm một
chương trình và các quá trình được thực hiện khi lập trình C trên Linux.) .
Qua phần báo cái này, có thể hiểu được them về các khái niệm trong lập trình C trên hệ điều
hành Linux, có được cái nhìn tổng quan xung quanh vịêc viết chương trình.


Cơng cụ được sử dụng để Lập trình C trên Linux:
Để lập trình C trên Linux, bạn cần có một số tài liệu, phương tiện để thực hiện nó:
 Cài đặt hệ điều hành Linux (Có thể có nhiều phiên bản như Redhat, Ubuntu, Knoppix…)
 Một IDE để biên dịch code: có thể là CodeBlock hay Eclipse…

D.Trình bày chi tiết phần lý thuyết - Chi tiết về lập trình C trên Linux
I. Tìm hiểu chung về hệ điều hành Linux
Đặc điểm chính của HĐH Linux
Do mã nguồn Linux phân phối tự do và miễn phí, nên ngay từ đầu đã có rất nhiều nhà lập trình
tham gia vào q trình phát triển hệ thống. Nhờ đó đến thời điểm hiện nay Linux là hệ điều hành
hiện đại, bền vững và phát triển nhanh nhất, hỗ trợ các công nghệ mới gần như ngay lập tức.
Linux có tất cả các khả năng, đặc trưng cho các hệ điều hành đầy đủ tính năng dịng UNIX. Xin
đưa ra đây danh sách ngắn gọn những khả năng này.
Nhiều tiến trình thật sự
Tất cả các tiến trình là độc lập, khơng một tiến trình nào được cản trở cơng việc của tiến trình
khác. Để làm được điều này nhân thực hiện chế độ phân chia thời gian của bộ xử lý trung tâm,

lần lượt chia cho mỗi tiến trình một khoảng thời gian thực hiện. Cách này hoàn toàn khác với chế
độ “nhiều tiến trình đẩy nha” được thực hiện trong Windows 95, khi một tiến trình phải nhường
bộ xử lý cho các tiến trình khác (và có thể làm chậm trễ rất lâu việc thực hiện).
Truy cập nhiều người dùng
Linux không chỉ là HĐH nhiều tiến trình, Linux hỗ trợ khả năng nhiềungười dùng làm việc cùng
lúc. Khi này Linux có thể cung cấp tất cả các tàinguyên hệ thống cho người dùng làm việc qua
các terminal ở xa khác nhau.
Swap bộ nhớ lên đĩa
Swap bộ nhớ cho phép làm việc với Linux khi dung lượng bộ nhớ có hạn. Nội dung của một số
phần (trang) bộ nhớ được ghi lên vùng đĩa cứng xác định từ trước. Vùng đĩa cứng này được coi
là bộ nhớ phụ thêm vào. Việc này có làm giảm tốc độ làm việc, nhưng cho phép chạy các
chương trình cần bộ nhớ dung lượng lớn mà thực tế khơng có trên máy tính.
Tổ chức bộ nhớ theo trang
Hệ thống bộ nhớ Linux được tổ chức ở dạng các trang với dung lượng 4K. Nếu bộ nhớ đầy, thì
HĐH sẽ tìm những trang bộ nhớ đã lâu khơng được sử dụng để chuyển chúng từ bộ nhớ lên đĩa
cứng. Nếu có trang nào đó trong số những trang này lại trở thành cần thiết, thì Linux sẽ phục hồi
chúng từ đĩa cứng (vào bộ nhớ). Một số hệ thống Unix cũ và một số hệ thống hiện đại (bao gồm
cả Microsoft Windows) chuyển lên đĩa tất cả nội dung của bộ nhớ thuộc về những ứng dụng
không làm việc tại thời điểm hiện thời (tức là TẤT CẢ các trang bộ nhớ thuộc về ứng dụng sẽ
được lưu lên đĩa khi không đủ bộ nhớ) và như vậy kém hiệu quả hơn.
Nạp môđun thực hiện “theo yêu cầu”


Nhân Linux hỗ trợ việc cung cấp các trang bộ nhớ theo yêu cầu, khi này chỉ phần mã cần thiết
của chương trình mới nằm trong bộ nhớ, cịn những phần mã khơng sử dụng tại thời điểm hiện
tại thì nằm lại trên đĩa.
Cùng sử dụng chương trình
Nếu cần chạy một lúc nhiều bản sao của cùng một ứng dụng nào đó6, thì Linux chỉ nạp vào bộ
nhớ một bản sao của mã chương trình và tất cả các tiến trình giống nhau cùng sử dụng một mã
này.

Thư viện chung
Thư viện – bộ các quá trình (thao tác) được chương trình dùng để làm việc với dữ liệu. Có một
số thư viện tiêu chuẩn được dùng cùng lúc cho vài tiến trình. Trên các hệ thống cũ những thư
viện đó nằm trong mỗi tập tin chương trình, và thực hiện cùng lúc những chương trình này dẫn
đến hao hụt bộ nhớ khơng đáng có. Trên các hệ thống mới (bao gồm Linux) có hỗ trợ làm việc
với các thư viện động (dynamic) và tĩnh (static) được chia ra, và như vậy cho phép giảm kích
thước bộ nhớ bị ứng dụng chiếm.
Bộ đệm động của đĩa
Bộ đệm của đĩa đó là một phần bộ nhớ của hệ thống dùng làm nơi lưu những dữ liệu thường
dùng của đĩa, nhờ đó nâng cao rất nhiều tốc độ truy cập tới những chương trình và tiến trình
thường dùng. Người dùng MS-DOS sẽ nhớ đến chương trình SmartDrive, chương trình này dự
trữ một phần bộ nhớ có kích thước xác định để làm bộ đệm cho đĩa. Linux sử dụng hệ thống
đệm linh động hơn: bộ nhớ được dự trữ cho đệm được tăng lên khi bộ nhớ không được sử dụng,
và sẽ giảm xuống khi hệ thống hay tiến trình cần nhiều bộ nhớ hơn. 100% tương ứng với tiêu
chuẩn POSIX 1003.1. Hỗ trợ một phần các khả năng của System V và BSD POSIX 1003.1
(Portable Operating System Interface – giao diện của hệ điều hành lưu động) đưa ra giao diện
tiêu chuẩn cho các hệ thống Unix, đó là một bộ các thủ tục ngôn ngữ C. Ngày nay giao diện này
được tất cả các hệ điều hành mới hỗ trợ. Microsoft Windows NT cũng hỗ trợ POSIX 1003.1.
Linux 100% tương ứng với tiêu chuẩn POSIX 1003.1. Thêm vào đó Linux cịn hỗ trợ các khả
năng của System V và BSD để tăng tính tương thích. System V IPC Linux sử dụng công nghệ
IPC (InterProcess Communication) để trao đổi thông tin giữa các tiến trình, để sử dụng tín hiệu
và bộ nhớ chung.
Khả năng chạy chương trình của HĐH khác
Trong lịch sử Linux không phải là hệ điều hành đầu tiên. Người ta đã viết ra hàng loạt các
chương trình ứng dụng, trong đó có cả những chương trình có ích và không đến nỗi tồi, cho các
HĐH đã phát triển trước Linux, bao gồm DOS, Windows, FreeBSD và OS/2. Để chạy những
chương trình như vậy dưới Linux đã phát triển các trình giả lập (emulator) cho DOS, Windows
3.1, Windows 95 và Wine. Ngồi ra, cịn có một loạt các chương trình tạo máy ảo7 mã nguồn mở
cũng như sản phẩm thương mại: qemu, bochs, pearpc, vmware,. . .HĐH Linux cịn có khả năng
chạy chương trình dành cho bộ xử lý Intel của các hệ thống Unix khác, nếu hệ thống đáp ứng

tiêu chuẩn iBCS2 (intel Binary Compatibility).
Hỗ trợ các định dạng hệ thống tập tin khác nhau
Linux hỗ trợ một số lượng lớn các định dạng hệ thống tập tin, bao gồm các hệ thống tập tin DOS
và OS/2, và cả các hệ thống tập tin mới, như reiserfs, HFS,. . . . Trong khi đó hệ thống tập tin


chính của Linux, được gọi là Second Extended File System (ext2fs) và Third Extended File
System (ext3fs) cho phép sử dụng khơng gian đĩa một cách có hiệu quả.
Khả năng hỗ trợ mạng
Linux có thể gắn vào bất kỳ mạng nội bộ nào. Hỗ trợ tất cả các dịch vụ Unix, bao gồm
Networked File System (NFS), kết nối từ xa (telnet, rlogin, ssh), làm việc trong các mạng
TCP/IP, truy cập dial-up qua các giao thức SLIP và PPP,v.v. . .Đồng thời có hỗ trợ dùng Linux
là máy chủ hoặc máy khách cho mạng khác, trong đó có chia sẻ (dùng chung, sharing) các tập tin
và in từ xa trong các mạng Macintosh, NetWare và Windows.
Làm việc trên các phần cứng khác nhau
Mặc dù đầu tiên HĐH Linux được phát triển cho máy tính cá nhân (PC) trên nền tảng Intel
386/486, bây giờ nó có thể làm việc trên tất cả các bộ vi xử lý Intel bắt đầu từ 386 và kết thúc là
các hệ thống nhiều bộ xử lý Pentium IV, bao gồm cả các bộ xử lý 64bit. Đồng thời Linux còn
làm việc trên rất nhiều bộ xử lý tương thích với Intel của các nhà sản xuất khác, như AMD.
Trong Internet cịn có những thơng báo nói rằng trên các bộ xử lý Athlon và Duron của AMD
Linux cịn làm việc tốt hơn so với trên Intel.
Ngồi ra cịn có phiên bản Linux cho các bộ xử lý khác bao gồm ARM, DEC Alpha, SUN Sparc,
M68000 (Atari và Amiga), MIPS, PowerPC và những bộ xử lý khác8. Xin được nói ln là trong
cuốn sách này chúng ta chỉ xem xét trường hợp Linux cho các máy tính tương thích với IBM.

II. Giới thiệu qua về thư viện
Hầu như tất cả các chương trình đểu dựa liên kết tới một thư viện hoặc nhiều hơn. Một vài
chương trình sử dụng các hàm trong C ( như là printf hay malooc) sẽ được liên kết tới thư viện
C runtime. Nếu chương trình có giao diện người dùng(GUI), nó sẽ liên kết vào thư viện
windoing. Nếu chương trình của bạn dùng một cơ sở dữ liệu, người cung cấp cơ sở dữ liệu đó sẽ

đưa cho bạn thư viện có thể sử dụng truy xuất một cách dễ dàng. Trong mỗi một trường hợp, bạn
phải quyết định xem nên chọn liên kết tới thư viện tĩnh hoặc động. Nếu bạn chọn thư viện tĩnh,
thì chương trình của bạn sẽ trở nên cồng kềnh hơn, và khó nâng cấp hồn thiện hơn, nhưng dĩ
nhiên là sẽ dễ triển khai hơn. Nếu liên kết tới thư viện động, chương trình của bạn sẽ besmaller,
dễ nâng cấp, nhưng khó phát triển. Những phần giải thích hướng dẫn liên kết cả thư viện tĩnh và
động, xem xét các yếu tố để có thêm chi tiết, và đưa ra một số quy tắc chính giúp cho bạn quyết
định nên chọn liên kết tới thư viện nào.

1. Lưu trữ

Lưu trữ ( hay thư viện tĩnh) một cách đơn giản là lưu trữ một tập hợp các đối tượng như là một
file đơn lẻ.(Lưu trữ tương đương như một file trong windows có đi là .LIB). Khi bạn quyết
định lưu trữ với liên kết, liên kết tìm kiếm trong kho trữ tìm những file đối tượng, trích chúng
ra, và liên kết chúng vào trong chương trình, với điều kiện là ngay lúc đó có các đối tượng. Bạn
có tạo ra kho trữ bằng các lệnh. Các file lưu trữ truyền thống sử dụng đuôi mở rộng là a.a hơn là
đuôi .o đã chấp nhận bởi các tệp đối tượng bình thường. Ở đây bằng cách nào bạn có thể nối
test1.o và test2.o thành một file lưu trữ libtes.a : % ar cr libtest.a test1.o test2.o
Các thanh cờ cr chỉ cho cách tạo ra kho trữ. Khi kết nối vào kho trữ bằng lệnh, nó sẽ tìm kiếm
trong kho trữ các ký hiệu đơn giản ( hàm hoặc biến số) bởi sự liên quan tới các tệp đối tượng đã
có trong q trình nhưng chưa định nghĩa. Các tệp đối tượng xác định các kí hiệu đã rút ra từ


kho trữ khi mà bắt gặp các lệnh. Ví dị giả định test.c chứa đoạn code ở trong Listing2.7 và
app.c có code ở phần Listing2.8.
Nội dung test.c
int f ()
{
return 3;
}
Listing 2.8 (app.c) A Program That Uses Library Functions

int main ()
{
return f ();
}
3. Bạn có thể sử dụng các cờ để gỡ bở file từ kho trữ hoặc thực hiện các thao tác trong kho trữ.
Các quá trình hoạt động hiếm được sử dụng nhưng đều được dẫn chứng bằng tài liệu ở trên trang
nhớ.
Bây giờ giả định test.o được nối với một số file đối tượng để sinh ra kho trữ libtest.o. Sau đây là
dịng lệnh sẽ khơng hoạt động:
% gcc -o app -L. -ltest app.o
app.o: In function `main':
app.o(.text+0x4): undefined reference to `f'
collect2: ld returned 1 exit status
Thông báo lỗi chỉ ra là libtest.a đã không chứa định nghĩa của file, đã khơng có sự liên kết tới
file đó. Đó là bởi vì libtest.a đã được tìm kiếm khi có sự xung đột ở lần đầu tiên, và trỏ tới liên
kết khác do đó đã khơng tham chiếu tới file được nhắc tới. Trong một trường hợp khác, nếu
chúng ta sử dụng các dịng, sẽ khơng có các thơng báo lỗi được đưa ra:
% gcc -o app app.o -L. ltest
Đó là lí do mà tham chiếu tới file trong app.o do đó liên kết tới cả file đối tượng test.o từ kho lưu
trữ libtesr.a

2. Sự chia sẻ thư viện

Một thư viện chia sẻ ( chia sẻ các đối tượng đã biết, hoặc là các liên kết động) là liên kết những
kho trữ giống nhau thành flie đối tượng. Tuy nhiên, có rất nhiều sự khác biệt quan trọng. Khác
biệt quan trọng nhất là khi chia sẻ thư viện là liên kết tới chương trình, những thao tác có thể
thực hiện được cuối cùng không chứa đựng đoạn code hiện tại ở trong thư viện chia sẻ. Thay vào
đó, cơng việc có thể thực hiện được chỉ đơn thuần chứa chỉ tham chiếu tới thư viện chia sẻ. Một
vài chương trình trong hệ thống đều liên kết dựa vào những thư viện chia sẻ tương tự, chúng sẽ
tham chiếu tất cả thư viện, nhưng thậm chí khơng có include. Theo cách đó, thư viện bị “ chia

sẻ” giữa tất cả các chương trình mà có liên kết tới nó.
Khác biệt quan trọng thứ hai là một thư viện chia sẻ không đơn thuần chỉ là tập các file đối
tượng, ngoài những mối liên kết đã chọn dùng chúng còn cần thỏa mãn những tham chiếu khơng
xác định. Thay vì, các file đối tượng được soạn thì thư viện chia sẻ được nối vào một file đối
tượng đơn lẻ do đó chương trình dựa vào các liên kết ở trong thư viện chia sẻ luôn luôn bao gồm
tất cả các code ở trong thư viện, hơn là các phần chia đã cần. Khi tạo ra một thư viện chia sẻ, bạn
phải biên dịch các đối tượng sẽ cấu tạo lên thư viện bằng cách sử dụng fPIC tùy chọn trong
chương trình biên dịch, như là : % gcc -c -fPIC test1.c Tùy chọn fPIC chỉ ra chương trình biên
dịch mà bạn sẽ sử dụng test.o như là một phần của sự chia sẻ đối tượng.


Position-Independent Code (PIC)(Vị trí độc lập code)
PIC giữ vững cho Position-Independent Code. Các hàm trong thư viện chia sẻ có thể nạp vào ở
các địa chỉ khác nhau trong những chương trình khác nhau, do đó code ở trong đối tượng chia sẻ
phải không sinh ra từ địa chỉ(hoặc vị trí) ở nơi nó nạp vào. Sự cân nhắc khơng ảnh hưởng đến
bạn, là một lập trình viên, trừ khi là bạn phải nhớ cách sử dụng cờ fPIC khi đang biên soạn code
cái mà bạn muốn sử dụng ở trong thư viện chia sẻ.
Khi bạn nối các file đối tượng vào trong htw viện chia sẻ, như là:
% gcc -shared -fPIC -o libtest.so test1.o test2.o
Sự tùy chọn chia sẻ chỉ ra các liên kết tạo ra thư viện chia sẻ hơn là các hoạt động có thể diễn ra
bình thường. Các thư viện chia sẻ sử dụng đuôi mở rộng là .so. Như là lưu trữ tĩnh, tên luôn luôn
bắt đầu với lib chỉ ra file nào là thư viện. Liên kết với thư viện chia sẻ như là liên kết với một
kho trữ tĩnh. Ví dụ những dịng sau đây sẽ liên kết với libtest.so nếu như nó ở trên thư mục hiện
tại, hoặc một trong thư viện chuẩn tìm kiếm thư mục ở trên hệ thống:
% gcc -o app app.o -L. ltest
Cho là cả libtest.a và libtest.so đều sẵn có. Sau đó liên kết một thư viện hoăc khơng cái khác.
Liên kết tìm kiếm mỗi thư mục( đầu tiên lý thuyết là với –L tùy chọn, và sau đó tới thư mục
chuẩn) Khi liên kết tìm tới thư mục chứa libtest.a hoặc libtest.so, liên kết sẽ dừng việc tìm kiếm
thư mục. Nếu chỉ có một trong hai biến thể hiện hữu ở trong thư mục, thì liên kết sẽ chọn biến
thể đó. Cách khác, liên kết chọn theo phiên bản thư viện chia sẻ, nếu không bạn phải nhất định

chỉ dẫn nó cách khác. Bạn có thể sử dụng, -tĩnh tùy chọn cho đòi hỏi lưc trữ tĩnh. Ví dụ những
dịng sau đây sẽ sử dụng lưu trữ libtest.a, dù là thư viện chia sẻ libtest.so đã sẵn có:
% gcc static -o app app.o -L. ltest
Lệnh hiển thị ldd ở thư viện chia sẻ đã liên kết thực hiện được. Các thư viện cần sẵn có khi các
thao tác lệnh được chạy. Chú ý ldd sẽ liệt kê thêm vào các thư viện được gọi là ld-linux.so, nó là
một bộ phận của GNU/Linux
Sử dụng LD_LIBRARY_PATH
Khi bạn liên kết một chương trình với một thư viện chia sẻ, liên kết đó khơng đặt đường dẫn đầy
đủ tới thư viện chia sẻ ở kết mục kết quả thu được. Thay vì đó nó chỉ đặt tên ở trong thư viện
chia sẻ. Khi chương trình thực sự chạy, hệ thống tìm kiếm thư viện chia sẻ và tải nó. Hệ thống
chỉ tìm kiếm /lib và /usr/lib, theo mặc định. Nếu một thư viện chia sẻ được liên kết vào chương
trình của bạn và được cài đặt ở bên ngoài thư mục, nó sẽ được tìm thấy, và hệ thống sẽ từ chối
chạy chương trình. Một giải pháp cho vấn đề đó là sử dụng –Wl,-rpath tùy chọn khi liên kết tới
chương trình. Cho là bạn có thể sử dụng :
% gcc -o app app.o -L. -ltest -Wl,-rpath,/usr/local/lib
Sau đó khi app chạy, hệ thống sẽ tìm kiếm /usr/local/lib cho một vài thư viện phụ thuộc.
Một giải pháp khác để giải quyết là đặt môi trường biến số LD_LIBRARRY_PATH khi đang
chạy chương trình. Như mơi trường biến PATH, LD_LIBRARRY_PATH có dấu hai chấm tách
rời danh sách của thư mục. Ví dụ, nếu LD_LIBRARY_PATH is /usr/local/lib:/opt/lib, then
/usr/local/lib and /opt/lib sẽ được tìm kiếm trước thư mục chuẩn /lib and /usr/lib. Bạn nên chú ý
nếu bạn có LD_LIBRARRY_PATH, liên kết sẽ tìm kiếm thư mục đã định có sự bổ sung thư
mục đã định với tùy chọn –L khi nó xây dựng các thao tác có thể thực hiện được.

3. Thư viện chuẩn

Tất nhiên nếu như bạn chưa chỉ địn các thư viện khi bạn liên kết nó tới chương trình của bạn, nó
sẽ tất nhiên là sử dụng thư viện chia sẻ. Có điều đó bởi vì GCC tự động liên kết tới thư viện C
chuẩn.,libc, cho bạn. Thư viện C chuẩn không bao gồm các hàm tốn học; Thay vào đó chúng ở



trong một thư viện tách rời., libm, bạn cần chỉ định nó một cách rõ ràng. Ví dụ biên dịch và liên
kết chương trình compute.c nó sử dụng các hàm lượn giác như là sin hay cos, bạn phải dùng
code:% gcc -o compute compute.c lm
Nếu bạn viết một chương trình C++ và liên kết nó sử dụng các lệnh trong C++ hoặc g++, bạn sẽ
cần thư viện C++ chuẩn, libstdc++.

4. Thư viện phụ thuộc

Một thư viện sẽ thường phụ thuộc vào một thư viện khác. Ví dụ, nhiều GNU/Linux hệ thống bao
gồm cả libtiff, một thư viện chứa các hàm đọc ra và ghi ảnh files vào trong định dạng TIFF. Thư
viện đó sẽ quay vịng sử dụng các thư viện libjpeg và libz
Phần listening 2.9 trình bày một chương trình nhỏ có sử dụng libtiff để mở một file ảnh TIFF.
Phần listening 2.9 (tifftest.c) sử dụng libtff :
#include <stdio.h>
#include <tiffio.h>
int main (int argc, char** argv)
{
TIFF* tiff;
tiff = TIFFOpen (argv[1], "r");
TIFFClose (tiff);
return 0;
}
Bạn đã có thể nhận thấy sự khác biệt tới LD_RUN_PATH ở trong một vài tài liệu trực tuyến.
Đừng tin vào những gì bạn đọc; biến số đã không thực tồn tại ở dưới GNU/Linux. Lưu file
nguồn như tifftest.c Với việc biên dịch chương trình và liên kết với libtiff, chỉ định –ltiff đường
liên kết link của bạn là : % gcc -o tifftest tifftest.c ltiff
Theo mặc định, nó sẽ thực hiện sự chọn lọc các phiên bản thư viện chia sẻ của libtiff, đã tìm ở
/usr/lib/libtiff.so. Bởi vì libtiff sử dụng libjpeg và libz, các phiên bản của thư viện chia sẻ của cả
hai cũng kéo theo chúng vào(một thư viện chia sẻ có thể trỏ tới một thư viện chia sẻ khác cái mà
nó phụ thuộc. Để kiểm chứng điều đó, sử dụng lệnh ldd:

% ldd tifftest
libtiff.so.3 => /usr/lib/libtiff.so.3 (0x4001d000)
libc.so.6 => /lib/libc.so.6 (0x40060000)
libjpeg.so.62 => /usr/lib/libjpeg.so.62 (0x40155000)
libz.so.1 => /usr/lib/libz.so.1 (0x40174000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
Thư viện tĩnh, trong một nguồn khác, không thể trỏ tới thư viện khác. Nếu quyết định liên kết
với một phiên bản tĩnh khác của libtiff bởi định dạng –static trong câu lệnh, bạn sẽ bắt gặp các kí
tự:
% gcc -static -o tifftest tifftest.c -ltiff
/usr/bin/../lib/libtiff.a(tif_jpeg.o): In function `TIFFjpeg_error_exit':
tif_jpeg.o(.text+0x2a): undefined reference to `jpeg_abort'
/usr/bin/../lib/libtiff.a(tif_jpeg.o): In function `TIFFjpeg_create_compress':
tif_jpeg.o(.text+0x8d): undefined reference to `jpeg_std_error'
tif_jpeg.o(.text+0xcf): undefined reference to `jpeg_CreateCompress'
...
Việc liên kết với một chương trình tĩnh, bạn phải chỉ định tới 2 thư viện riêng của bạn:
% gcc -static -o tifftest tifftest.c -ltiff -ljpeg –lz


Đôi khi, hai thư viện sẽ bị phụ thuộc lẫn nhau qua lại. Ở các từ khác, kho trữ đầu tiên sẽ tham
chiếu các kí tự được định nghĩa ở trong kho trữ thứ hai. Cái hoàn cảnh tổng quát này nảy sinh ra
ngồi thiết kế, nhưng nó chỉ đơi khi xảy ra. Trong một số trường hợp, bạn có thể quyết định thư
viện đơn nhiều thời gian trên các dịng lệnh. Kết nối sẽ tìm ra thơng tin mới ở mỗi thư viện thời
gian mà nó xuất hiện. Ví dụ, dịng ngun nhân libfoo.a tương lai sẽ tìm nhiều khoảng thời gian :
% gcc -o app app.o -lfoo -lbar lfoo
Cho dù là libfoo.a tham chiếu tới các kí tự ở libbar.a, và ngược lại, chương trình sẽ liên kết thành
công.

5. Thuận và chống


Bây giờ bạn cho là đã biết hết mọi thứ về lưu trữ tĩnh và thư viện chia sẻ, bạn chắc chắn sẽ ngạc
nhiên với cách sử dụng chúng. Có nhiều sự cân nhắc chú ý tới ý định của bạn. Một thuận lợi chủ
yếu của thư viện chia sẻ là nó lưu trữ được nhiều khơng gian trên hệ thống nơi mà chương trình
được cái đặt. Nếu bạn đang cài đặt 10 chương trình, và chúng đều sử dụng chung một thư viện
chia sẻ, bạn sẽ giải thốt được nhiều khơng gian do sử dụng một thư viện chia sẻ. Nếu như bạn
sử dụng lưu trữ tĩnh thay vào đó, kho trữ sẽ bao gồm cả 10 chương trình. Do đó, sử dụng thư
viện chia sẻ đã giải thốt được nhiều khơng gian đĩa cứng. Nó cũng giảm bớt thời gian nếu như
chương trình của bạn tải dữ liệu từ Web. Lợi thế liên quan tới thư viện chia sẻ là người sử dụng
có thể nâng cấp thư viện khơng có sự phục thuộc vào chương trình. Ví dụ cho là bạn làm ra thư
viện chia sẻ quản lí kết nối HTTP. Một vài chương trình có thể đã phụ thuộc vào thư viện. Nếu
như bạn tìm được lỗi ở trên thư viện, bạn có thể nâng cấp hồn thiện thư viện đó lên. Ngay lập
tức tất cả chương trình phụ thuộc trên thư viện sẽ được sửa, bạn không cần phải liên kết tất cả
các chương trình theo cách mà bạn làm với một kho trữ chuẩn. Sự thuận lợi đó khiến bạn nghĩ là
bạn nên luôn luôn sử dụng thư viện chia sẻ. Tuy nhiên, tại sao lại có sự tồn tại và sử dụng kho
trữ. Sự thật là việc nâng cấp thư viện chia sẻ ảnh hưởng đến tất cả các chương trình phụ thuộc
trên nó nó có thể là một bất lợi. Ví dụ, nếu như bạn đang phát triển một phần mềm then chốt, bạn
đã có thể liên kết tới kho trữ tĩnh tuy nhiên nâng cấp thư viện chia sẻ trên hệ thống sẽ khơng ảnh
hưởng tới chương trình của bạn. (Cách khác người sử dụng có thể nâng cấp thư viện chia sẻ,
bằng cách ngắt chương trình của bạn, và gọi sự hỗ trợ )
Nếu như bạn không thể cài đặt thư viện của bạn ở trong /lib hoặc /usr/lib, bạn nên suy nghĩ chắc
chắn trước khi sử dụng thư viện chia sẻ. Cá biệt, -Wl, -rpath thủ thuật sẽ không làm việc nếu
như bạn không biết nơi mà thư viện sẽ kết thúc. Và yêu cầu thông của người sử dụng thiết lập
LD_LIBRARY_PATH cách thức thêm bước làm cho chúng. Bởi vì mỗi người sử dụng đã phải
làm một cách đơn lẻ, đó là cơng lao lớn lao họ thêm vào. Bạn sẽ phải xem sự ảnh hưởng thuận
lợi và khơng thuận lợi cho mọi chương trình mà bạn phân phối.

6. Nạp vào động và không nạp

Thỉnh thoảng bạn đã có thể muốn nạp vào một vài code thời gian chạy khơng dùng liên kết cho

code đó. Ví dụ , nghĩ tới ứng dụng hỗ trợ modules “plug-in”, như là một trình duyệt web. Trình
duyệt cho phép bên thứ 3 phát triển tạo ra plug-ins để cung ứng các hàm thêm vào. Trình duyệt
web sau đó tự động nạp vào code ở các thư viện. Các hàm sẵn có sử dụng dưới Linux bởi sử
dụng các hàm dlopen. Bạn có thể mở một thư viện chia sẻ tên là libtest.so bởi lời gọi dlopen như
là:
dlopen ("libtest.so", RTLD_LAZY)


Việc tải sử dụng các hàm, bao gồm <dlfcn.h> ở đầu file và liên kết với tùy chọn ldl một cách
tình cờ . Sự trả lại các giá trị từ các hàm là một void * đã sử dụng như là một kênh truy cập bộ
nhớ ở thư viện chia sẻ.Bạn có thể bỏ qua giá trị tới hàm dlsym tới khi thu được địa chỉ của một
hàm cái đã được nạp vào với thư viện chia sẻ. Ví dụ, nếu libtest.so định nghĩa một hàm tên là
my_function, bạn có thể gọi nó như sau:
void* handle = dlopen ("libtest.so", RTLD_LAZY);
void (*test)() = dlsym (handle, "my_function");
(*test)();
dlclose (handle);
Hệ thống dlsym gọi có thể sử dụng để đạt được con trỏ tới biến số tĩnh ở trong thư viện chia sẻ.
Cả dlopen và dlsym trả về NULL nếu như nó khơng thành cơng. Trong sự việc đó, bạn có thể gọi
dlerror( khơng có tham số) để đạt được một thông báo lỗi diễn tả rõ vấn đề đó. Hàm dlclose
khơng nạp vào thư viện chia sẻ. Kĩ thuật, dlopen nạp thực sự thư viện chỉ có thể khi nó chưa sẵn
sàng nạp. Nếu thư viện đã vào nạp, dlopen dễ dành tăng một tham chiếu có giá trị. Tương tự,
dlclose giảm tham chiếu có giá trị và khơng nạp thư viện chỉ khi tham chiếu có giá trị là zero.
Nếu như bạn đang viết code ở trên thư viên chia sẻ C++, bạn sẽ chắc chắn muốn trình bày các
hàm và các tham số cái mà bạn dự định truy xuất ở một nơi nào đó với ở ngồi liên kết “C” đã
chỉ rõ. Trong trường hợp đó, nếu hàm trong C++ my_function ở trong một thư viện chia sẻ và
bạn muốn truy xuất nó với dlsym, bạn nên trình bày nó như sau:
extern "C" void foo ();
Ngăn cản của trình biên dịch trong C++ là từ việc sai tên hàm, cái sẽ thay đổi tên của hàm là từ
foo tới sự khác biệt,các tên được mã hóa thêm thơng tin về các hàm. Một trình biên dịch sẽ

khơng là hỏng tên; nó sẽ sử dụng bất cứ cái tên nào mà bạn đưa vào thơng qua tên hàm và biến
số.

III. Tìm hiểu một chút về shell
Shell là chương trình giữa bạn và Linux (hay nói chính xác hơn là giữa bạn với nhân Linux). Mỗi
lệnh bạn gõ ra sẽ được Shell diễn dịch rồi chuyển tới nhân Linux. Nói một cách dễ hiểu Shell là
bộ diễn dịch ngơn ngữ lệnh, ngồi ra nó cịn tận dụng triệt để các trình tiện ích và chương trình
ứng dụng có trên hệ thống…
Các loại Shell thơng dụng:
Trong thế giới Unix/Linux có rất nhiều Shell…
1) Shell Bourne (sh)
Do Steven Bourne viết, đó là Shell nguyên thuỷ có mặt trên hầu hết các hệ thống Unix/Linux…
Nó rất hữu dụng cho việc lập trình Shell nhưng nó khơng xử lý tương tác người dung như các
Shell khác…
2) Bourne Again Shell (bash)
Đây là phần mở rộng của sh, nó kế thừa những gì sh đã có và phá huy những gì sh chưa có…Nó
có giao diện lập trình rất mạnh và linh hoạt…Cùng với giao diện lệnh dễ dung…Đây là Shell
được cài đặt mặc định trên các hệ thống Linux.


3) Shell C (csh) Đáp ứng tương thích cho người dung…Nó hỗ trợ rất mạnh cho những
Programmer C…và với đặc tính tự động hồn thành dịng lệnh…
4) Shell Korn (ksh) Có thể nói đây là một Shell tuyệt vời, nó kết hợp tính năng ưu việt của sh và
csh…
Ngồi ra cịn có một số Shell khác như: ssh, nfssh, mcsh…
MC (Midnight Commander) một Shell thực hiện yêu cầu của người dung thông qua môi trường
đồ họa…Tương tự như NC (Norton Commander) trong DOS…

IV.Cách biên dịch một chương trình C.
Ngồi vịêc sử dụng IDE biên dịch code C (ví dụ như Dev C, Borland C… ), khác với việc viết

một chương trình C trên các hệ điều hành khác như Windows, Linux có tích hợp sẵn cơng cụ để
biên dịch code cho một chương trình C. Như vậy, chúng ta có hai cách để biên dịch được một
chương trình C, đó là : 1. Sử dụng cơng cụ có sẵn trong hệ điều hành Linux.
2. Sử dụng IDE biên dịch code. (Ở đây, chúng ta tìm hiểu về IDE
Codeblock)

a) Biên dịch bằng cơng cụ có sẵn
1.1 Soạn thảo với Emacs
Emacs là trình soạn thảo văn bản đa chức năng. Đây là phần mềm tự do, chạy được trên nhiều hệ
điều hành và có thể mở rộng để thêm vào chức năng mới. Emacs phổ biến trong giới lập trình
máy tính và người dùng máy tính thơng thạo kĩ thuật.
Chương trình EMACS, tên được tạo ra từ Editor MACroS, đầu tiên dùng cho trình soạn thảo
TECO (Text Editor and Corrector) được Richard Stallman, Guy Steele và Dave Moon viết năm
vào 1976. Nó dựa trên cặp chương trình soạn thảo TECO-macro là TECMAC và TMACS được
viết bởi Guy Steele, Dave Moon, Richard Greenblatt, Charles Frankston và một số người khác.
Qua thời gian đã xuất hiện nhiều phiên bản Emacs, nhưng ngày nay 2 phiên bản phổ biến nhất là
GNU Emacs, do Richard Stallman bắt đầu viết vào 1984, và XEmacs, phân nhánh từ GNU
Emacs năm 1991. Cả hai đều dùng ngôn ngữ Emacs Lisp có khả năng mở rộng mạnh mẽ, cho
phép chúng xử lí nhiều tác vụ khác nhau, từ việc lập trình và biên dịch chương trình máy tính
đến duyệt web.
Emacs có thể chạy trên nhiều hệ điều hành khác nhau như các hệ thống giống Unix (GNU/Linux,
các loại BSD, Solaris, AIX, v.v.), MS-DOS, Microsoft Windows, OpenVMS và Mac OS X.
Emacs chạy trên giao diện văn bản lẫn đồ hoạ. Trên các hệ điều hành giống Unix, Emacs dùng
hệ thống X Window để tạo giao diện đồ hoạ trực tiếp hoặc thông qua "widget toolkit" như Motif,


LessTif hay GTK+. Emacs có thể dùng giao diện đồ hoạ nguyên thuỷ của Mac OS X và
Microsoft Windows.
Một số người phân biệt chữ emacs viết thường, dùng để chỉ các trình biên tập giống Emacs (nhất
là GNU Emacs và XEmacs), và Emacs viết hoa chữ đầu, dùng để chỉ GNU Emacs.

Emacs hiện là một phía của cuộc chiến trình biên tập, phía bên kia là vi.

Tính năng:









Soạn thảo trên nhiều cửa sổ (window) và bộ đệm (buffer)
Tìm kiếm, thay thế, tự sửa lỗi
Soạn thảo đệ quy (recursive edit): cho phép soạn thảo khi một câu lệnh đang thực hiện
giữa chừng
Nhiều chế độ soạn thảo: văn bản thường, các file chương trình, ngơn ngữ đánh dấu
(HTML), LaTeX, vẽ hình bằng các kí tự
Các macro bàn phím
Sửa đổi theo ý thích cá nhân bằng cách chỉnh sửa các biến của chương trình
Lập trình bằng ngơn ngữ Emacs Lisp
Nhiều chương trình phụ trợ (danh sách thư mục, đọc và soạn e-mail, trò chơi ...)

Một trình biên soạn (editor) là một chương trình mà bạn dùng để soạn thảo mã nguồn. Có rất
nhiều các trình biên soạn khác nhau có thể dùng được với Linux, nhưng trình biên soạn phổ biến
và được đề cao nhất đương nhiên là GNU Emacs.
1.1.1 Mở một file nguồn C hoặc C++
Bạn có thể khởi động Emacs bằng cách gõ emacs trong cửa sổ lệnh và nhấn nút Return. Khi
Emacs đã được khởi động, bạn có thể sử dụng các ký hiệu phía trên để tạo một file mới. Bấm
vào menu Files, chọn Open Files, rồi sau đó gõ tên file mà bạn muốn mở vào ô “minibuffer” ở

phía dưới cùng mành hình. Nếu bạn muốn tạo một file nguồn C, bạn kết thúc tên file bằng .c
hoặc .h. Nếu bạn muốn tạo một file nguồn C++, bạn kết thúc tên file bằng
.cpp, .hpp, .cxx, .hxx, .C hoặc .H. Khi file đã được mở ra, bạn có thể soạn thảo như mong muốn
bằng bất kỳ chương trình soạn thảo văn bản thông thường nào. Để lưu file, chọn Save Buffer
trong menu Files. Khi bạn đã kết thúc sử dụng Emacs, bạn có thể chọn Exit Emacs trong menu
Files.
Nếu bạn khơng thích trỏ và bấm, bạn có thể sử dụng các phím tắt để tự động mở, lưu file và thốt
Emacs. Để mở một file, bấm tổ hợp phím Ctrl+X rồi Ctrl+F. Để lưu một file, bấm Ctrl+X rồi
Ctrl+S. Để thoát Emacs, bấm Ctrl+X rồi Ctrl+C. Nếu bạn muốn làm quen hơn với Emacs, bạn
nên chọn mục Emacs Tutorial trong Help menu. Tutorial cung cấp cho bạn rất nhiều hướng dẫn
để bạn sử dụng Emacs một cách hiệu quả.
1.1.2 Tự động định dạng
Nếu bạn đã quen với việc lập trình trong mơi trường phát triển hợp nhất (Integrated
Development Environment - IDE), bạn cũng sẽ quen với việc trình biên soạn giúp bạn định dạng
đoạn mã của mình. Emacs có thể cung cấp kiểu chức năng tương tự như vậy. Nếu bạn mở một


file nguồn C hay C++, Emacs sẽ tự động hiểu rằng file đó chứa mã nguồn chứ khơng phải chỉ là
một đoạn văn bản thơng thường. Nếu bạn nhấn phím Tab trên một dòng trống, Emacs sẽ dịch
chuyển con trỏ vào một khoảng. Nếu bạn nhấn phím Tab tại một dòng đã chứa sẵn một đoạn mã,
Emacs sẽ đẩy lùi đoạn mã vào một khoảng. Ví dụ như bạn đã gõ vào đoạn mã sau:
Int main()
{
printf (“Hello, world\n”);
}
Nếu bạn nhấn phím Tab ở dịng lệnh gọi hàm printf, Emacs sẽ định dạng lại đoạn mã của bạn
thành như này:
Int main()
{
printf (“Hello, world\n”);

}
Khi bạn sử dụng Emacs nhiều hơn, bạn sẽ thấy nó có thể giúp bạn thực hiện tất cả các kiểu định
dạng phức tạp như thế nào. Nếu bạn cịn muốn nhiều hơn thế, bạn có thể lập trình cho Emacs thể
hiện một cách chính xác bất kỳ kiểu tự động định dạng nào bạn có thể nghĩ ra. Nhiều người đã sử
dụng tính năng tiện lợi này để bổ sung cho các chế độ biên soạn của Emacs trở nên đơn giản như
soạn thảo văn bản, cung cấp phương tiện làm game, và bổ sung cơ sở dữ liệu.
1.1.3 Làm nổi bật cú pháp
Để thêm vào khả năng định dạng mãn nguồn của bạn, Emacs có thể khiến cho việc đọc mã
nguồn C hoặc C++ trở nên dễ dàng hơn bằng việc sử dụng các màu sắc khác nhau cho các cú
pháp lệnh khác nhau. Ví dụ, Emacs có thể chuyển các từ khố thành một màu, các loại kiểu dữ
liệu như int một màu khác…
Sử dụng màu sắc khiến cho việc xác định vị trí của các lỗi cú pháp trở nên dễ dàng hơn rất nhiều.
Cách dễ nhất để bật chế độ đổi màu là thay đổi file ~/.emacs và thêm vào xâu sau:
(global-font-lock-mode t)
Lưu file đó lại, thốt Emacs và khởi động lại Emacs.
Bạn có thể đã thấy rằng xâu ký tự trên trông giống như một đoạn mã của ngơn ngữ lập trình
LISP. Đó là vì xâu ký tự đó chính là đoạn mã LISP. Hầu hết phần mềm Emacs được viết bằng
ngôn ngữ LISP. Bạn có thể thêm chức năng cho Emacs bằng cách viết thêm mã LISP.
1.2 Biên dịch với GCC
Một trình biên dịch chuyển mã nguồn từ dạng con người có thể đọc được sang dạng mã máy để
chương trình có thể chạy. Tất cả các trình biên dịch có thể sử dụng trên Linux đều là một phần
của bộ tổng hợp trình biên dịch GNU, thường được biết đến với tên gọi GCC. GCC cũng bao
gồm các trình biên dịch cho C, C++, Java, Objective-C, Fortran, và Chill.
Giả sử bạn có một dự án như Listing 1.2 với một file mã nguồn C++ (reciprocal.cpp) và một file
mã nguồn C (main.c) như Listing 1.1. Hai file này sẽ được biên dịch và liên kết cùng nhau để tạo
nên một chương trình gọi là reciprocal. Chương trình này sẽ tính tốn số nghịch đảo của một số
nguyên.
Listing 1.1 (main.c) C source file - main.c
#include <stdio.h>
#include “reciprocal.hpp”



int main (int argc, char **argv)
{
int i;
i = atoi (atgv[1]);
printf (“Số nghịch đảo của %d là %g\n”, i, reciprocal(i));
return 0;
}
Listing 1.2 (reciprocal.cpp) C++ source file - reciprocal.cpp
#include <cassert>
#include ”reciprocal.hpp”
double reciprocal (int i) {
//i nên khác 0.
assert (i != 0);
return 1.0/i;
}
Một file tiêu đề gọi là reciprocal.hpp (xem listing 1.3).
Listing 1.3 (reciprocal.hpp) Header file - reciprocal.hpp
#ifdef __cplusplus
extern “C” {
#endif
extern double reciprocal (int i);
#ifdef __cplusplus
}
#endif
Bước đầu tiên là chuyển mã nguồn C và C++ thành mã máy.
1.2.1 Biên dịch một file nguồn
Tên của trình biên dịch C là gcc. Để biên dịch một file nguồn C, bạn dùng lựa chọn -c. Vì vậy,
nhập lựa đoạn lệnh sau vào vị trí dấu nhắc con trỏ lệnh để biên dịch file main.c:

% gcc -c main.c
Ta được một file tên là main.o.
Trình biên dịch cho C++ được gọi là g++. Quá trình xử lý của nó rất giống với gcc; việc dịch file
reciprocal.cpp sẽ được hoàn tất với đoạn lệnh sau:
% g++ -c reciprocal.cpp
Lựa chọn -c báo cho g++ chỉ dịch chương trình sang một file đối tượng; khơng có lựa chọn đó,
g++ sẽ thử kết nối chương trình để tạo nên một chương trình hồn thiện, có thể chạy được. Sau
khi bạn gõ lệnh này, bạn sẽ có một file đối tượng gọi là reciprocal.o.
Tất nhiên bạn sẽ cần đến một vài lựa chọn khác để xây dựng một chương trình lớn. Lựa chọn -I
được dùng để báo cho GCC biết cần tìm các file đầu đề ở đâu. Nếu để mặc định, GCC sẽ tìm
trong thư mục hiện hành và trong các thư mục chứa các file tiêu đề của các thư viện chuẩn khi
cài đặt. Nếu bạn cần dùng đến một file tiêu đề ở chỗ khác, bạn sẽ cần lựa chọn -I. Ví dụ, giả sử
như dự án của bạn có một thư mục tên là src để chứa các file nguồn, và một thư mục tên là


include. Bạn sẽ dịch file reciprocal.cpp như sau để chỉ định rằng g++ phải sử dụng thư mục
../include để tìm file reciprocal.hpp:
% g++ -c -I ../include reciprocal.cpp
Đôi khi bạn sẽ muốn sử dụng các macro trong dịng lệnh. Ví dụ như, trong đoạn mã sản phẩm
cuối cùng, bạn không muốn việc kiểm tra được thực hiện trong file reciprocal.cpp; vốn chỉ để
giúp bạn sửa lỗi chương trình. Bạn có thể tắt chức năng kiểm tra này bằng việc dùng macro
NDEBUG. Bạn có thể thêm #define vào file reciprocal.cpp, nhưng như vậy sẽ cần phải thay đổi
cả mã nguồn. Sẽ là dễ dàng hơn nếu như chỉ việc dùng NDEBUG vào dòng lệnh như sau:
% g++ -c -D NDEBUG reciprocal.cpp
Nếu bạn muốn sử dụng NDEBUG với vài giá trị cụ thể, bạn có thể làm như sau:
% g++ -c -D NDEBUG=3 reciprocal.cpp
Nếu bạn thực sự đang xây dựng sản phẩm cuối cùng, hẳn bạn sẽ muốn GCC tối ưu hoá đoạn mã
để nó có thể chạy nhanh hết mức có thể. Bạn có thể làm điều này bằng việc sử dụng lựa chọn -02
trên dịng lệnh. (GCC có nhiều cấp độ tối ưu khác nhau; cấp độ thứ hai được sử dụng cho hầu hết
các chương trình.) Ví dụ, đoạn lệnh biên dịch file reciprocal.cpp sau có kèm theo lựa chọn tối ưu

hoá:
% g++ -c -02 reciprocal.cpp
Chú ý rằng dịch chương trình với chức năng tối ưu hố có thể làm chương trình của bạn khó có
thể sửa lỗi bằng trình sửa lỗi hơn (xem phần 1.4, “Sửa lỗi với GDB”). Cũng như vậy, trong các
trường hợp cụ thể, biên dịch với chức năng tối ưu hố có thể làm lộ ra các lỗi trong chương trình
của bạn mà trước đó khó có thể phát hiện.
Bạn có thể dùng rất nhiều các lựa chọn khác với gcc và g++. Cách tối nhất để có một danh sách
hồn chỉnh các lựa chọn là tìm các tài liệu trên mạng. Bạn có thể làm việc này bằng cách gõ
dòng lệnh sau vào cửa sổ lệnh:
% info gcc
1.2.2 Kết nối các file đối tượng
Bây giờ bạn đã dịch main.c và utilities.cpp, bạn sẽ muốn liên kết chúng với nhau. Bạn nên luôn
dùng g++ để kết nối một chương trình chứa mã C++, kể cả khi nó có chứa mã C. Nếu chương
trình của bạn chỉ chứa mã C, bạn nên dùng gcc thay vì g++. Vì chương trình này chứa cả mã C
và C++, bạn nên sử dụng g++ như sau:
% g++ -o reciprocal main.o reciprcocal.o
Lựa chọn -o sẽ khiến tên của file được tạo thành từ bước kết nối. Bây giờ bạn có thể chạy file
reciprocal như sau:
% ./reciprocal 7
The reciprocal of 7 is 0.142857
Như bạn có thể thấy, g++ đã tự động kết nối vào thư viện biên dịch C chuẩn có chứa hàm printf.
Nếu bạn cần kết nối tới những thư viện khác (như bộ công cụ đồ hoạ), bạn sẽ phải chỉ định thư
viện đó bằng lựa chọn -l. Trong Linux, hầu hết tên của các thư viện luôn bắt đầu bằng lib. Ví dụ
như, thư viện PAM (Pluggable Authentication Module) được gọi là libpam.a. Để liên kết với thư
viện libpam.a, bạn phải sử dụng lệnh sau:
% g++ -o reciprocal main.o reciprocal.o -lpam
Trình biên dịch sẽ tự động thêm phần lib vào đầu và phần .a vào cuối.
Với các file đầu đề, trình liên kết sẽ tìm các thư viện trong các chỗ cơ bản, bao gồm cả thư mục
/lib và /usr/lib, vốn chứa các thư viện chuẩn của hệ thống. Nếu bạn muốn trình liên kết tìm trong
các thư mục khác nữa, bạn nên sử dụng lựa chọn -L, đây là một lựa chọn tương đương với lựa



chọn -I. Bạn có thể sử dụng dịng lệnh dưới đây để chỉ định cho trình liên kết tìm các thư viện
trong thư mục /usr/local/lib/pam trước khi tìm trong các chỗ thông thường:
% g++ -o reciprocal main.o reciprocal.o -L/usr/local/lib/pam -lpam
Mặc dù bạn không phải sử dụng lựa chọn -I để khiến bộ chọn trước tìm kiếm trong thư mục hiện
hành, bạn sẽ phải sử dụng lựa chọn -L để khiến bộ liên kết tìm thư viện trong thư mục hiện hành.
Cụ thể, bạn có thể dùng dịng lệnh sau để chỉ định cho trình liên kết tìm thư viện test trong thư
mục hiện hành:
% gcc -o app app.o -L. -ltest
1.3 Tự động hố q trình xử lý với GNU Make

b) Tìm hiểu một chút về makefile
1.Tổng quan về make

Cơng cụ make tự động xác định những thành phần của một chương trình lớn cần phải biên tập
lại, và đưa ra những câu lệnh để biên tập lại chúng. Nó được dung nhiều từ khi lập trình C trên
Linux trở lên phổ biến, nhưng bạn có thể sử dụng make với những ngơn ngữ lập trình khác mà
trình biên dịch của nó có thể chạy với những lệnh shell. Thực vậy, make khơng giới hạn chế các
chương trình.
Để chuẩn bị sử dụng make bạn cần phải viết một file, gọi là makefile, dùng để miêu tả mối quan
hệ giữa các file trong một chương trình và cung cấp lệnh cho việc cập nhật các file khác. Trong
một chương trình, tập tin thi hành sẽ được cập nhật từ file object.
Với một makefile đã có, vào một thời điểm khác, bạn có thể thay đổi các file mã nguồn bằng câu
lệnh shell đơn giản:
Make
là đủ để thi hành tất cả các thay đổi trong biên tập lệnh. Chương trình Make sử dụng cơ sở dữ liệu
makefile và giảm thiểu thời gian cho việc cập nhật các file.Với mỗi file này, nó sẽ đưa ra các
dòng lệnh được ghi lại trong cơ sở dữ liệu.


2.Giới thiệu về makefile

Bạn cần một file gọi là makefile để ra lệnh cho make phải làm gì. Makefile báo cho make biết
làm thế nào để biên dịch và kết nối một chương trình. Khi make biên dịch lại trình soạn thảo, mỗi
file mã nguồn cũng bị biên tập lại. Nếu tiêu để file thay đổi thì mỗi file mã nguồn bao gồm tiêu
đề file phải được biên dịch lại để an toàn. Mỗi sản phẩm sau khi biên dịch sẽ tương ứng với một
file mã nguồn. Cuối cùng, nếu một file mã nguồn được biên dịch lại, tất cả các object file cũng
như các file được viết ra, được lưu lại trong q trình biên dịch trước đó cũng phải được liên kết
với nhau để sản xuất ra trình biên soạn mới có thể thực thi.

3.Một makefile đơn giản.

Đây là một makefile đơn giản dễ hiểu mô tả cách một tập tin thi hành được gọi edit phụ thuộc
vào tám file object mà trong đó phụ thuộc vào tám file mã nguồn C và ba file
tiêu đề (header).
Trong ví dụ này, tất cả các file C bao gồm defs.h, nhưng chỉ những lệnh định

nghĩa biên soạn là sử dụng thư viện command.h, và chỉ những file cấp độ thấp mà có thay đổi
buffer trình biên dịch là sử dụng thư viện buffer.h


edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h

cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

Chúng ta chia cắt mỗi dòng lệnh quá dài bằng những dấu “\” để có thể dễ nhìn hơn. Sử dụng
makefile này để tạo tập tin thi hành gọi là edit, gõ:
Make

Để sử dụng makefile này để xoá tập tin thi hành và tất cả các object file từ thư mục, gõ:
make clean

Trong ví dụ makefile, đích (target) bao gồm các tập tin thi hành `edit', và object file `main.o'
và `kbd.o'. Không thể thiếu là các file như `main.c' and `defs.h'. . Trong thực tế, mỗi file
`.o' đã bao gồm cả target và phần chính. Những dịng lệnh bao gồm : `cc -c main.c' và
`cc -c kbd.c'.
Khi target là một file, nó cần được biên tập lại hoặc được kết nối nếu một trong số phần chính
nào đó thay đổi. Cần bổ sung them, phần chính tạo ra chính nó, nên cập nhật trước. Ví dụ, edit
phụ thuộc vào một trong tám file object trong file tiêu đề (header file) defs.h.
Một lệnh shell viết trên một dòng chứa target và phần chính. Những lệnh shell cho biết làm thé

nào để cập nhật target file. Ký tự tab phải có ở mỗi dịng lệnh nhận ra dịng lệnh từ các dịng
khác trong makefile. Target `clean' khơng phải là một file, chỉ đơn thuần là tên của một hành
động.bạn không muốn thực hiện hành động trong rule (quy tắc) này, `clean' khơng phải là
phần chính của bất kỳ rule nào khác. Do đó, make khơng thực hiện điều gì trừ khi bạn ra lệnh rõ
ràng cho nó. Chú ý rằng rule này khơng phải là phần chính, nó chỉ có mục đích là chạy các lệnh
rõ rang.

4.Make xử lý một makefile như thế nào?


Theo mặc định, make bắt đầu với target đầu tiên (khơng có tên của target nào bắt đầu bằng `.')
Nó được gọi là default goal. (Goal là taget mà make cố gắng cập nhật. Bạn có thể ghi đè tính
chất này bằng cách sử dụng dòng lệnh hoặc với biến đặc biệt .DEFAULT_GOAL ). Trong ví dụ trên,
default goal là cập nhật chương trình edit; bởi vậy, chúng ta đưa rule này ra đầu tiên.
Vì thế, khi bạn đưa ra dòng lệnh : make
Make đọc makefile trong thư mục hiện tại anh bắt đầu xử lý rule đầu tiên. Trong ví dụ, rule này
lien kết lại edit nhưng trước khi make có thể xử lý hồn tồn rule này, nó phải xử lý các rule
cho các file mà edit phụ thuộc, trong trường hợp này là các file object. Mỗi file đều đựơc xử
lý theo rule của riêng mình. Những rule này cho phép cập nhật các file `.o' bằng cách biên dịch
các file mã nguồn. Sự biên tập lại phải được làm nếu file mã nguồn hoặc file tiêu đề có tên giống
với phần chính.
Các rule khác được xử lý vì các target của chúng xuất hiện như thành phần chính của goal. Nếu
các rule khác khơng phụ thuộc vào goal, rule đó khơng được xử lý, trừ khi bạn lệnh cho make
thực hiện.
Trước khi biên dịch lại mọt file object, make coi việc cập nhật nó như việc bắt buộc, file mã
nguồn và file tiêu đề. Makefile không chỉ định rõ bất cứ điều gì sẽ được làm cho chúng, các file
`.c' và `.h' không là target của bất kỳ rule nào, nên make không thực hiện điều gì cho chúng.
Nhưng make sẽ tự động cập nhật chương trình C được tạo ra.
Sauk hi biên dịch lại các file object cần thiết, make quyết định xem có hay không liên kết lại
edit. Việc này sẽ phải được làm nếu file edit khơng tồn tại, hoặc nếu có file object nào đó

mới hơn nó. Nếu một object file được biên dịch lại, nó sẽ mới hơn edit , nên edit được liên
kết lại. Vì vậy, nếu chúng ta thay đổi file insert.c và chạy make, make sẽ biên dịch file đó để
cập nhật insert.o, sau đó sẽ liên kết edit. Nếu ta thay đổi file command.h và chạy make,
make sẽ biên dịch lại file object kbd.o, command.o và files.o và kết nối với file edit.

5.Quy tắc viết makefile

Một makefile đơn giản có gồm những quy tắc (rules):
Target …: prerequisites ...
command
...
...
Target : thường là tên của một file sinh ra bởi chương trình , ví dụ như target là một object file .
Target có thể là tên của một hành động được thực thi, ví dụ như “clean”.
Prerequisites: là một file được sử dụng như dữ liệu vào để tạo ra Target. Một Target thường phụ
thuộc vào một vài file khác.
Command: là một hành động mà make thực thi. Một quy tắc có thể có hơn một lệnh là được viết
trên mỗi dòng riêng biệt. Chú ý: khi viết bạn cần cách 1 tab từ đầu dòng cho mõi dòng lệnh để
tránh gây sự nhầm lẫn. Thơng thường, các dịng lệnh trong một quy tắc như là một điều bắt buộc
phục vụ cho việc tao ra các Target file.
Một quy tắc (rule) sẽ giải thích làm thế nào và khi nào sẽ làm lại (remake) một file nào đó, cái
mà Target của một rule khác. Make thực thi lệnh như một điều kiện để tạo hoặc cập nhật Target.
Một rule có thể giải thích làm thế nào và bao giờ sẽ thực thi một hành động.
Một makefile có thể chứa các văn bản không nằm trong rule, nhưng một makefile đơn giản chỉ
cần chứa rule.


5.1 Nội dung của makefile

Makefile bao gồm:

 Quy tắc rõ ràng (explicit rule) : nói khi nào là làm thế nào để làm lại các file. Gọi
đến các target của rule. Nó ghi vào danh sách các file mà Target phụ thuộc vào,
gọi đến Prerequisites của target và có thể đưa ra các lệnh để tạo và cập nhật các
target.
 Quy tắc tuyệt đối (implicit rule): quyết định khi nào và làm thé nào để làm lại một
lớp của các file căn cứ vào tên của chúng. Nó mơ tả làm thế nào một target có thể
phụ thuộc vào một file với một cái tên tương tự như của target và đưa ra các lệnh
để tạo hoặc cập nhật các target.
 Định nghĩa biến số (variable definition) : là một dòng định rõ một giá trị chuỗi
văn bản cho một biến số mà có thể được thay thế bên trong văn bản sau. Một ví
dụ Makefile đơn giản đưa ra một định nghĩa biến số cho object như một danh sách
của tất cả các object file.
 Sự chỉ dẫn (directive) : là một lệnh cho make thực hiện một việc nào đó đặc biệt
trong khi đọc các makefile. Nó bao gồm:
 Đọc một makefile khác
 Quyết định xem sử dụng hay bỏ qua một phần của makefile.
 Định nghĩa một biến từ một chuỗi nguyên văn chứa nội dung gồm nhiều
dòng.
 Trong phạm vi tập lệnh (Nếu dòng bắt đầu với ký tự TAB) tồn bộ dịng bị bỏ qua trong
shell, đúng như với một dòng khác bắt đầu với TAB. Shell quyết định làm thé nào để
dịch văn bản.
 Trong phạm vi chỉ dẫn định nghĩa, lời chú ý (comments) khơng bị bỏ qua trong q trình
định nghĩa biến số, nhưng đúng hơn là nó giữ cho khơng bị thay đổi giá trị của biến số.
Trong khi biến số được mở rộng, chúng sẽ được ưu tiên như make lời chú ý hoặc như văn
bản tập lệnh, phụ thuộc vào ngữ cảnh mà biến số được định danh.

5.2

Đặt tên cho makefile


Theo mặc định, khi make tìm kiếm một makefile, nó sẽ theo dõi tên của makefile hợp lệ :
GNUmakefile, makefile và Makefile. Thông thường, bạn gọi makefile là
makefile hoặc Makefile. Tên đầu tiên được kiểm tra là GNUmakefile, nó không
được đề nghị cho tất cả makefile. Bạn nên sử dụng tên này nếu bạn có một makefile riêng
biệt của GNU make và nó sẽ khơng thể được hiểu bởi một phiên bản make khác. Các chương
trình make khác tìm kiếm makefile và Makefile nhưng khơng tìm GNUmakefile.
Nếu make khơng tìm thấy cái tên nào trong các tên trên, nó sẽ không sử dụng makefile. Bạn
sẽ phải định rõ một mục tiêu với một đối số lệnh, và make sẽ cố gắng đốn nhận làm thé nào
để làm lại nó sử dụng duy nhất nó với các rule chuẩn được cài đặt sẵn.
Nếu bạn không muốn sử dụng tên chuẩn cho makefile, bạn có thể chỉ rõ tên của makefile với
tuỳ chọn : `-f' hoặc `--file' .Đối số `-f' hoặc `--file' sẽ yêu cầu make hiểu file name
này giống như makefile. Tất cả các makefile đều được móc nối với nhau theo một sự sắp xếp
nhất định. Với những tên mặc định của makefile là GNUmakefile, makefile và
Makefile sẽ không bị kiểm tra tự động nếu như bạn định rõ `-f' hoặc `--file'.


5.3 Including Other Makefile

Include chỉ dẫn ra lệnh make hoãn đọc các makefile hiện tại một hoặc nhiều các makefile
khác trước khi tiếp tục. Chỉ dẫn này là một dòng trong makefile có dạng :
include filenames …
filenames chứa nội dung tên của file shell. nếu filenames trống, thì khơng có lỗi nào đựoc in
ra.Không gian them được chấp nhận và bỏ qua ở đầu dịng nhưng ký tự tab thì không được
chấp nhận. Whitespace được yêu cầu giữa include tên file. Whitespace them được bỏ qua và
kết thúc ở phần hướng dẫn. Một chú thích (comment ) được bắt đầu bằng dấu “#” được cho
phép ở vị trí kết thúc dòng. Nếu như file name chứa biến hoặc hàm tham chiếu, chúng sẽ
được mở rộng.
Ví dụ, nếu bạn có 3 file .mk là a.mk, b.mk và c.mk và phần mở rộng $(bar) tới bish bash ,
theo dõi biểu thức sau:
include foo *.mk $(bar)


Nó sẽ tương đương với:

include foo a.mk b.mk c.mk bish bash
lý một include chỉ dẫn, nó sẽ dừng việc đọc nội dung

Khi make xử
của makefile và đọc luân
phiên từ một danh sách file. Khi hoàn thành, make tiếp tục đọc makefile trong chỉ dẫn xuất
hiện.
Một lý do để sử dụng include chỉ dẫn là khi một vài chương trình được điều khiển bởi các
makefile riêng lẻ trong các thư mục khác nhau, cần sử dụng một bộ định nghĩa biến hoặc các
rule mẫu.
Nếu bạn muốn make bỏ qua hồn tồn một makefile khơng tồn tại và khơng thể đựoc viết lại,
với khơng có dịng thơng báo, hãy sử dụng –include chỉ dẫn của include,tương tự:
-include filenames...

Hành động này giống như include trong các cách loại ra rằng không có sai sót nếu như một
filenames khơng tồn tại. Để phù hợp với một vài make bổ sung, sinclude là một tên khác
cho –include.

5.4 Biến Makefiles

Nếu môi trường Biến makefiles được định nghĩa, make coi giá trị của nó như một danh sách
tên của makefile thêm để đọc trước các biến khác. Công việc này rất giống với include chỉ
dẫn: các thư mục khác nhau sẽ được tìm kiếm cho những file này. Trong phép cộng, mục tiêu
mặc định không được ghi lại từ một trong các makefile này và sẽ khơng có lỗi nào nếu như
những file được ghi vào danh sách của makefiles khơng được tìm thấy.
Mục đích chính của Makefile là trong giao tiếp giữa lời gọi đệ quy của make . Nó khơng
được khuyến khích dung để bố trí mơi trường biến trước những bước đầu lời gọi của make .

vì nó gây lên sự hỗn độn với các makefile ở bên ngoài. Tuy nhiên, nếu bạn đang chạy make
mà khơng có các makefile đặc trưng. một makefile trong MAKEFILES có thể được sử dụng
để giúp việc các rule được cài đặt sẵn làm vịêc tốt hơn.

5.5 Biến MAKEFILE_LIST
Vì make đọc các makefile khác nhau, bao gồm sự thu được từ biến MAKEFILES
các dòng lệnh, files mặc định, hoặc từ include chỉ dẫn, tên của chúng sẽ tự động đựơc bổ
sung vào biến MAKEFILE_LIST . Chúng đựơc them vào trước khi make phân tách chúng.

,


Nghĩa là điều đầu tiên makefile làm là xem xét các từ mới nhất trong biến này, nó sẽ là tên
của makefile hiện tại. Makefile hiện tại cũng đã sử dụng include , tuy nhiên, từ mới nhất sẽ
là included chính xác cho makefile.
Nếu một makefile được đặt tên là Makefile có nội dung là:
name1 := $(lastword $(MAKEFILE_LIST))
include inc.mk
name2 := $(lastword $(MAKEFILE_LIST))
all:

@echo name1 = $(name1)
@echo name2 = $(name2)

Sau đó bạn chờ để đưa ra:
name1 = Makefile
name2 = inc.mk

5.6 Một số các loại biến khác


GNU make cũng hỗ trợ một số biến đặc biệt khác. Trừ những tài liệu ở đây, những giá trị này
mất đi những thuộc tính nếu chúng được thiết lập bởi makefile hoặc trên dòng lệnh.
.DEFAULT_GOAL : được sử dụng nếu khơng có target được định danh trên dịng lệnh.
Biến .default_goal cho phép bạn tìm ra các mục tiêu mặc định hiên tại, khởi động lại

thuật toán lựa chọn mục tiêu mặc định bằng cách xoá đi giá trị của chúng, hoặc thiết lập
rõ rang mục tiêu mặc định. Ví dụ:
# Query the default goal.
ifeq ($(.DEFAULT_GOAL),)
$(warning no default goal is set)
endif
.PHONY: foo
foo: ; @echo $@
$(warning default goal is $(.DEFAULT_GOAL))
# Reset the default goal.
.DEFAULT_GOAL :=
.PHONY: bar
bar: ; @echo $@
$(warning default goal is $(.DEFAULT_GOAL))
# Set our own.
.DEFAULT_GOAL := foo

Makefile này in ra:

no default goal is set



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×