Lab 02: CGO Interop

Time: 60 minutes | Level: Architect | Docker: golang:1.22-alpine

Overview

CGO deep dive: calling C functions from Go, exporting Go functions to C, passing structs across the boundary, memory management (C.malloc/C.free/C.CString/C.GoString), performance overhead analysis, and when to avoid CGO entirely.


Step 1: Basic CGO — C from Go

package main

// CGO preamble: C code embedded in Go source
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// Simple C function
int add(int a, int b) {
    return a + b;
}

// String manipulation
char* greet(const char* name) {
    char* result = malloc(256);
    snprintf(result, 256, "Hello from C, %s!", name);
    return result;
}

// Struct definition
typedef struct {
    double x;
    double y;
} Point;

double distance(Point a, Point b) {
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return sqrt(dx*dx + dy*dy);
}
*/
import "C"  // Must be immediately after preamble, no blank line
import (
	"fmt"
	"unsafe"
)

func main() {
	// Call C function
	result := C.add(C.int(3), C.int(4))
	fmt.Printf("C.add(3, 4) = %d\n", int(result))

	// String: Go → C → Go (with memory management)
	goName := "Alice"
	cName := C.CString(goName)    // Allocates C string (must free!)
	defer C.free(unsafe.Pointer(cName))

	cGreeting := C.greet(cName)   // Returns C string (must free!)
	defer C.free(unsafe.Pointer(cGreeting))

	goGreeting := C.GoString(cGreeting)
	fmt.Printf("C.greet() = %q\n", goGreeting)

	// Struct passing
	a := C.Point{x: C.double(0), y: C.double(0)}
	b := C.Point{x: C.double(3), y: C.double(4)}
	dist := C.distance(a, b)
	fmt.Printf("C.distance((0,0), (3,4)) = %.2f\n", float64(dist))
}

Step 2: Export Go Functions to C


Step 3: Memory Management Rules


Step 4: CGO Performance Overhead


Step 5: CGO with Real Libraries


Step 6: Runtime.Pinner — Safe Pointer Pinning (Go 1.21+)


Step 7: CGO Build Configuration


Step 8: Capstone — CGO Demonstration

(CGO requires a C compiler — demonstrated conceptually; CGO_ENABLED=0 builds run anywhere)

📸 Verified Output:


Summary

Operation
API
Memory Owner

Go string → C

C.CString(s)

C (must C.free)

C string → Go

C.GoString(p)

Go (GC managed)

Go bytes → C

C.CBytes(b)

C (must C.free)

C bytes → Go

C.GoBytes(p, n)

Go (GC managed)

Struct passing

Copy by value

Both

Pin Go pointer

runtime.Pinner

Go (pinned duration)

CGO overhead

~50-100ns/call

N/A

Cross-compile

CGO_ENABLED=0

N/A

Last updated