CGO: Cruzando el Límite Go-C
Go compila a un binario autocontenido por defecto, pero cgo permite que un paquete llame a código C y se enlace con bibliotecas nativas.
Busca en todas las páginas de la documentación
Go compila a un binario autocontenido por defecto, pero cgo permite que un paquete llame a código C y se enlace con bibliotecas nativas.
Ese puente es potente y costoso: cada cruce cambia cómo el runtime programa el trabajo, cómo compilas y cómo razonas sobre la memoria.
import "C" compila Go junto con C a través de un shim generado, de modo que las funciones Go puedan llamar a C y C pueda llamar a Go exportado..so.syscall/x/sys, sidecars RPC, rutas de WebAssembly y reglas de seguridad FFI.Un programa Go normal es compilado enteramente por gc (el compilador de Go) y enlazado en un solo binario.
cgo inserta un segundo frente del compilador: cmd/cgo lee archivos Go que contienen import "C" y emite código pegamento C.
El compilador de Go luego compila tu paquete junto con ese pegamento, y el enlazador C de la plataforma incluye libc y cualquier biblioteca que nombres en #cgo LDFLAGS.
El modelo mental es un sándwich de tres capas:
Código Go <--> Shim generado por cgo <--> Biblioteca C / libc
El pseudo-paquete C en el código fuente de Go no es un paquete Go real.
Tipos como C.int y funciones como C.sqrt son declaraciones que cgo mapea a símbolos C.
Los comentarios inmediatamente encima de import "C" son directivas cgo: líneas #include, #cgo CFLAGS, #cgo LDFLAGS y #define que configuran el lado C.
Sin import "C", un archivo es Go puro incluso si vive junto a archivos cgo.
Las etiquetas de compilación como //go:build cgo (y el legado // +build cgo) te permiten enviar alternativas puras de Go cuando CGO_ENABLED=0, lo cual es común en CI, contenedores estáticos y compilaciones cruzadas.
Cada llamada cgo de Go a C se ejecuta en un hilo de SO dedicado.
Mientras está dentro de C, ese hilo no está ejecutando código Go, por lo que el planificador puede generar otro hilo para que otros goroutines sigan progresando.
Por lo tanto, un tráfico cgo intenso aumenta el recuento de hilos de SO y el costo de cambio de contexto.
Es por eso que un bucle ajustado que llama a C puede limitar el rendimiento por debajo de lo que Go puro logra en el mismo hardware.
Cruzar el límite también activa comprobaciones de punteros.
El recolector de basura de Go no debe mover memoria que C todavía referencia.
Las reglas documentadas en cmd/cgo restringen pasar punteros de Go a C de maneras que podrían sobrevivir a la llamada o esconder punteros dentro de valores Go que no son punteros.
Las violaciones causan pánico en tiempo de ejecución en compilaciones verificadas.
Compilar con cgo habilitado requiere una cadena de herramientas C funcional (gcc o clang en Linux, herramientas de línea de comandos de Xcode en macOS, MinGW en Windows).
CGO_ENABLED=0 deshabilita cgo por completo; go build falla en paquetes que importan C a menos que existan archivos alternativos.
El enlace estático, musl vs glibc, y la compilación cruzada a GOOS=linux GOARCH=arm64 desde macOS se convierten en tareas de ingeniería de lanzamiento en lugar de una sola línea de go build.
/*
#include <stdio.h>
*/
import "C"
func greet(name string) {
cs := C.CString(name)
defer C.free(unsafe.Pointer(cs))
C.printf(C.CString("hello %s\n"), cs)
}El fragmento muestra el patrón recurrente: convertir strings de Go a C (C.CString), liberar asignaciones de C (C.free) y mantener las llamadas a C cortas para que las vidas de los punteros sean obvias.
Los equipos suelen aislar cgo detrás de un pequeño paquete interno con una API pura de Go.
Los llamadores dependen de tipos y errores de Go; solo el envoltorio importa C.
Ese límite hace posible probar la lógica sin C en la mayoría de los paquetes y cambiar implementaciones más tarde.
| Enfoque | Fortaleza | Debilidad | Mejor Ajuste |
|---|---|---|---|
| cgo in-process | Latencia de llamada más baja, espacio de direcciones compartido | Complejidad de hilos + compilación | La ruta crítica debe permanecer in-process |
| RPC a sidecar C/C++ | Aislamiento independiente de lanzamiento y caída | Sobrecarga de red/IPC | Código heredado que no puedes enlazar de forma segura |
Puerto puro de Go o x/sys | Compilaciones simples, compilación cruzada fácil | Costo inicial de reescritura o cobertura incompleta | Llamadas al sistema y algoritmos con equivalentes en Go |
| Reescribir en Go | API idiomáticas, una cadena de herramientas | Costo de tiempo y validación | Bibliotecas de alcance manejable |
Las revisiones de seguridad y cadena de suministro tratan el código cgo como código nativo: los desbordamientos de búfer en C se convierten en un problema de tu proceso.
Fuzzing y sanitizadores (ASan/UBSan) en el lado C pertenecen al mismo nivel de calidad que las pruebas de Go.
La observabilidad se divide entre runtimes: los perfiles de CPU de Go muestran el tiempo de cgo bajo runtime.cgocall, pero los puntos calientes de C necesitan pprof en la biblioteca C o perfiladores externos.
Para contenedores, documenta si la imagen necesita libc, libstdc++ o archivos .so del proveedor y si envías variantes CGO_ENABLED=0 para imágenes scratch.
C.CString asigna en el heap de C; debes liberarlo con C.free (o ceder la propiedad a C con un contrato documentado).CGO_ENABLED=0 fallan a menos que elijas controladores alternativos.Cualquier archivo Go con import "C" y el bloque de comentarios cgo encima de él.
La herramienta go invoca cmd/cgo automáticamente durante go build y go test.
Sí, solo los archivos que importan C participan en cgo.
Mantén pocos archivos con import "C" y empuja la lógica a hermanos Go puros.
El planificador de Go no puede preempir código C arbitrario de forma segura.
cgo ejecuta C en un hilo que el runtime gestiona para que los goroutines y las señales sigan comportándose de manera predecible.
Una variable de entorno que por defecto es 1 cuando se detecta una cadena de herramientas C.
Establece CGO_ENABLED=0 para forzar compilaciones Go puras para binarios estáticos y CI más simple.
No, muchas llamadas al sistema están envueltas en Go puro (golang.org/x/sys) o en la biblioteca estándar.
Recurre a cgo cuando no exista un envoltorio Go seguro o un proveedor solo envíe un SDK C.
Los archivos //go:build cgo solo compilan cuando cgo está activado; los archivos !cgo proporcionan stubs.
Este patrón potencia módulos portátiles como controladores de sqlite y bibliotecas de terminal.
Sí, con funciones //export y un uso cuidadoso de runtime.LockOSThread cuando C mantiene estado local del hilo.
Las devoluciones de llamada son más difíciles que las llamadas unidireccionales; consulta la guía de exportación en esta sección.
Sí, las rutas #cgo LDFLAGS: -L${SRCDIR}/lib son comunes para archivos .a vendidos.
Aún eres responsable de las licencias y los artefactos específicos de la plataforma en vendor/ o internal/.
Necesitas compilaciones arm64 de cada .so y un compilador cruzado coincidente si compilas cruzado.
Los artefactos Go puros no tienen ese acoplamiento.
Cuando existe una biblioteca pura de Go mantenida, cuando el aislamiento RPC es aceptable, o cuando necesitas binarios estáticos diminutos sin libc.
Mide la latencia antes de comprometerte con cgo in-process.
Los perfiles de CPU etiquetan runtime.cgocall.
También observa el recuento de hilos y la latencia de cola bajo carga; ambos aumentan cuando las secciones C se ejecutan durante mucho tiempo.
Para el navegador y algunos hosts de borde, sí; Go puede apuntar a wasm con GOOS=js o wasip1.
La integración nativa del sistema operativo todavía necesita cgo o envoltorios de llamadas al sistema puros de Go.
#cgoVersiones de Stack: Esta página fue escrita para Go 1.26.x (predeterminado Green Tea GC, modernizadores go fix - verificar parche en la compilación), chi (última versión - verificar en la compilación), gin (última versión - verificar en la compilación), echo (última versión - verificar en la compilación), google.golang.org/grpc (última versión - verificar en la compilación), sigs.k8s.io/controller-runtime (última versión - verificar en la compilación), kubebuilder (última versión - verificar en la compilación), tinygo (última versión - verificar objetivos de placa en la compilación), wazero (última versión - verificar en la compilación) y golangci-lint (última versión - verificar conjunto de linters en la compilación).
Revisado por Chris St. John·Última actualización: 18 jul 2026