Trình biên dịch cl (MSVC)
cl.exe
là trình biên dịch C/C++ chính thức của Microsoft, đi kèm với Visual Studio (hoặc Build Tools for Visual Studio). Trình biên dịch này tuân theo hệ sinh thái MSVC và sử dụng cú pháp Windows-style (với dấu /
) thay vì UNIX-style (-
như gcc/clang).
Nó tích hợp chặt chẽ với linker link.exe
, các trình tối ưu như /O2
, /GL
, và hệ thống debug PDB.
Cú pháp tổng quát
cl [file.cpp] [cờ tiền xử lý] [cờ biên dịch] [cờ cảnh báo] [cờ tối ưu] [cờ include] [/link [cờ linker]]
- Các cờ compiler nhóm theo chức năng: preprocess, biên dịch, cảnh báo, tối ưu, include, ...
/link
nên đặt cuối lệnh, vì mọi thông số sau đó sẽ được chuyển sang linkerlink.exe
Các nhóm cờ và giải thích
Dưới đây sẽ liệt kê các cờ cơ bản và phổ biến đại diện cho mỗi nhóm cờ trong cú pháp tổng quát.
- Cờ tiền xử lý
/D<macro>
: Định nghĩa macro/U<macro>
: Xóa macro/FI<file>
: Yêu cầu trình biên dịch tự động include một file header (.h
/.hpp
) vào đầu mỗi file nguồn trong quá trình tiền xử lý/E
: Chỉ tiền xử lý, in ra stdout/showIncludes
: In danh sách file header
- Cờ biên dịch / cấu hình
/c
: Chỉ biên dịch, không link/Fo:<file.obj>
: Tên file đối tượng đầu ra/Fe:<file.exe>
: Tên file thực thi đầu ra/LD
: Tạo DLL (tương đương/link /DLL
)/MD
,/MT
: Liên kết runtime C++ dạng DLL hoặc static/EHsc
: Bật exception C++/GR-
: Tắt RTTI/m32
//m64
: Chỉ định kiến trúc 32-bit hoặc 64-bit khi biên dịch/Zc:__cplusplus
: Đảm bảo macro__cplusplus
đúng chuẩn
- Cờ cảnh báo
/W3
,/W4
,/Wall
: Mức độ cảnh báo/WX
: Chuyển cảnh báo thành lỗi/permissive-
: Tuân thủ nghiêm C++ standard/std:c++20
: Chọn chuẩn C++
- Cờ tối ưu hóa
/Od
: Tắt tối ưu (debug)/O1
,/O2
,/Ox
: Tối ưu hiệu năng/GL
: Bật Link-Time Code Generation (LTO)/Gw
: Tối ưu global/GS-
: Tắt stack check (không khuyến nghị)
- Cờ include
/I<dir>
: Thêm thư mục include/external:I<dir>
: External header, ý nghĩa tương tự cờ-isystem <dir>
củag++
, cảnh báo trong các file header ở đây sẽ bị bỏ qua./external:W0
: Tắt cảnh báo external
- Cờ linker (sau /link)
/link /OUT:<file.exe>
: Tên file exe/IMPLIB:<file.lib>
: Tạo import library khi build DLL/DLL
: Xác nhận build DLL/LIBPATH:<dir>
: Thư mục chứa.lib
/SUBSYSTEM:<arg>
: Chỉ định loại chương trình:console
,windows
,native
,... — ảnh hưởng đến entry point và hiển thị CMD.mylib.lib
,user32.lib
,...: Các file lib liên kết
Lựa chọn cờ đầu ra: /Fe:
hay /link /OUT:
?
- Nên ưu tiên dùng
/Fo:<file.obj>
và/Fe:<file.exe>
để đặt tên file đầu ra ngay trong trình biên dịchcl
- Tránh dùng
/link /OUT:
trừ khi bạn cần điều khiển linker rất chi tiết (export symbol, PDB, DEF file...) - Các cờ như
/Fe:
,/Fo:
tích hợp tốt với các cờ debug hoặc chia nhỏ build theo file - Sử dụng
/Fe:
rõ ràng hơn, dễ dùng hơn và là best practice khi biên dịch theo phong cách MSVC
Trình liên kết của cl
Trình liên kết mặc định trong hệ sinh thái MSVC là link.exe
, được tích hợp sẵn khi sử dụng trình biên dịch cl.exe
. Đây là linker của Microsoft, hoạt động tối ưu cho định dạng PE/COFF trên Windows.
- Tương thích chặt chẽ với cl.exe: Mọi lệnh biên dịch không có
/c
sẽ tự động gọilink.exe
để thực hiện liên kết. - Hỗ trợ sinh nhiều loại file đầu ra:
- File thực thi
.exe
- Thư viện động
.dll
+ import library.lib
- Thư viện tĩnh
.lib
- File debug
.pdb
kèm symbol
- File thực thi
- Tối ưu cho hệ sinh thái Windows:
- Tích hợp tốt với Visual Studio và MSBuild
- Hỗ trợ chuẩn gọi hàm Windows (
__stdcall
,__cdecl
,__fastcall
) - Hỗ trợ file
.def
để xuất symbol cụ thể
- Hiệu suất ổn định và tối ưu hóa runtime:
- Tương thích với Link-Time Code Generation (
/GL
,/LTCG
) - Có thể sinh file
.map
để phân tích bố cục bộ nhớ chương trình - Tối ưu hóa kích thước và tốc độ khi kết hợp với
/O2
,/OPT:REF
,/OPT:ICF
- Tương thích với Link-Time Code Generation (
- Không tương thích với linker GNU hoặc LLVM (như
ld
,lld
):link.exe
sử dụng cú pháp và flag riêng biệt (Windows-style)- Không dùng được với file
.o
,.a
kiểu ELF
- Mọi tùy chọn dành cho
link.exe
(như/OUT:
,/DLL
,/LIBPATH:
,/DEF:
,...) phải được đặt sau/link
khi gọi từ dòng lệnhcl.exe
. - Khi dùng với
clang-cl
,link.exe
vẫn là linker mặc định nếu không cấu hìnhlld-link
hoặc linker khác.
Ví dụ
Biên dịch file thực thi .exe
:
cl main.cpp utils.cpp /std:c++20 /Fe:build\myapp.exe /W4 /O2 /Iinclude /link /LIBPATH:lib mylib.lib
Giải thích:
- File nguồn:
main.cpp
,utils.cpp
- Cờ biên dịch:
/std:c++20
,/Fe:build\myapp.exe
(Chọn chuẩn C++ và chỉ định file đầu ra) - Cờ cảnh báo:
/W4
(Cấu hình mức cảnh báo) - Cờ tối ưu:
/O2
(Tối ưu hóa tốc độ) - Cờ include:
/Iinclude
(Thêm thư mục chứa header) - Từ khóa bắt buộc để chuyển phần còn lại cho linker:
/link
- Cờ linker:
/LIBPATH:lib mylib.lib
(Chỉ định thư mục và thư viện để liên kết)
Biên dịch và sử dụng thư viện tĩnh (.lib
)
1. Biên dịch mã nguồn thành file đối tượng (.obj
)
- Mục đích: Biên dịch các file
.cpp
thành file đối tượng.obj
để chuẩn bị đóng gói thành thư viện.lib
- Cú pháp tổng quát:
cl /c <file.cpp> /Fo:<file.obj> [cờ biên dịch khác]
- Ví dụ áp dụng:
cl /c mylib.cpp /Fo:build\mylib.obj /std:c++20 /W4 /EHsc
2. Đóng gói file đối tượng thành thư viện tĩnh (.lib
)
- Mục đích: Tạo thư viện tĩnh
.lib
từ một hoặc nhiều file.obj
- Cú pháp tổng quát:
lib <file.obj> [/OUT:<file.lib>]
- Ví dụ áp dụng:
lib build\mylib.obj /OUT:lib\mylib.lib
- Kiểm tra thư viện sau khi đóng gói:
Sau khi tạo
.dll
và.lib
, bạn có thể dùng các công cụ dòng lệnh của MSVC để kiểm tra thông tin trong các file thư viện.-
Liệt kê symbol trong
.lib
hoặc.dll
dumpbin /EXPORTS lib\mylib.dll
- Hiển thị danh sách các hàm được export.
- Dùng để xác nhận các hàm đã được export đúng (hữu ích nếu bạn không dùng
.def
hoặc gặp lỗi unresolved symbol).
dumpbin /SYMBOLS lib\mylib.lib
- Hiển thị symbol trong import library
.lib
, xác định các tên hàm mà linker sẽ dùng khi bạn biên dịch chương trình chính.
-
Hiển thị thông tin toàn bộ file
dumpbin /ALL lib\mylib.dll > dllinfo.txt
- Ghi toàn bộ thông tin chi tiết về cấu trúc file .dll vào file dllinfo.txt, bao gồm:
- Header PE
- Import table, export table
- Base relocation, debug info (nếu có)
- Ghi toàn bộ thông tin chi tiết về cấu trúc file .dll vào file dllinfo.txt, bao gồm:
-
Liệt kê các file
.obj
trong thư viện.lib
lib /LIST lib\mylib.lib
- Tương tự
llvm-ar -t
, dùng để xác nhận thư viện.lib
chứa những file.obj
nào.
- Tương tự
-
Trích xuất .obj từ
.lib
(nếu cần)lib /EXTRACT:mylib.obj lib\mylib.lib
- Trích xuất một file
.obj
từ thư viện.lib
. Có thể dùng để kiểm tra hoặc debug riêng file.obj
.
- Trích xuất một file
lưu ý- Các công cụ này như
dumpbin.exe
vàlib.exe
đều đi kèm với Developer Command Prompt for Visual Studio. - Nếu bạn không tìm thấy lệnh
dumpbin
, hãy mở terminal đúng môi trường hoặc thêm đường dẫn đếnVC\Tools\...
vàoPATH
.
-
3. Liên kết thư viện .lib
với chương trình chính
- Mục đích: Biên dịch chương trình chính (
.cpp
) và liên kết với thư viện tĩnh.lib
để tạo file thực thi - Cú pháp tổng quát:
cl <main.cpp> [cờ biên dịch khác] /I<dir_include> /Fe:<output.exe> /link /LIBPATH:<dir_lib> <libname.lib>
- Ví dụ áp dụng:
cl main.cpp /std:c++20 /W4 /O2 /Iinclude /Fe:bin\myapp.exe /link /LIBPATH:lib mylib.lib
main.cpp
→[file.cpp]
/std:c++20
,/Fe:...
→[cờ biên dịch]
/W4
→[cờ cảnh báo]
/O2
→[cờ tối ưu]
/Iinclude
→[cờ include]
/link
→ tách nhóm linker/LIBPATH:lib
,mylib.lib
→[cờ linker]
Biên dịch và sử dụng thư viện động (.dll
)
1. Biên dịch mã nguồn thành file đối tượng (.obj
)
-
Mục đích: Biên dịch file nguồn (
.cpp
) thành file đối tượng (.obj
) để sử dụng trong quá trình tạo DLL. -
Cú pháp tổng quát:
cl /c <file.cpp> /Fo:<file.obj> [cờ biên dịch khác]
-
Ví dụ áp dụng:
cl /c src\mylib.cpp /Fo:build\mylib.obj /std:c++20 /W4 /EHsc /DBUILD_MYLIB
/c
: Chỉ biên dịch, không liên kết (tạo.obj
)src\mylib.cpp
: File nguồn cần biên dịch/Fo:build\mylib.obj
: Chỉ định tên file đối tượng đầu ra/std:c++20
: Biên dịch theo tiêu chuẩn C++20/W4
: Bật mức cảnh báo cao (Level 4)/EHsc
: Bật xử lý ngoại lệ C++ (try-catch), không hỗ trợ SEH/DBUILD_MYLIB
: Định nghĩa macroBUILD_MYLIB
(tương đương#define BUILD_MYLIB
)
-
Header file cần thiết:
- Sử dụng
__declspec(dllexport)
khi build DLL, và__declspec(dllimport)
khi sử dụng DLL trong chương trình khác. - Dùng macro để gom cả 2 tình huống:
#ifdef BUILD_MYLIB
#define MYLIB_API __declspec(dllexport)
#else
#define MYLIB_API __declspec(dllimport)
#endif
extern "C" MYLIB_API int add(int a, int b);
- Sử dụng
Calling convention là gì?
-
Calling convention là gì?
Calling convention (quy ước gọi hàm) là quy tắc quy định:- Thứ tự truyền tham số
- Ai chịu trách nhiệm thu dọn stack (caller hay callee)
- Cách đặt tên hàm (name mangling) (nếu có)
-
Các loại phổ biến trong Windows (x86)
Tên gọi Từ khóa Truyền tham số Thu dọn stack Dùng phổ biến ở đâu Cdecl __cdecl
Từ phải sang trái Caller Mặc định trong C/C++ Stdcall __stdcall
Từ phải sang trái Callee Windows API, AutoIt, VB6 Fastcall __fastcall
Một số dùng thanh ghi Callee Hiệu năng cao (ít dùng) Thiscall __thiscall
this
trongecx
, còn lại trên stackCallee Mặc định với hàm thành viên C++ a.
__cdecl
- Caller (người gọi) phải clean stack sau khi gọi hàm.
- Cho phép overload số lượng tham số (
printf
,scanf
) - Thường dùng mặc định trong C/C++ compile
__cdecl int add(int a, int b);
b.
__stdcall
- Callee (hàm được gọi) sẽ tự động dọn stack.
- Thường dùng cho Windows API, DLL để gọi từ ngôn ngữ khác
- Gọi từ AutoIt, VB6, Pascal, C# (P/Invoke) → nên dùng
__stdcall
__stdcall int add(int a, int b); // gọn hơn cho người gọi
c.
__fastcall
- Truyền 2 tham số đầu tiên qua thanh ghi (
ecx
,edx
) → nhanh hơn. - Stack dọn bởi callee.
- Thường không cần thiết trừ khi tối ưu hóa chuyên sâu.
__fastcall int add(int a, int b);
d.
__thiscall
(mặc định cho C++ member function)this
nằm trong thanh ghiecx
- Các tham số còn lại trên stack
- Không dùng được trong
extern "C"
(vì C không cóthis
)
Nếu không khai báo rõ __stdcall
, thì hàm export từ DLL sẽ dùng calling convention mặc định, thường là:
__cdecl
trên Windows x86- Trên Windows x64: calling convention mặc định là Microsoft x64 ABI, không cần khai báo (vì tất cả dùng chung)
TÌNH HUỐNG:
- Nếu đang dùng Windows 64-bit:
- Không có
__stdcall
hay__cdecl
phân biệt nữa - Tất cả hàm gọi giữa module đều theo x64 calling convention (được thống nhất)
- Chương trình khác (64-bit) vẫn gọi được hàm dù không khai báo
__stdcall
- Không có
- Nhưng nếu viết DLL 32-bit và chương trình khác (32-bit) sử dụng DLL này:
- Thì phải chỉ định rõ
__stdcall
; Nếu không, sẽ xảy ra:- Lỗi stack khi trả về từ hàm
- Treo chương trình hoặc kết quả sai
- Thì phải chỉ định rõ
2. Tạo thư viện động (.dll
) và import library (.lib
)
- Mục đích: Tạo
.dll
từ file.obj
, đồng thời sinh file import.lib
để dùng cho liên kết tĩnh (implicit linking). - Cú pháp tổng quát:
cl /LD <file.obj> /Fe:<file.dll> /link /IMPLIB:<file.lib> [tùy chọn linker khác]
- Ví dụ áp dụng:
cl /LD build\mylib.obj /Fe:lib\mylib.dll /link /IMPLIB:lib\mylib.lib
/LD
: Yêu cầu tạo thư viện động.dll
(tự động bật/DLL
khi chuyển cho linker)build\mylib.obj
: File đối tượng được liên kết thành.dll
/Fe:lib\mylib.dll
: Đặt tên file đầu ra làmylib.dll
trong thư mụclib\
/link
: Bắt đầu nhóm cờ dành cho linker (link.exe
)/IMPLIB:lib\mylib.lib
: Tạo file import library.lib
để hỗ trợ liên kết tĩnh (implicit linking)
3. Sử dụng thư viện động (.dll
)
-
Liên kết tĩnh (Implicit Linking)
- Cú pháp tổng quát:
cl <main.cpp> [cờ biên dịch khác] /I<dir_include> /Fe:<output.exe> /link /LIBPATH:<dir_lib> <import.lib>
- Yêu cầu:
- File
.lib
là import library (do/IMPLIB:
sinh ra) - File
.h
phải dùng__declspec(dllimport)
khi include - File
.dll
phải tồn tại khi chạy chương trình
- File
- Lưu ý quan trọng:
mylib.dll
phải nằm cùng thư mục với.exe
, hoặc phải khai báo trong%PATH%
- Có thể dùng API như
SetDllDirectory()
để bổ sung thư mục DLL tại runtime
- Cú pháp tổng quát:
-
Tải động (Explicit Linking)
- Không cần
mylib.lib
haymylib.h
, nhưng cần:- Biết tên hàm và prototype
- Dùng
LoadLibrary
,GetProcAddress
- Cú pháp biên dịch:
cl main.cpp /Fe:myapp.exe
- Mã ví dụ:
#include <windows.h>
#include <iostream>
typedef int (*AddFunc)(int, int);
int main() {
HMODULE h = LoadLibrary("lib\\mylib.dll");
if (!h) return 1;
AddFunc add = (AddFunc)GetProcAddress(h, "add");
if (!add) return 2;
std::cout << "2 + 3 = " << add(2, 3) << "\n";
FreeLibrary(h);
}
- Không cần