When CGO Meets Zig: A More Elegant Way to Tinker, Compared with the GCC Backend
“GCC won the benchmarks, but Zig won at life.” (GCC wins the benchmark, but Zig wins the DX/Developer Experience.) Switching the cgo compiler from gcc to zig on Windows? GCC is fast but environment setup can be a hassle; Zig is a lightweight single file but compiles slowly and first-run latency is ridiculous. Is the performance gap large? Maybe, but the gain in developer efficiency is worth the tinkering.
目录12 个章节
Introduction
Recently, while building a Go project involving a database (SQLite) and audio processing in a Windows environment, I ran into an expected error:
cgo: C compiler "gcc" not found: exec: "gcc": executable file not found in %PATH%
That's because Go's cgo mechanism needs to call an external C compiler to process C code. Windows does not include a GCC environment by default, so we need to manually configure a C toolchain.
Quickly Installing GCC with Scoop
For developers who are used to command-line tools, I don't recommend downloading an installer and configuring environment variables by hand. With Scoop, you can solve this very quickly:
scoop install gcc
After installation, run gcc --version in the terminal. If you see version output, that means Scoop has already configured the environment variables automatically.
At this point, if you run go build, the project can usually compile normally.
Using Zig Instead of GCC
Although GCC can solve the problem, after further research I found that using Zig as the C compiler is a more efficient choice. Although Zig is a programming language, its compiler (zig cc) can be used as a complete C/C++ compiler frontend.
Why Choose Zig?
- Lighter installation: A single
scoop install ziggives you a single-file compiler, without having to manage a complex folder structure like MinGW. - Built-in standard library: Zig ships libc source for multiple platforms, so compilation does not depend on the host's system libraries.
- Static linking advantages: Binaries compiled with Zig are more stable when handling static linking, and can reduce the generated
.exefile's dependence on external.dlls. - Native cross-compilation support: This is Zig's strongest feature. You can compile cgo programs targeting Linux or macOS directly on Windows, just by specifying the
-targetparameter, without configuring a complex cross-compilation toolchain.
Installing Zig
Install it with Scoop the same way
scoop install zig
Configuring Go to Invoke the Zig Compiler
If you have already installed Zig, you can make Go use it by default with the following steps:
Enable CGO:
go env -w CGO_ENABLED=1
Specify the C/C++ compiler:
Point the CC and CXX Go environment variables at Zig:
go env -w CC="zig cc"
go env -w CXX="zig c++"
Compiling for a specific platform (example):
If you need to compile for the Linux platform:
$env:GOOS="linux"
$env:GOARCH="amd64"
$env:CC="zig cc -target x86_64-linux-gnu"
go build
Let's Test It
To test cgo compilation, I had Grandpa LLM help write a snippet that reverses a string. This example covers CGO's core flow: C code defining the logic, Go code calling it via CGO, and data being passed between Go and C.
Create the file main.go
package main
/*
// 使用 Zig 的编译器驱动
#cgo LDFLAGS: -s -w
#include <stdlib.h>
#include <string.h>
// 这是一个简单的 C 函数,用于反转字符串
void reverse_string(char* str) {
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
input := "Hello from Zig + CGO!"
fmt.Printf("Original: %s\n", input)
// 将 Go 字符串转换为 C 字符串(会在堆上分配内存)
cStr := C.CString(input)
// 必须手动释放 C 内存
defer C.free(unsafe.Pointer(cStr))
// 调用 C 函数
C.reverse_string(cStr)
// 将修改后的 C 字符串转回 Go 字符串
output := C.GoString(cStr)
fmt.Printf("Reversed: %s\n", output)
}
Local Compilation
First test with gcc
$env:CGO_ENABLED="1"
go build main.go
Then test with zig
$env:CGO_ENABLED="1"
$env:CC="zig cc"
go build main.go
Note
There's a small pitfall here
After version 1.9.4, Go introduced a security check mechanism that by default intercepts some CGO flags it considers “unsafe”
-s and -w are commonly used size-reduction flags, but they are intercepted by default
So you need to add an environment variable to allow them
$env:CGO_LDFLAGS_ALLOW="-s|-w"
You can also extract the C code into separate files
Create hello.h
void reverse_string(char* str);
Create hello.c
#include <string.h>
void reverse_string(char* str) {
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}
Modify main.go
package main
/*
#include "hello.h"
*/
import "C"
import (
"fmt"
"unsafe"
)
func main() {
s := C.CString("Zig is awesome")
defer C.free(unsafe.Pointer(s))
C.reverse_string(s)
fmt.Println(C.GoString(s))
}
After extracting the code into a .c file, CGO will automatically look for C files in the same directory and hand them off to CC (i.e. zig cc). You usually don't need to write LDFLAGS in main.go by hand. This not only keeps the code cleaner, but also neatly sidesteps Go's flag-checking mechanism.
Performance Comparison
Although Zig is wonderful, I had Grandpa LLM help write another script to test compile speed and the resulting binary's execution speed across different backends.
The results showed...
| Dimension | GCC (15.2) | Zig (0.15.2) | Gap / Conclusion |
|---|---|---|---|
| Compile time (Avg) | 6.67 s | 16.40 s | Zig is 1.4× slower. The LLVM architecture is heavier, and parsing the cross-platform compatibility layer takes time. |
| Runtime (Avg) | 18.54 ms | 30.83 ms | Zig is about 66% slower. Affected by cold-start latency and CGO call overhead. |
| Max first-run latency | 25.30 ms | 483.68 ms | Ridiculous! The Zig binary appears to trigger a Windows Defender scan. |
| Runtime stability (StdDev) | 1.34 ms | 45.54 ms | GCC is extremely stable; Zig fluctuates wildly (dragged down by cold-start outliers). |
| Environment dependencies | Complex (requires installing MinGW/MSYS2) | Minimal (single file, no deps) | This is where Zig's core advantage lies. |
Note: Zig's first-run latency (483ms) usually only happens the first time the system executes that binary. If you add
app_zig.exeto the Windows Defender whitelist, its median runtime will stabilize around 26ms. Although still slightly behind GCC, it is already within an acceptable range for engineering purposes.
Conclusion
Don't blindly buy into Zig's performance optimizations.
Although Zig is hailed as a challenger to modern C, in the CGO scenario on Windows, GCC is still king for local performance and compile speed.
So why bother tinkering with Zig?
Because setting up a GCC environment on Windows is an absolute nightmare for beginners. Zig's real value isn't those few milliseconds of execution speed, but that it lets you escape the complex MinGW environment and achieve the ultimate developer experience of 'one command, compile for every platform, zero-dependency deployment'. This is a classic tradeoff of a little performance cost for developer efficiency.
Wrap-up
For handling cgo compilation on Windows, Scoop + GCC is the fastest basic solution. But if you want a clean build environment, or have a need for cross-platform compilation, Scoop + Zig is a more advanced choice in terms of technical design.
This way, we can avoid heavyweight Visual Studio or a complex MinGW environment, and keep the development environment clean and efficient.
_EOF_
When CGO Meets Zig: A More Elegant Way to Tinker, Compared with the GCC Backend
- 本文作者:
- 程序设计实验室
- 版权声明:
- 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章
使用go语言开发hive导出工具
作者弃bash、Python、C#,选Go写Hive导出工具,只因部署简单。Go的err处理烦人,但交叉编译省事。工具交互式,搞定导出。
2023-10-17 · Go · 约 1 分钟
使用go语言开发自动化API测试工具
这个工具我使用了 go 语言开发,主要是考虑到了 go 语言可以傻瓜式的实现交叉编译,生成的可执行文件直接上传到服务器就可以执行,非常方便。
2024-03-08 · Go · 约 1 分钟
2025年的最后一天,分享我使用go语言开发的电子书转换工具网站
受够了满屏广告的转换工具,随手给命令行工具套了个 Go-Gin 的壳。本想图个清静,结果这群人直接把服务器跑崩了,目前只能被迫停机。代码丢在 GitHub 了,别指望我会去修那还没写完的登录功能。
2025-12-31 · Go · 约 1 分钟
花里胡哨,带小屏幕的硬盘盒,硕乐NVMe数显固态硬盘盒开箱
呵,这年头连硬盘盒都要加个毫无用处的小屏幕。实测读取速度还不如更便宜的竞品,所谓的创新就这?包装简陋螺丝刀割手,除了金属外壳手感尚可简直一无是处。
2025-11-03 · 存储 · 约 1 分钟
讨论
共 0 条加载评论中…