If the C library were small, we would just port it to pure Go, and if its performance were not critical for our purposes, we would be better off invoking a C program as a helper subprocess using the os/exec package. It’s when you need to use a complex, performance-critical library with a narrow C API that it may make sense to wrap it using cgo.

package main

// #include <stdio.h>
// #include <stdlib.h>
//
// void print(char *str) {
//     printf("%s\n", str);
// }
import "C"
import "unsafe"

func main() {
    s := "hello, cgo"
    cs := C.CString(s) // 在 C 空间分配内存
    defer C.free(unsafe.Pointer(cs))
    C.print(cs) // Hello, cgo
}

The import "C" declaration imports pseudo-package C. There is no package C, but this import causes go build to preprocess the file using the cgo tool before the Go compiler sees it.

  • During preprocessing, cgo generates a temporary package that contains Go declarations corresponding to all the C functions and types us ed by the file.
  • The cgo tool discovers these types by invoking the C compiler in a special way on the contents of the comment that precedes the import declaration.
    • If import "C" is immediately preceded by a comment, that comment, called the preamble, is used as a header when compiling the C parts of the package.
    • The preamble may contain any C code, including function and variable declarations and definitions. These may then be referred to from Go code as though they were defined in the package C.
      • All names declared in the preamble may be used, even if they start with a lower-case letter.
      • Exception: static variables in the preamble may not be referenced from Go code; static functions are permitted.
    • The comment may also contain #cgo directives that specify extra options to the C toolchain.
      • The CFLAGS and LDFLAGS values contribute extra arguments to the compiler and linker commands.
      • Values defined in multiple directives are concatenated together.
      • When the cgo directives are parsed, any occurrence of the string ${SRCDIR} will be replaced by the absolute path to the directory containing the source file. This allows pre-compiled static libraries to be included in the package directory and linked properly.
    • When the Go tool sees that one or more Go files use the special import "C", it will look for other non-Go files in the directory and compile them as part of the Go package. Any .c, .s, .S or .sx files will be compiled with the C compiler.
      • Any .h, .hh, .hpp, or .hxx files will not be compiled separately, but, if these header files are changed, the package (including its non-Go source files) will be recompiled.
      • Note that changes to files in other directories do not cause the package to be recompiled, so all non-Go source code for the package should be stored in the package directory, not in subdirectories.
    • The cgo tool will always invoke the C compiler with the source file’s directory in the include path; i.e. -I${SRCDIR} is always implied.
      • This means that if a header file foo/bar.h exists both in the source directory and also in the system include directory (or some other place specified by a -I flag), then #include <foo/bar.h> will always find the local version in preference to any other version.

For security reasons, only a limited set of flags are allowed, notably -D, -U, -I, and -l. To allow additional flags, set CGO_CFLAGS_ALLOW to a regular expression matching the new flags.

The cgo tool is enabled by default for native builds on systems. It is disabled by default when cross-compiling.

  • Control this by setting the CGO_ENABLED environment variable when running the go tool: set it to 1 to enable the use of cgo, and to 0 to disable it.
  • When cross-compiling, you must specify a C cross-compiler for cgo to use.

The go tool will set the build constraint "cgo" if cgo is enabled. The special import "C" implies the "cgo" build constraint, as though the file also said // +build cgo.

go build -x -v 输出带有 cgo 代码的 Go 源文件的构建细节。

Go References to C

The standard C numeric types are available under the names C.char, C.schar (signed char), C.uchar (unsigned char), C.short, C.ushort (unsigned short), C.int, C.uint (unsigned int), C.long, C.ulong (unsigned long), C.longlong (long long), C.ulonglong (unsigned long long), C.float, C.double, C.complexfloat (complex float), and C.complexdouble (complex double). The C type void* is represented by Go’s unsafe.Pointer. The C types __int128_t and __uint128_t are represented by [16]byte.

任何类型的指针值都可以转换为 unsafe.Pointer 类型,unsafe.Pointer 类型也可以转换回任意类型的指针类型。

To access a struct, union, or enum type directly, prefix it with struct_, union_, or enum_, as in C.struct_stat.

Within the Go file, C’s struct field names that are keywords in Go can be accessed by prefixing them with an underscore: if x points at a C struct with a field named type, x._type accesses the field. C struct fields that cannot be expressed in Go, such as bit fields or misaligned data, are omitted in the Go struct, replaced by appropriate padding to reach the next field or the end of the struct.

  • Go code cannot refer to zero-sized fields that occur at the end of non-empty C structs. To get the address of such a field (which is the only operation you can do with a zero-sized field) you must take the address of the struct and add the size of the struct.

The size of any C type T is available as C.sizeof_T, as in C.sizeof_struct_stat.

A C function may be declared in the Go file with a parameter type of the special name _GoString_. This function may be called with an ordinary Go string value. The string length, and a pointer to the string contents, may be accessed by calling the C functions.

// size_t _GoStringLen(_GoString_ s);
// const char *_GoStringPtr(_GoString_ s);
  • These functions are only available in the preamble, not in other C files.
  • The C code must not modify the contents of the pointer returned by _GoStringPtr.

As Go doesn’t have support for C’s union type in the general case, C’s union types are represented as a Go byte array with the same length.

Go structs cannot embed fields with C types.

cgo translates C types into equivalent unexported Go types. Because the translations are unexported, a Go package should not expose C types in its exported API: a C type used in one Go package is different from the same C type used in another.

Any C function (even void functions) may be called in a multiple assignment context to retrieve both the return value (if any) and the C errno variable as an error (use _ to skip the result value if the function returns void).

Calling C function pointers is currently not supported, however you can declare Go variables which hold C function pointers and pass them back and forth between Go and C. C code may call function pointers received from Go.

In C, a function argument written as a fixed size array actually requires a pointer to the first element of the array. C compilers are aware of this calling convention and adjust the call accordingly, but Go cannot. In Go, you must pass the pointer to the first element explicitly: C.f(&C.x[0]).

Calling variadic C functions is not supported. It is possible to circumvent this by using a C function wrapper.

Strings in C are represented by a zero-terminated ('\0') array of chars. Conversion between Go and C strings is done with the C.CString, C.GoString, and C.GoStringN functions. These conversions make a copy of the string data.

A few special functions convert between Go and C types by making copies of the data. In pseudo-Go definitions:

// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char

// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer

// C string to Go string
func C.GoString(*C.char) string

// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string

// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte

C.malloc does not call the C library malloc directly but instead calls a Go helper function that wraps the C library malloc but guarantees never to return nil.

  • If C’s malloc indicates out of memory, the helper function crashes the program, like when Go itself runs out of memory.
  • Because C.malloc cannot fail, it has no two-result form that returns errno.

Passing Pointers

Go is a garbage collected language, and the garbage collector needs to know the location of every pointer to Go memory.

Memory allocations made by C code are not known to Go’s memory manager. When you create a C string with C.CString (or any C memory allocation) you must remember to free the memory when you’re done with it by calling C.free.

Here the term Go pointer means a pointer to memory allocated by Go (such as by using the & operator or calling the predefined new function) and the term C pointer means a pointer to memory allocated by C (such as by a call to C.malloc).

Whether a pointer is a Go pointer or a C pointer is a dynamic property determined by how the memory was allocated; it has nothing to do with the type of the pointer.

Go types, other than the type’s zero value, always include Go pointers. This is true of string, slice, interface, channel, map, and function types. Array and struct types may or may not include Go pointers, depending on the element types.

A pointer type may hold a Go pointer or a C pointer.

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers. The C code must preserve this property: it must not store any Go pointers in Go memory, even temporarily.

  • When passing a (Go) pointer to a field in a (C) struct, the Go memory in question is the memory occupied by the field, not the entire struct.
  • When passing a pointer to an element in an array or slice, the Go memory in question is the entire array or the entire backing array of the slice.

C code may not keep a copy of a Go pointer after the call returns. This includes the _GoString_ type which includes a Go pointer; _GoString_ values may not be retained by C code.

Go code may not store a Go pointer in C memory. C code may store Go pointers in C memory, subject to the rule above: it must stop storing the Go pointer when the C function returns.

These rules are checked dynamically at runtime. The checking is controlled by the cgocheck setting of the GODEBUG environment variable.

  • The default setting is GODEBUG=cgocheck=1, which implements reasonably cheap dynamic checks.
  • These checks may be disabled entirely using GODEBUG=cgocheck=0.
  • Complete checking of pointer handling, at some cost in run time, is available via GODEBUG=cgocheck=2.

It is possible to defeat this enforcement by using the unsafe package, and of course there is nothing stopping the C code from doing anything it likes. However, programs that break these rules are likely to fail in unexpected and unpredictable ways.

The runtime/cgo.Handle type can be used to safely pass Go values between Go and C.

The current implementation has a bug. While Go code is permitted to write nil or a C pointer (but not a Go pointer) to C memory, the current implementation may sometimes cause a runtime error if the contents of the C memory appear to be a Go pointer. Therefore, avoid passing uninitialized C memory to Go code if the Go code is going to store pointer values in it. Zero out the memory in C before passing it to Go.
未 zero out 的 C 指针可能被认为是 Go 指针。


References