Trong bài này chúng ta sẽ tìm hiểu cấu trúc và cách biên dịch của một chương trình C/C++. Kiến thức này giúp bạn nắm được tổng quát cách hoạt động của một chương trình C.
Bài trong serie Học lập trình C từ A tới Z
Cấu trúc chương trình C
Một chương trình C có thể được cấu thành bới 2 file:
File header là một file với định dạng .h chứa các khai báo hàm và định nghĩa marco và có thể được chia sẻ qua nhiều file nguồn. Ngoài ra còn có khai báo các nguyên mẫu hàm để chia sẻ với các file nguồn khác.
File header được sử dụng khi cần liên kết nhiểu file.c (thực thi) với nhau, nếu dự án chỉ có 1 file thực thi thì không cần dùng file header
File thực thi với định dạng .c chứa cách hoạt động của chương trình. Các thành phần của một chương trình C cơ bản bao gồm:
- Các lệnh tiền xử lí
- Các hàm
- Các biến
- Các lệnh và biểu thức
- Các comment (ghi chú về code giúp ích cho công tác bảo trì và nâng cấp)
Lệnh tiền xử lý
Bộ tiền xử lý (Preprocessor) là các directive (chỉ thị), cung cấp chỉ lệnh tới bộ biên dịch để tiền xử lý thông tin trước khi bắt đầu biên dịch thực sự.
#define
Chỉ thị tiền xử lý #define tạo các biểu tượng hằng. Biểu tượng hằng là một macro và mẫu chung của chỉ thị tiền xử lý này trong C/C++ là:
#define ten_cua_macro ten_thay_the
Khi dòng này xuất hiện trong một file, tất cả macro xuất hiện theo sau trong file này sẽ được thay thế bởi ten_thay_the trước khi chương trình được biên dịch. Ví dụ:
#define PI 3.14159
Khi khai báo như này, chúng ta có thể sử dụng từ PI thay cho số 3.14159
#include
Chỉ thị này báo cho trình biên dịch biết file này sử dụng các hàm tại file khác (file được include). Thường là các thư viện, các file khác trong 1 project.
#include <stdio.h> or #include "user.h"
Khi thư viện trong dấu ngoặc nhọn <> trình biên dịch sẽ tìm file đó trong thư viện của nó (thường ở ổ C, ổ cài trình biên dịch)
Khi nằm trong dấu ngoặc kep, trình biên dịch sẽ tìm file đó trong folder chứa file thực thi mà bạn đang lập trình. Ví dụ file .c của bạn đang ở ổ D thì nó sẽ tìm tất cả trong ổ D file nào có tên giống user.h
Biên dịch có điều kiện #ifndef và #endif
Có một số chỉ thị tiền xử lý có thể sử dụng để biên dịch có sự tuyển chọn giữa các phần trong source code của bạn. Tiến trình này được gọi là biên dịch có điều kiện.
Chỉ lệnh tiền xử lý có điều kiện khá giống với cấu trúc lựa chọn if. Bạn xét code sau:
#ifndef NULL #define NULL 0 #endif
Code này nghĩa là nếu chưa định nghĩa NULL thì định nghĩa NULL = 0.
Ngoài ra chúng ta có thể dùng điều kiện tiền xử lý để in ra các LOG nếu DEBUG được định nghĩa phía trước lệnh này. Nếu không có trình biên dịch sẽ bỏ qua luôn
#ifdef DEBUG LOG = "Sai ket qua" #endif
Lệnh LOG để được biên dịch trong chương trình nếu biểu tượng hằng DEBUG đã được định nghĩa ở trước chỉ thị #ifdef DEBUG.
Bạn có thể sử dụng lệnh #if 0 để chú thích một phần của chương trình, như sau:
#if 0 khong duoc bien dich phan code nay #endif
Các biến (Variable)
Là giá trị có thể thay đổi được trong code bằng các phép toán hoặc gán giá trị.
Các biến sẽ được đề cập trong bài viết: Cách khai báo biến trong C
Các hàm (Function)
Là cách chương trình hoạt động, có hàm main() là hàm bắt buộc phải có trong chương trình C, ngoài ra còn có các hàm tự tạo khác.
Các hàm sẽ được đề cập trong bài viết: Cách khai báo hàm trong C
Các lệnh và biểu thức
Các lệnh này có thể là việc sử dụng các hàm khác hoặc các lệnh gán, rẽ nhánh, lặp, tính toán … mà chúng ta sẽ học ở các bài tiếp theo
Chú Thích (Comment)
Đây là những phần viết ra cho lập trình viên hiểu, không có giá trị trong chương trình, sẽ bị trình biên dịch bỏ khi chạy. Có 2 cách
//code này đảo chiều động cơ
/* hàm này làm cái gì đó */
Cách biên dịch mã nguồn trong lập trình C/C++
Trong các bài trước chúng ta đã biết, C là một ngôn ngữ bậc cao và nội dung viết trong đó rất giống với cách viết và suy nghĩ của con người. Tuy nhiên thì trong máy tính chỉ có 2 trạng thái tồn tại đó là có dòng điện chạy qua và không có dòng điện chạy qua, tương ứng với 2 số 0
và 1
, do đó máy tính không thể hiểu được nội dung chúng ta đã viết trong mã nguồn của chương trình C đâu.
Và để cho máy tính có thể hiểu ngôn ngữ con người, chúng ta cần phải biên dịch nội dung đã viết sang dạng 1
, 0
cho máy tính hiểu. Công việc này được gọi là biên dịch chương trình, hay còn gọi là compile chương trình, và công cụ sử dụng để biên dịch chương trình C được gọi là trình biên dịch.
Quá trình được chia ra làm 4 giai đoạn chính:
- Giai đoàn tiền xử lý (Pre-processor)
- Giai đoạn dịch NNBC sang Asembly (Compiler)
- Giai đoạn dịch asembly sang ngôn ngữ máy (Asember)
- Giai đoạn liên kết (Linker)
Preprocessor (tiền xử lý)
Đây là bước đầu tiên trong quá trình biên dịch chương trình. Tại đây sẽ thực hiện các công đoạn chuẩn bị trước khi chúng ta bắt đầu xử lý chính trong chương trình C.
Nói một cách đơn giản thì nếu việc biên dịch mã nguồn C là công việc nấu cơm thì tại Preprocessing (tiền xử lý) chúng ta sẽ tiến hành chuẩn bị gạo, rửa rau cắt thịt v.v.. trước khi bắt đầu nấu cơm vậy.
Có thể kể đến một số xử lý ở Preprocessing (tiền xử lý) trong biên dịch C như sau:
- Load và đọc các library cần thiết sử dụng trong chương trình
- Mở rộng các marcro được định nghĩa sau từ khóa define
- Xử lý trước các lệnh bắt đầu sau ký tự #
- Xóa comment trong mã nguồn, và biên dịch trước một số bộ phận trong mã nguồn.
Compiler (biên dịch)
Tiếp theo Preprocessor chính là Compiler (biên dịch) – xử lý chính trong trình biên dịch chương trình C.
Dựa vào compiler, mã nguồn được viết trong file C từ ngôn ngữ bậc cao mà con người hiểu được sẽ được biên dịch sang ngôn ngữ assembly ở dạng các mã assembly code. Ngôn ngữ assembly ở đây là ngôn ngữ bậc thấp, là ngôn ngữ trung gian giữa ngôn ngữ bậc cao và ngôn ngữ máy tính, có tác dụng chuyển ngôn ngữ bậc cao sang dạng các chỉ thị 1 đối 1 cho máy tính.
Compiler ở đây theo nghĩa hẹp có nghĩa là quá trình biên dịch mã nguồn C sang ngôn ngữ assembly. Tuy nhiên thông thường thì chúng ta cũng sử dụng Compiler theo nghĩa rộng chính là toàn bộ quá trình biên dịch từ Preprocessor (tiền xử lý) đến Linker (liên kết).
Assembler (tập hợp)
Tiếp theo, trình tập hợp Assembler sẽ chuyển đổi các mã assembly code đã dịch ở Compiler ở trên thành các mã máy tính – loại ngôn ngữ mà máy tính có thể hiểu được. Các mã máy tính này được biểu diễn bởi số 0
và số 1
, và được tập hợp trong một file máy tính.
Mặc dù tại giai đoạn này, mã nguồn của chương trình C đã được chuyển thành một file ở dạng mà máy tính có thể hiểu được, nhưng ở giai đoạn này do chúng ta chưa liên kết đủ đủ thông tin trong file, nên file này chưa thể thực thi một cách bình thường được.
Linker (liên kết)
Đây là bước cuối cùng trong biên dịch chương trình trong C. Tại đây, chúng ta sử dụng trình liên kết Linker để liên kết các thông tin còn thiếu như các thư viện (library) chẳng hạn vào file máy tính đã tạo ở Assembler.
Việc liên kết thông tin cuối cùng đã hoàn thành được file máy tính, và chúng ta có thể chạy file này một cách bình thường.
Kết
Đọc xong bài này chắc hẳn các bạn đã hiểu cấu trúc của một chương trình C và cách máy tính biên dịch nó. Nhiều người học lập trình C mà thường quên mất các lý thuyết cơ bản này, làm việc học trở nên khó khăn hơn rất nhiều.
Ok, chúng ta sẽ tới các bài tiếp theo của Serie Học lập trình C từ A tới Z
Nếu thấy có ích hãy chia sẻ bài viết và tham gia nhóm Nghiện Lập Trình để giao lưu và học hỏi nhé.