[C++] Các quá trình biên dịch C++: Tiền xử lý (Preprocess), Biên dịch (Compile), Liên kết (Linker)

Trong C/C++ quá trình biên dịch mã nguồn trải qua 3 quá trình:
   - Tiền xử lý (Preprocess)
   -  Biên dịch (Compile)
   -  Liên kết (Linker). Xem hình sau:



1. Tiền xử lý (Preprocess):
     - Quá trình này không quan tâm đến cú pháp C/C++.
     - Các chỉ thị tiền xử lý bắt đầu bằng ký tự #:
         + Phép thay thế: #define, #undefine
     + Phép chèn tệp tin: #include
     + Phép lựa chọn các dòng lệnh để biên dịch: #if
      #if bieu_thuc_hang  
          bieu_thuc_hang được định nghĩa dạng như sau #define DEBUG 1 //Tức là ở biểu thức thay thế biểu thức hằng phải là một giá trị hằng kiểu interger.

      #ifdef ten_macro  hoặc #ifndef ten_macro
                   ten_macro  được định nghĩa như sau:
          #define TEN_MACRO BIEU_THUC_THAY_THE_MACRO //Biểu thức thay thế tên Macro có thể là chuỗi rỗng.
2. Biên dịch (Compile):
     - Quá trình này sẽ biên dịch các tệp tin .c/.cpp sang tệp tin object .o/.obj. Trong C/C++ biên dịch các tệp tin .c/.cpp từ trên xuống dưới và biên dịch các tệp tin độc lập với nhau, tức là mỗi tệp tin .c/.cpp sẽ được biên dịch sang tệp tin object .o/.obj. (Ở quá trình biên dịch này các tệp tin .c/.cpp không có liên quan gì nhau).

3. Liên kết (Linker):
     - Quá trình này sẽ liên kết tất cả các tệp tin object .o/.obj để tạo ra tệp tin .exe

4. Các dạng sử dụng chỉ thị tiền xử lý:
         4.1 Phép thay thế: #define
       #define tên day_ky_tu
       #define tên

         4.2 Phép chèn tệp tin: #include
       #include <tên tệp tin>
       #include "tên tệp tin"

         4.3 Phép lựa chọn các dòng lệnh để biên dịch: #if
       + Dạng 1:
         #if bieu_thuc_hang
            //Code 1 here
         [#else
            //Code 2 here]
         #endif

       + Dạng 2:
         #if bieu_thuc_hang_1
             //Code 1 here
         #elif bien_thuc_hang_2
             //Code 1 here
             ...
         #elif bieu_thuc_hang_n
             //Code n here
         [#else
             //Code n + 1 here]
         #endif

       + Dạng 3:  ten_macro được định nghĩa bởi #define
         #ifdef ten_macro
            //Code 1 here 
         [#else
            //Code 2 here ]    
         #endif

       + Dạng 4:
         #ifndef ten_macro
            //Code 1 here 
         [#else
            //Code 2 here ]        
         #endif

5. Một số ví dụ về quá trình tiền xử lý:
Chú ý:  → để biết quá trình preprocess bằng cách setting biến preprocess trong phần thuộc tính, sau đó vào thư mục xem tệp tin .i sinh ra trong quá trình preprocess.

Ví dụ 1: Ví dụ về quá trình define 
          Định nghĩa một giá trị hằng có mấy cách ? Cách nào hay hơn trong ví dụ sau ?
#define PI 3.14

const float PI = 3.14f;
         → Đáp án: Cách 2 hay hơn vì cách 1 không biết kiểu giá trị.

Ví dụ 2Ví dụ về quá trình define  biểu thức
    Hỏi kết quả in ra giá trị bao nhiêu ?
    #include <iostream>
    using namespace std;

    #define ADD(x, y) x + y

    void main()
    {
          cout << ADD(2, 3) << endl;
          getchar();
    }
→ đáp án: kết quả in ra 5.
→ Lưu ý: Khi định nghĩa một biểu thức ta nên đặt nó trong các dấu ngoặc tròn để tránh sai sót. Nên thêm dấu ngoặc cho từng tham số. Chẳng hạn định nghĩa biểu thức ở trên sửa lại như sau:
    #define ADD(x, y) (x + y)  //Thêm dấu ngoặc tròn
Ví dụ 3Ví dụ về quá trình define  biểu thức
         Hỏi kết quả đoạn chương trình sau ?
#include <iostream>
using namespace std;

#define MUL(x) x << 1

void main()
{
      cout << MUL(2+3) << endl;
      getchar();
}
         → Đáp án: khi chạy chương trình này sẽ cho kết quả 51. Nguyên nhân do câu lệnh sau: 
          cout << MUL(2+3) << endl;
       -> sẽ thay thành câu lệnh:          
          cout << MUL(5) << endl;
       -> Qua quá trình preprocess sẽ thay MUL(5) thành 5<<1 nên kết quả sau quá trình preprocess là: cout << 5 << 1 << endlcho kết quả 51.

          → Tránh lỗi này ta thay câu lệnh:
#define MUL(x) x<<1
               Bằng câu lệnh:
#define MUL(x) (x<<1//Thêm dấu ngoặc 
Lưu ý chung: khi định nghĩa một macro cần chú ý:
          - Giữa tên macro (tên hàm) và dấu ngoặc mở không được đặt các khoảng trống.
#define MUL(x) (x<<1//Hợp lệ
#define MUL (x) (x<<1//Không hợp lệ vì có khoản trống giữa tên macro và dấu ngoặc mở 

Ví dụ 4: Ví dụ về quá trình preprocess include lặp 2 lần như đoạn code sau:

//Main.cpp
#include <iostream>
using namespace std;

#include "Todo1.h"
#include "Todo2.h"

void main()
{
      getchar();
}

//Todo1.h
#include "Todo2.h"


//Todo2.cpp
#include "Todo1.h"

          → Chương trình trên lỗi fatal error C1014: too many include files. → Do include lặp.

          → Hướng giải quyết:
               Cách 1: Thêm #pragma one vào tệp tin Todo1.h và Todo2.h.


               Cách 2: Hoặc thêm đoạn code sau vào mỗi tệp tin .h để tránh include nhiều lần.
#ifndef FILENAME_H_
#define FILENAME_H_
#include "Todo1.h"
  //Code tập tin .h

#endif
Ví dụ 5: Ví dụ về quá trình biên dịch (compile)
#include <iostream>
using namespace std;

#define ADD(x, y) x + y  // (1)

int ADD(int x, int y)    // (2)
{
      return x + y;
}

void main()
{
      cout << ADD(2, 3) << endl;
      getchar();
}
    Hỏi lỗi chỗ nào ?
          → Đáp án: chương trình sẽ thay biến ADD ở hàm int ADD(int x, int y) thành x + y nên ở quá trình tiền xử lý cho kết quả như sau --> Dẫn đến biên dịch lỗi?
int x + y(int x, int y)

Ví dụ 6: Ví dụ về quá trình biên dịch (compile) cho thấy C++ thứ tự biên dịch từ trên xuống.
#include <iostream>
using namespace std;

void main()
{
      cout << Add(2, 3) << endl;
}

int Add(int x, int y)
{
      return x + y;

}
→ Lỗi: error C3861: 'Add': identifier not found
→ Cách giải quyết:
      - Khai báo prototype trên hàm main()
      - Hoặc copy hàm Add() lên trên hàm main 
      - Hoặc định nghĩa hàm Add() sang một tệp tin .cpp và khai báo prototype vào tệp tin .h. Sau đó include tệp tin này main.cpp.
#include <iostream>
using namespace std;

int Add(int x, int y);//khi báo prototype

void main()
{
      cout <<Add(2, 3) << endl;
}

int Add(int x, int y)
{
      return x + y;
}
Ví dụ 7: Ví dụ về quá trình linker. Chương trình gồm 2 tệp tin Functions.cppMain.cpp. Hỏi đoạn code bê dưới lỗi vì sao ?       
//Functions.cpp
int Add(int x, int y)
{
      return x + y;

}
//Main.cpp
#include <iostream>
using namespace std;

#include "Functions.cpp"//sẽ copy nguyên tệp tin Functions.cpp vào Main.cpp
void main()
{
      cout << Add(2, 3) << endl;
      getchar();
}

→ Lỗi: error LNK2005: "int __cdecl Add(int,int)" (?Add@@YAHHH@Z) already defined in main.obj → hàm int Add(int x, int y) được định nghĩa 2 lần trong tệp tin Functions.cpp và Main.cpp nên ở quá trình linker không biết gọi hàm int Add(int x, int y ) ở tệp tin nào.

→ Cách giải quyết: Xóa bớt một hàm Add(int x, int y) thêm tệp tin Main.cpp bằng cách
thêm tệp tin “Functions.h” như đoạn code bên dưới:

//Main.cpp
#include <iostream>
using namespace std;

#include "Functions.h"
void main()
{
      cout << Add(2, 3) << endl;
      getchar();
}

//Functions.h
int Add(int x, int y);


//Functions.cpp
int Add(int x, int y)
{
      return x + y;
} 

Ví dụ 8: Ví dụ về quá trình linker
    Trong ví dụ sau, quá trình compile không lỗi, nhưng quá trình linker bị lỗi.
#include <iostream>
using namespace std;

int Add(int a, int b);

void main()
{
      cout << Add(5, 6) << endl;
      getchar();

}
          → Nguyên nhân thiếu định nghĩa hàm add(), ta thêm vào.
int Add(int a, int b)
{
      return a + b;

}

→ Có khai báo prototype biên dịch ok, nhưng quá trình lỗi linker.
Ví dụ 9Ví dụ về quá trình linker 2 project với nhau. Một dự án config Lib và dự án còn lại vào phần thuộc tính link dự án Lib vào. Xem ví dụ ở phần Hướng dẫn tạo và sử dụng thư viện Lib và DLL.

Bài tập về phần tiền xử lý:
Bài 1: Giải thích khác nhau của biểu thức theo sau 2 câu lênh:  #if và #ifded. Cho ví dụ ?
#include <iostream>

#define DEBUG

void main()
{
#if DEBUG
      printf("Debug 1\n");
#endif

#ifdef DEBUG
      printf("Debug 2\n");
#endif
      system("pause");
}

Bài 2: khi biên dịch chương trình sau đây thì bị lỗi dòng nào ? Giải thích ?

#include <iostream>

#define DEBUG

void main()
{
#if DEBUG
      printf("Debug 1\n");
#endif

#ifdef DEBUG
      printf("Debug 2\n");
#endif
      system("pause");
}

Bài 3:...


----------Hết-------------