Trình biên dịch clang-cl
clang-cl
là frontend C/C++ của LLVM được thiết kế để tương thích hoàn toàn với trình biên dịch MSVC (cl.exe
). Nó cho phép bạn biên dịch các dự án Windows sử dụng cú pháp MSVC nhưng vẫn tận dụng được các công nghệ tối ưu và công cụ kiểm tra hiện đại từ hệ sinh thái LLVM như clangd
, clang-tidy
, lld-link
, llvm-lib
,...
Không giống như clang++
(dùng cú pháp kiểu UNIX/GNU), clang-cl
sử dụng cú pháp kiểu MSVC, ví dụ: /c
, /Fo
, /link
, /I
, /D
,...
clang-cl
có thể biên dịch trực tiếp các project dùng .vcxproj
, hoặc dùng độc lập trên dòng lệnh.
Cú pháp tổng quát
clang-cl [file nguồn] [cờ tiền xử lý] [cờ cấu hình biên dịch] [cờ cảnh báo] [cờ tối ưu hóa] [cờ include] [cờ đầu ra] [/link [cờ liên kết]]
[file nguồn]
đặt ở đầu để dễ đọc[cờ liên kết]
(sau/link
) nên luôn được đặt cuối cùng, vì mọi thứ sau/link
sẽ được chuyển nguyên văn cho linker (lld-link
hoặclink.exe
). Việc đặt sai vị trí có thể khiến một số cờ bị bỏ qua hoặc bị hiểu sai.- Nhóm cờ được nhóm theo chức năng, giúp dễ debug và mở rộng
- Có thể thay đổi thứ tự, nhưng khuyến nghị theo nhóm rõ ràng
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 (giống#define
). Ví dụ:/DDEBUG
/U<macro>
: Xóa macro đã định nghĩa/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ạy tiền xử lý, in kết quả ra stdout/showIncludes
: Hiển thị danh sách các file header đã include
- Cờ cấu hình biên dịch
/c
: Chỉ biên dịch, không liên kết/LD
: Tạo thư viện động.dll
/MD
,/MT
: Liên kết thư viện runtime động (/MD
) hoặc tĩnh (/MT
)/EHsc
: Bật xử lý exception kiểu 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
phản ánh đúng chuẩn
- Cờ cảnh báo
/W3
,/W4
,/Wall
: Mức độ cảnh báo (tăng dần)/WX
: Cảnh báo được xem là lỗi/permissive-
: Tuân thủ chặt chẽ chuẩn C++ (tắt các extension của MSVC)/std:c++20
: Chỉ định chuẩn C++/experimental:module
: Bật hỗ trợ C++20 module (hỗ trợ C++20 module vẫn đang trong giai đoạn thử nghiệm và có thể không ổn định trên tất cả các phiên bảnclang-cl
)
- Cờ tối ưu hóa
/Od
: Không tối ưu hóa/O1
,/O2
,/Ox
: Tối ưu mã ở các mức khác nhau/GL
: Bật Link-Time Optimization (LTO)/Gw
: Tối ưu hóa lưu trữ dữ liệu tĩnh (global)/GS-
: Tắt stack security check (không khuyến nghị trong production)
- Cờ include
/I<dir>
: Thêm thư mục tìm kiếm header/external:I<dir>
: Include header kiểu “external” (dùng cho phân tích static)/external:W0
: Tắt cảnh báo từ external header
- Cờ đầu ra
/Fe:<file.exe>
: Đặt tên file thực thi đầu ra (nếu không dùng/link /OUT:
)/Fo:<file.obj>
: Đặt tên file đối tượng.obj
đầu ra/LD
: Tạo thư viện động.dll
/LDd
: Tạo.dll
debug (kèm theo/MDd
,... trong runtime)/link /OUT:<file.exe>
: Tùy chọn thay thế/Fe
, truyền trực tiếp cho linker/Zi
: Sinh thông tin debug (PDB) để dùng với trình gỡ lỗi như VS hoặc WinDbg/Z7
: Ghi thông tin debug vào.obj
thay vì file.pdb
/MP
: Biên dịch nhiều file song song (multi-processing build), hiệu quả của cờ này phụ thuộc vào số lượng core CPU và kích thước dự án./showIncludes
: In ra các file đã include (giúp tạo file dependency)
- Cờ liên kết (dùng sau
/link
)/OUT:<file.exe>
: Tên file đầu ra/IMPLIB:<file.lib>
: Tạo import library khi build.dll
/DLL
: Tạo.dll
(giống/LD
)/LIBPATH:<dir>
: Chỉ định 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.kernel32.lib
,user32.lib
: Liên kết thủ công thư viện hệ thống
clang-cl
không dùng các cờ GNU-style như-std=c++20
,-Wall
,-g
— mà thay bằng/std:c++20
,/W4
,/Zi
- Dù là LLVM backend, nhưng toàn bộ interface phía trước tương tự MSVC (
cl.exe
) - Có thể kết hợp với
lld-link
hoặclink.exe
- Thư viện chuẩn mặc định là MSVC STL, không phải
libstdc++
hoặclibc++
- Không hỗ trợ
-stdlib=libc++
, muốn dùnglibc++
phải chuyển sangclang++
hoặc dùng cross-compilation clang-cl
hỗ trợ hầu hết các cờ của cl và hỗ trợ một số cờ của Clang, nhưng phải dùng cú pháp-
(không đổi thành/
). Để kiểm tra cờ Clang nào được hỗ trợ, bạn có thể chạyclang-cl -help
hoặc thử nghiệm từng cờ cụ thể.- Một số cờ bắt buộc có ":" (
/Zc:__cplusplus
,/experimental:module
,...), một số cờ bắt buộc không có ":" (/FI<file>
,/Tc
,/Tp
,...) và một số cờ chấp nhận cách viết có hoặc không có ":" (/Fe:<file.exe>
,/Fo:<file.obj>
,...). Với nhóm cờ hỗ trợ cả hai cách viết có hoặc không có ":", khuyến khích nên chọn cú pháp dạng có ":" để tường minh và nhất quán. Ví dụ các cờ có cú pháp sau đều được chấp nhận:/Fe:file_out.exe
,/Fefile_out.exe
,/LIBPATH:lib
,/LIBPATHlib
. - Luôn kiểm tra tài liệu Microsoft (Compiler Options) hoặc
clang-cl /?
để xác nhận cú pháp đúng trước khi sử dụng. clang-cl
cũng chấp nhận kiểu viết/D<macro>
và/D <macro>
, nhưng cách dùng không có khoảng trắng/D<macro>
được khuyến khích.
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ịchclang-cl
- 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 clang++
- Mặc định
clang-cl
sử dụnglld-link.exe
, một linker của LLVM tương thích vớilink.exe
của MSVC. - Có thể thay bằng link.exe từ MSVC nếu cần.
lld-link
hỗ trợ LTO (/GL
+/LTCG
) và có tốc độ nhanh hơnlink.exe
trong nhiều dự án lớn.- Các cờ liên kết được đặt sau
/link
, ví dụ:clang-cl main.cpp /Fe:myapp.exe /link /LIBPATH:lib mylib.lib
- Import library (
.lib
) nên được đặt sau/link
, cùng với các thư viện hệ thống nếu cần (ví dụ:kernel32.lib
,user32.lib
)
Ví dụ
Biên dịch file thực thi .exe
:
clang-cl src\main.cpp src\lib.cpp /std:c++20 /W4 /O2 /Iinclude /Fe:build\myapp.exe /link /LIBPATH:lib mylib.lib
Giải thích:
- File nguồn:
src\main.cpp
,src\lib.cpp
- Cờ cấu hình:
/std:c++20
- Cờ cảnh báo:
/W4
- Cờ tối ưu:
/O2
- Cờ include:
/Iinclude
- Cờ đầu ra:
/Fe:build\myapp.exe
- Cờ liên kết:
/link /LIBPATH:lib mylib.lib
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 mã nguồn (
.cpp
) thành file đối tượng (.obj
) mà không liên kết, để chuẩn bị đóng gói thành thư viện.lib
. -
Cú pháp tổng quát:
clang-cl /c <file.cpp> /Fo:<file.obj> [cờ biên dịch khác]
-
Ví dụ áp dụng cơ bản:
clang-cl /c mylib.cpp /Fo:mylib.obj
/c
: Chỉ biên dịch, không liên kết./Fo
: Đặt tên file đối tượng đầu ra.obj
.
lưu ý- Có thể truyền nhiều file
.cpp
cùng lúc nếu muốn tạo nhiều.obj
. - Đảm bảo file
.cpp
include đúng.h
tương ứng để kiểm tra prototype.
2. Đóng gói file đối tượng thành thư viện tĩnh (.lib
)
-
Mục đích: Gộp một hoặc nhiều file
.obj
thành một thư viện.lib
để tái sử dụng và liên kết với các chương trình khác mà không cần biên dịch lại. -
Cú pháp tổng quát:
llvm-lib /out:<libname.lib> <file1.obj> [file2.obj ...]
-
Ví dụ áp dụng cơ bản:
llvm-lib /out:mylib.lib mylib.obj
llvm-lib
: Công cụ đóng gói thư viện trong hệ LLVM (tương tựlib.exe
của MSVC)./out:
: Đặt tên file thư viện đầu ra.
lưu ý- Nên sử dụng
llvm-lib
thay vìlib.exe
để đồng bộ vớiclang-cl
. - Có thể đóng gói nhiều
.obj
cùng lúc nếu thư viện có nhiều thành phần. - Không cần đặt tiền tố
lib
như với.a
, nhưng nên đặt tên rõ ràng, ví dụ:mylib.lib
.
-
Kiểm tra thư viện sau khi đóng gói:
Sau khi tạo thư viện.lib
từ.obj
, bạn có thể kiểm tra nội dung bên trong để đảm bảo các symbol (hàm, biến) đã được đóng gói chính xác.- Dùng
llvm-nm
:llvm-nm mylib.lib
- Liệt kê các symbol trong thư viện.
- Dấu
T
(text/code),D
(data) cho biết symbol được export.
- Dùng
dumpbin
của Visual Studio (nếu có):dumpbin /symbols mylib.lib
- Cung cấp thông tin rất chi tiết về tất cả symbol (có hoặc không export).
- Chạy trong Developer Command Prompt của MSVC.
- Không dùng
llvm-ar
hoặcar
:.lib
không tuân theo định dạngar
của UNIX, nên không nên dùngllvm-ar
với.lib
.
thông tinllvm-nm
hoạt động tốt với.lib
dạng COFF, nếu được tạo bởiclang-cl
hoặcllvm-lib
.- Nếu
llvm-nm
không hiển thị gì, hãy thửdumpbin
để kiểm tra sâu hơn. - Kiểm tra symbol đặc biệt hữu ích nếu bạn viết thư viện cho bên thứ ba hoặc cần xác minh symbol có đúng kiểu gọi hàm (
__cdecl
,__stdcall
) và có bị name mangling hay không.
- Dùng
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ớimylib.lib
để tạo file thực thi. -
Cú pháp tổng quát:
clang-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 cơ bản:
clang-cl main.cpp /Iinclude /Fe:myapp.exe /link /LIBPATH:lib mylib.lib
main.cpp
: File mã nguồn chính, gọi hàm từmylib.h
./Iinclude
: Chỉ định thư mục chứa file header./Fe
: Tên file thực thi đầu ra./link /LIBPATH:lib mylib.lib
: Chỉ định thư viện liên kết.
lưu ý- Không dùng
-l<name>
như trongclang++
; vớiclang-cl
, chỉ cần ghi trực tiếp tên file.lib
. mylib.lib
phải được tạo bằngllvm-lib
hoặclib.exe
để đảm bảo tương thích.- Cờ
/LIBPATH:
bắt buộc nếu.lib
không nằm trong thư mục hiện tại.
Biên dịch và sử dụng thư viện động (.dll
)
Thư viện động .dll
(Dynamic-Link Library) trong môi trường Windows cho phép nhiều chương trình chia sẻ chung mã thực thi, giúp giảm dung lượng file .exe
và hỗ trợ nạp thư viện động tại runtime. Có hai phương pháp sử dụng:
- Liên kết tĩnh (implicit linking): Gọi hàm từ
.dll
thông qua file import library.lib
. - Tải động (explicit linking): Nạp
.dll
thủ công trong runtime bằngLoadLibrary
/GetProcAddress
.
Cả hai cách đều chỉ yêu cầu phân phối .exe
và .dll
. File .lib
chỉ cần trong giai đoạn biên dịch (implicit linking).
1. Biên dịch mã nguồn thành file đối tượng (.obj
)
- Mục đích: Tạo file
.obj
từ mã nguồn.cpp
, để sau đó build.dll
. - Cú pháp tổng quát:
clang-cl /c <file.cpp> /Fo:<file.obj> [cờ biên dịch khác]
- Ví dụ áp dụng cơ bản:
clang-cl /c src\mylib.cpp /Fo:build\mylib.obj /DBUILD_MYLIB /Iinclude
/DBUILD_MYLIB
: Macro để export symbol./Iinclude
: Thư mục chứamylib.h
.
- File header:
- Dùng
__declspec(dllexport)
và__declspec(dllimport)
thông qua macro để khai báo symbol dùng chung:#pragma once
#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);
} - Lưu ý:
extern "C"
giúp tránh name mangling, cần thiết nếu.dll
sẽ được gọi từ ngôn ngữ khác (C, Python, AutoIt,...).- Không nên sử dụng
extern "C"
với các thành phần đặc trưng của C++ nhưstd::string
,std::vector
, hàm overload, hàm template, class hoặc namespace. Những thành phần này không tương thích với ngữ cảnhextern "C"
vì không có định dạng ABI (Application Binary Interface) theo chuẩn C, có thể dẫn đến lỗi biên dịch hoặc lỗi liên kết (linker error).
- 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: Liên kết file
.obj
để tạomylib.dll
và filemylib.lib
để dùng khi implicit linking. - Cú pháp tổng quát:
clang-cl /LD <file.obj> [/I<dir>] [/D<macro>] /Fe:<file.dll> [cờ biên dịch khác] /link /IMPLIB:<file.lib> [/DEF:<file.def>] [/LIBPATH:<dir>] [lib1.lib ...] [/DEBUG] [cờ linker khác]
- Ví dụ áp dụng cơ bản:
clang-cl /LD build\mylib.obj /Fe:bin\mylib.dll /link /IMPLIB:lib\mylib.lib
/LD
: Biên dịch và liên kết thành.dll
./Fe
: Tên file.dll
đầu ra./IMPLIB:
: Tạo file.lib
chứa thông tin liên kết (import library).
- Cấu trúc tổ chức dự án mẫu:
project/
├── include/mylib.h
├── src/mylib.cpp
├── build/mylib.obj
├── lib/mylib.lib
├── bin/mylib.dll - Lưu ý:
- Có thể thêm
/DEF:mylib.def
nếu bạn muốn kiểm soát chính xác symbol export. Nội dungmylib.def
ví dụ:LIBRARY "mylib"
EXPORTS
addLIBRARY
là tên DLL (không cần đuôi.dll
)EXPORTS
liệt kê các hàm public
- Nếu chỉ dùng để tải động
.dll
, có thể bỏ/IMPLIB
, ví dụ:clang-cl /LD build\mylib.obj /Fe:bin\mylib.dll
- Có thể thêm
3. Sử dụng thư viện động .dll
- Liên kết tĩnh (Implicit linking)
- Mục đích: Gọi hàm như thông thường qua
.lib
, nhưng thật ra lời gọi được định tuyến tới.dll
. - Cú pháp tổng quát:
clang-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 cơ bản:
clang-cl main.cpp /Iinclude /Fe:myapp.exe /link /LIBPATH:lib mylib.lib
main.cpp
: File nguồn, gọi các hàm được khai báo trongmylib.h
./Iinclude
: Khai báo thư mục chứa filemylib.h
./Fe:myapp.exe
: File thực thi đầu ra./link /LIBPATH:lib mylib.lib
: Chỉ định thư mục chứa file.lib
và tên của file.lib
(mylib.lib
) để liên kết.
- Lưu ý:
- Cần
mylib.h
với__declspec(dllimport)
(macro MYLIB_API) để khai báo symbol. mylib.dll
phải có mặt khi chạy chương trình:- Đặt cùng thư mục với
.exe
, hoặc - Thêm thư mục chứa
.dll
vàoPATH
, hoặc - Dùng API như
SetDllDirectory()
để thiết lập.
- Đặt cùng thư mục với
- Cần
- Mục đích: Gọi hàm như thông thường qua
- Tải động (Explicit linking)
- Mục đích: Nạp
.dll
tại runtime bằng API LoadLibrary, không cần.lib
, linh hoạt hơn. - Ví dụ:
#include <windows.h>
#include <iostream>
typedef int (*AddFunc)(int, int);
int main() {
HMODULE hDLL = LoadLibraryA("mylib.dll");
if (!hDLL) {
std::cerr << "Không thể nạp mylib.dll\n";
return 1;
}
AddFunc add = (AddFunc)GetProcAddress(hDLL, "add");
if (!add) {
std::cerr << "Không tìm thấy hàm add\n";
return 1;
}
std::cout << "2 + 3 = " << add(2, 3) << "\n";
FreeLibrary(hDLL);
return 0;
} - Biên dịch chương trình:
clang-cl src\main.cpp /Fe:bin\myapp.exe
- Lưu ý:
- Không cần
mylib.lib
hoặcmylib.h
, nhưng cần:- Tên hàm chính xác (
add
) - Prototype (kiểu đối số, trả về)
- Biết chuẩn gọi hàm (
__cdecl
mặc định)
- Tên hàm chính xác (
- Có thể dùng
llvm-nm
hoặcdumpbin /exports
để xem các hàm export trong.dll
.
- Không cần
- Mục đích: Nạp