Thiết lập và sử dụng gccgo
Tài liệu này giải thích cách sử dụng gccgo, một trình biên dịch cho ngôn ngữ Go. Trình biên dịch gccgo là một frontend mới cho GCC, trình biên dịch GNU được sử dụng rộng rãi. Mặc dù bản thân frontend được cấp phép theo kiểu BSD, gccgo thường được sử dụng như một phần của GCC và do đó được bao phủ bởi Giấy phép Công cộng GNU (giấy phép bao gồm gccgo như một phần của GCC; nó không bao gồm mã được tạo bởi gccgo).
Lưu ý rằng gccgo không phải là trình biên dịch gc; xem
hướng dẫn Cài đặt Go cho trình biên dịch đó.
Các bản phát hành
Cách đơn giản nhất để cài đặt gccgo là cài đặt một bản phát hành nhị phân GCC được biên dịch có hỗ trợ Go. Các bản phát hành nhị phân GCC có trên nhiều trang web khác nhau và thường được đưa vào trong các bản phân phối GNU/Linux. Chúng tôi kỳ vọng rằng hầu hết những người biên dịch các tệp nhị phân này sẽ bao gồm hỗ trợ Go.
Bản phát hành GCC 4.7.1 và tất cả các bản phát hành 4.7 sau đó đều bao gồm một trình biên dịch và thư viện Go 1 hoàn chỉnh.
Do thời điểm phát hành, GCC 4.8.0 và 4.8.1 gần với nhưng không giống hệt Go 1.1. Bản phát hành GCC 4.8.2 bao gồm triển khai Go 1.1.2 hoàn chỉnh.
Các bản phát hành GCC 4.9 bao gồm triển khai Go 1.2 hoàn chỉnh.
Các bản phát hành GCC 5 bao gồm triển khai đầy đủ các thư viện người dùng Go 1.4. Thời gian chạy Go 1.4 chưa được tích hợp hoàn toàn, nhưng điều đó không ảnh hưởng đến các chương trình Go.
Các bản phát hành GCC 6 bao gồm triển khai đầy đủ các thư viện người dùng Go 1.6.1. Thời gian chạy Go 1.6 chưa được tích hợp hoàn toàn, nhưng điều đó không ảnh hưởng đến các chương trình Go.
Các bản phát hành GCC 7 bao gồm triển khai đầy đủ các thư viện người dùng Go 1.8.1. Như các bản phát hành trước, thời gian chạy Go 1.8 chưa được tích hợp hoàn toàn, nhưng điều đó không ảnh hưởng đến các chương trình Go.
Các bản phát hành GCC 8 bao gồm triển khai đầy đủ bản phát hành Go 1.10.1. Thời gian chạy Go 1.10 đã được tích hợp hoàn toàn vào các nguồn phát triển GCC, và bộ gom rác đồng thời được hỗ trợ đầy đủ.
Các bản phát hành GCC 9 bao gồm triển khai đầy đủ bản phát hành Go 1.12.2.
Các bản phát hành GCC 10 bao gồm triển khai đầy đủ bản phát hành Go 1.14.6.
Các bản phát hành GCC 11 bao gồm triển khai đầy đủ bản phát hành Go 1.16.3.
Các bản phát hành GCC 12 và 13 bao gồm triển khai đầy đủ thư viện chuẩn Go 1.18. Tuy nhiên, GCC chưa hỗ trợ generics.
Mã nguồn
Nếu bạn không thể sử dụng bản phát hành hoặc muốn tự biên dịch gccgo,
mã nguồn gccgo có thể truy cập qua Git. Trang web GCC có
hướng dẫn lấy mã nguồn GCC.
Mã nguồn gccgo đã được bao gồm. Để thuận tiện, một phiên bản ổn định
của hỗ trợ Go có sẵn trong nhánh
devel/gccgo của kho lưu trữ mã GCC chính:
git://gcc.gnu.org/git/gcc.git.
Nhánh này được cập nhật định kỳ với các nguồn trình biên dịch Go ổn định.
Lưu ý rằng mặc dù gcc.gnu.org là cách thuận tiện nhất
để lấy mã nguồn frontend Go, đó không phải là nơi lưu trữ nguồn chính.
Nếu bạn muốn đóng góp thay đổi cho frontend trình biên dịch Go,
xem Đóng góp cho
gccgo.
Biên dịch
Biên dịch gccgo giống như biên dịch GCC
với một hoặc hai tùy chọn bổ sung. Xem
hướng dẫn trên trang web gcc.
Khi bạn chạy configure, thêm
tùy chọn --enable-languages=c,c++,go (cùng với các ngôn ngữ khác
mà bạn muốn biên dịch). Nếu bạn đang nhắm đến x86 32-bit,
bạn sẽ muốn biên dịch gccgo để mặc định hỗ trợ các lệnh compare and exchange có khóa;
thực hiện bằng cách dùng thêm tùy chọn configure --with-arch=i586
(hoặc kiến trúc mới hơn, tùy thuộc vào nơi bạn cần chương trình chạy).
Nếu bạn đang nhắm đến x86 64-bit, nhưng đôi khi muốn sử dụng
tùy chọn -m32, hãy dùng tùy chọn configure
--with-arch-32=i586.
Gold
Lưu ý: Trình liên kết gold đã bị deprecated từ GNU Binutils 2.44 (tháng 2 năm 2025) và có thể bị xóa trong các bản phát hành tương lai. Hãy cân nhắc sử dụng lld (trình liên kết của LLVM) như một thay thế, cũng hỗ trợ split stacks trên x86_64.
Trên các hệ thống x86 GNU/Linux, trình biên dịch gccgo có khả năng sử dụng stack nhỏ không liên tục cho các goroutine. Điều này cho phép chương trình chạy nhiều goroutine hơn, vì mỗi goroutine có thể sử dụng một stack tương đối nhỏ. Thực hiện điều này yêu cầu sử dụng trình liên kết hỗ trợ split stacks. Trình liên kết gold phiên bản 2.22 trở lên hỗ trợ tính năng này, cũng như lld.
Nếu bạn vẫn cần dùng gold, bạn có thể cài đặt GNU binutils 2.22 trở lên (lưu ý: từ binutils 2.44, gold nằm trong một tarball binutils-with-gold riêng), hoặc bạn có thể tự biên dịch gold.
Để tự biên dịch gold, biên dịch GNU binutils,
sử dụng --enable-gold=default khi bạn chạy
script configure. Trước khi biên dịch, bạn phải cài đặt
các gói flex và bison. Một trình tự điển hình sẽ trông như thế này
(bạn có thể thay /opt/gold bằng bất kỳ thư mục nào
mà bạn có quyền ghi):
git clone git://sourceware.org/git/binutils-gdb.git mkdir binutils-objdir cd binutils-objdir ../binutils-gdb/configure --enable-gold=default --prefix=/opt/gold make make install
Dù bạn cài đặt gold theo cách nào, khi cấu hình gccgo, hãy sử dụng
tùy chọn --with-ld=GOLD_BINARY.
Tương tự, với lld dùng --with-ld=/path/to/ld.lld.
Các điều kiện tiên quyết
Một số điều kiện tiên quyết là cần thiết để biên dịch GCC, như
được mô tả trên
trang web gcc.
Quan trọng là phải cài đặt tất cả các điều kiện tiên quyết trước khi
chạy script configure của gcc.
Các thư viện điều kiện tiên quyết có thể được tải xuống thuận tiện bằng cách dùng
script contrib/download_prerequisites trong các nguồn GCC.
Các lệnh biên dịch
Sau khi tất cả các điều kiện tiên quyết được cài đặt, một trình tự biên dịch và
cài đặt điển hình sẽ trông như thế này (chỉ sử dụng
tùy chọn --with-ld nếu bạn đang dùng trình liên kết gold như
mô tả ở trên):
git clone --branch devel/gccgo git://gcc.gnu.org/git/gcc.git gccgo mkdir objdir cd objdir ../gccgo/configure --prefix=/opt/gccgo --enable-languages=c,c++,go --with-ld=/opt/gold/bin/ld make make install
Sử dụng gccgo
Trình biên dịch gccgo hoạt động như các frontend gcc khác. Từ GCC 5, bản cài đặt gccgo
cũng bao gồm một phiên bản lệnh go,
có thể được sử dụng để biên dịch các chương trình Go như mô tả tại
https://godev.go-mizu.dev/cmd/go.
Để biên dịch một tệp mà không sử dụng lệnh go:
gccgo -c file.go
Thao tác đó tạo ra file.o. Để liên kết các tệp lại để tạo thành
tệp thực thi:
gccgo -o file file.o
Để chạy tệp kết quả, bạn cần cho chương trình biết nơi tìm các gói Go đã biên dịch. Có một vài cách để làm điều này:
-
Đặt biến môi trường
LD_LIBRARY_PATH:LD_LIBRARY_PATH=${prefix}/lib/gcc/MACHINE/VERSION [or] LD_LIBRARY_PATH=${prefix}/lib64/gcc/MACHINE/VERSION export LD_LIBRARY_PATHỞ đây
${prefix}là tùy chọn--prefixđược dùng khi biên dịch gccgo. Đối với bản cài đặt nhị phân, giá trị này thường là/usr. Dùnglibhaylib64phụ thuộc vào mục tiêu. Thông thườnglib64là đúng cho các hệ thống x86_64, vàliblà đúng cho các hệ thống khác. Mục đích là đặt tên cho thư mục chứalibgo.so. -
Truyền tùy chọn
-Wl,-Rkhi bạn liên kết (thay lib bằng lib64 nếu phù hợp với hệ thống của bạn):go build -gccgoflags -Wl,-R,${prefix}/lib/gcc/MACHINE/VERSION [or] gccgo -o file file.o -Wl,-R,${prefix}/lib/gcc/MACHINE/VERSION -
Sử dụng tùy chọn
-static-libgođể liên kết tĩnh với các gói đã biên dịch. -
Sử dụng tùy chọn
-staticđể thực hiện liên kết hoàn toàn tĩnh (mặc định cho trình biên dịchgc).
Các tùy chọn
Trình biên dịch gccgo hỗ trợ tất cả các tùy chọn GCC
không phụ thuộc vào ngôn ngữ, đáng chú ý là các tùy chọn -O
và -g.
Tùy chọn -fgo-pkgpath=PKGPATH có thể được sử dụng để đặt
một tiền tố duy nhất cho gói đang được biên dịch.
Tùy chọn này được lệnh go tự động sử dụng, nhưng bạn có thể muốn
dùng nó nếu bạn gọi gccgo trực tiếp.
Tùy chọn này dành cho các chương trình lớn
chứa nhiều gói, để cho phép nhiều gói sử dụng cùng một định danh làm tên gói.
PKGPATH có thể là bất kỳ chuỗi nào; một lựa chọn tốt cho
chuỗi là đường dẫn được dùng để import gói.
Các tùy chọn -I và -L, đồng nghĩa với nhau
trong trình biên dịch, có thể được sử dụng để đặt đường dẫn tìm kiếm khi tìm
các import.
Các tùy chọn này không cần thiết nếu bạn biên dịch bằng lệnh go.
Imports
Khi bạn biên dịch một tệp xuất ra thứ gì đó, thông tin xuất sẽ được lưu trữ trực tiếp trong tệp đối tượng. Nếu bạn biên dịch bằng gccgo trực tiếp, thay vì bằng lệnh go, thì khi bạn import một gói, bạn phải cho gccgo biết cách tìm tệp đó.
Khi bạn import gói FILE bằng gccgo, nó sẽ tìm dữ liệu import trong các tệp sau, và sử dụng tệp đầu tiên tìm thấy.
FILE.goxlibFILE.solibFILE.aFILE.o
FILE.gox, khi được sử dụng, thường sẽ chỉ chứa
dữ liệu xuất. Điều này có thể được tạo từ
FILE.o thông qua
objcopy -j .go_export FILE.o FILE.gox
Trình biên dịch gccgo sẽ tìm trong thư mục hiện tại
các tệp import. Trong các tình huống phức tạp hơn, bạn
có thể truyền tùy chọn -I hoặc -L cho
gccgo. Cả hai tùy chọn đều lấy các thư mục để tìm kiếm. Tùy chọn
-L cũng được truyền cho trình liên kết.
Trình biên dịch gccgo hiện tại (2015-06-15) không ghi lại tên tệp của các gói được import trong tệp đối tượng. Bạn phải sắp xếp để dữ liệu được import liên kết vào chương trình. Điều này không cần thiết khi biên dịch bằng lệnh go.
gccgo -c mypackage.go # Xuất mypackage gccgo -c main.go # Import mypackage gccgo -o main main.o mypackage.o # Liên kết rõ ràng với mypackage.o
Gỡ lỗi
Nếu bạn dùng tùy chọn -g khi biên dịch, bạn có thể chạy
gdb trên tệp thực thi của mình. Trình gỡ lỗi chỉ có hiểu biết hạn chế
về Go. Bạn có thể đặt breakpoint, thực hiện từng bước,
v.v. Bạn có thể in biến, nhưng chúng sẽ được in như thể chúng
có kiểu C/C++. Đối với các kiểu số, điều này không quan trọng. Chuỗi Go
và interface sẽ hiển thị dưới dạng cấu trúc hai phần tử. Các map
và channel Go luôn được biểu diễn dưới dạng con trỏ C đến các cấu trúc thời gian chạy.
Khả năng tương tác với C
Khi sử dụng gccgo, có khả năng tương tác hạn chế với C,
hoặc với mã C++ được biên dịch bằng extern "C".
Các kiểu
Các kiểu cơ bản ánh xạ trực tiếp: int32 trong Go là
int32_t trong C, int64 là
int64_t, v.v.
Kiểu int trong Go là một số nguyên có cùng kích thước với
con trỏ, và do đó tương ứng với kiểu C intptr_t.
Go byte tương đương với C unsigned char.
Con trỏ trong Go là con trỏ trong C.
Một struct trong Go giống như struct trong C với
các trường và kiểu giống nhau.
Kiểu string trong Go hiện được định nghĩa là một cấu trúc hai phần tử
(điều này có thể thay đổi):
struct __go_string {
const unsigned char *__data;
intptr_t __length;
};
Bạn không thể truyền mảng giữa C và Go. Tuy nhiên, con trỏ đến
mảng trong Go tương đương với con trỏ C đến
kiểu phần tử tương ứng.
Ví dụ, Go *[10]int tương đương với C int*,
giả sử rằng con trỏ C trỏ đến 10 phần tử.
Một slice trong Go là một cấu trúc. Định nghĩa hiện tại là (điều này có thể thay đổi):
struct __go_slice {
void *__values;
intptr_t __count;
intptr_t __capacity;
};
Kiểu của một hàm Go là con trỏ đến một struct (điều này có thể thay đổi). Trường đầu tiên trong struct trỏ đến mã của hàm, tương đương với con trỏ đến hàm C có các kiểu tham số tương đương, với một tham số trailing bổ sung. Tham số trailing là closure, và đối số để truyền là con trỏ đến struct hàm Go. Khi một hàm Go trả về nhiều hơn một giá trị, hàm C trả về một struct. Ví dụ, các hàm này xấp xỉ tương đương:
func GoFunction(int) (int, float64)
struct { int i; float64 f; } CFunction(int, void*)
Các kiểu interface, channel và map
của Go không có kiểu C tương ứng (interface là
một struct hai phần tử và channel cùng map là
con trỏ đến struct trong C, nhưng các struct cố ý không được ghi lại). Kiểu
enum của C tương ứng với một kiểu nguyên nào đó, nhưng kiểu chính xác
khó đoán trong trường hợp tổng quát; hãy sử dụng ép kiểu. Kiểu union
của C không có kiểu Go tương ứng. Kiểu struct của C chứa
các trường bit không có kiểu Go tương ứng. Kiểu class của C++ không
có kiểu Go tương ứng.
Cấp phát bộ nhớ hoàn toàn khác nhau giữa C và Go, vì Go sử dụng bộ gom rác. Các hướng dẫn cụ thể trong lĩnh vực này chưa được xác định, nhưng có thể sẽ được phép truyền con trỏ đến bộ nhớ được cấp phát từ C sang Go. Trách nhiệm cuối cùng giải phóng con trỏ sẽ vẫn thuộc về phía C, và tất nhiên nếu phía C giải phóng con trỏ trong khi phía Go vẫn còn một bản sao thì chương trình sẽ lỗi. Khi truyền con trỏ từ Go sang C, hàm Go phải giữ lại một bản sao hiển thị của nó trong một biến Go nào đó. Nếu không, bộ gom rác Go có thể xóa con trỏ trong khi hàm C vẫn đang sử dụng nó.
Tên hàm
Mã Go có thể gọi các hàm C trực tiếp bằng cách sử dụng một phần mở rộng Go được triển khai
trong gccgo: khai báo hàm có thể được đặt trước bởi
//extern NAME. Ví dụ, đây là cách hàm C
open có thể được khai báo trong Go:
//extern open func c_open(name *byte, mode int, perm int) int
Hàm C đương nhiên mong đợi một chuỗi kết thúc bằng NUL, trong
Go tương đương với con trỏ đến mảng (không phải slice!) của
byte với byte zero kết thúc. Vì vậy một lệnh gọi mẫu
từ Go trông như thế này (sau khi import gói syscall):
var name = [4]byte{'f', 'o', 'o', 0};
i := c_open(&name[0], syscall.O_RDONLY, 0);
(đây chỉ là ví dụ minh họa; để mở tệp trong Go, hãy sử dụng hàm
os.Open của Go thay thế).
Lưu ý rằng nếu hàm C có thể bị block, chẳng hạn như trong lệnh gọi
read, việc gọi hàm C có thể làm block chương trình Go.
Trừ khi bạn hiểu rõ những gì mình đang làm, tất cả các lệnh gọi
giữa C và Go nên được triển khai thông qua cgo hoặc SWIG, như đối với
trình biên dịch gc.
Tên của các hàm Go được truy cập từ C có thể thay đổi. Hiện tại,
tên của một hàm Go không có receiver là
prefix.package.Functionname. Tiền tố được đặt bởi
tùy chọn -fgo-prefix được dùng khi gói được biên dịch;
nếu tùy chọn không được dùng, mặc định là go.
Để gọi hàm từ C, bạn phải đặt tên bằng cách sử dụng
phần mở rộng GCC.
extern int go_function(int) __asm__ ("myprefix.mypackage.Function");
Tự động tạo khai báo Go từ mã nguồn C
Phiên bản Go của GCC hỗ trợ tự động tạo
khai báo Go từ mã C. Tính năng này khá cồng kềnh và hầu hết
người dùng nên sử dụng chương trình cgo với
tùy chọn -gccgo thay thế.
Biên dịch mã C của bạn như bình thường, và thêm tùy chọn
-fdump-go-spec=FILENAME. Điều này sẽ tạo
tệp FILENAME như một hiệu ứng phụ của quá trình
biên dịch. Tệp này sẽ chứa khai báo Go cho các kiểu,
biến và hàm được khai báo trong mã C. Các kiểu C không thể
biểu diễn trong Go sẽ được ghi dưới dạng chú thích trong mã Go. Tệp
được tạo sẽ không có khai báo package, nhưng
có thể được biên dịch trực tiếp bởi gccgo.
Quy trình này chứa đầy những lưu ý và hạn chế chưa được nêu rõ, và chúng tôi không đảm bảo rằng nó sẽ không thay đổi trong tương lai. Nó hữu ích hơn như một điểm xuất phát cho mã Go thực sự hơn là một quy trình thông thường.