In cgo, what does C.CString return, and who is responsible for freeing it?
answer
- two heaps, one collector
- malloc is on the other side of the wall
- the copy is deliberate, not incidental
- allocate on one line, defer on the next
- stdlib.h must be included for the release call
basics
~10 sC.CString copies a Go string into memory allocated by C's malloc and returns a *C.char. Go's garbage collector never manages that memory, so the caller must release it with C.free(unsafe.Pointer(p)).
solid answer
~50 s`C.CString(s)` allocates a buffer in the **C heap** with `malloc`, copies the bytes of the Go string into it, appends a terminating NUL, and returns a `*C.char`. Nothing about that buffer is known to Go's garbage collector — it is ordinary C memory — so the caller owns it and must call `C.free(unsafe.Pointer(p))`, which requires `#include <stdlib.h>` in the preamble. The usual shape is to allocate and immediately `defer` the free on the next line, so the two can never drift apart during later edits. The mirror direction, `C.GoString(p)`, also **copies**: it reads up to the NUL and produces a fresh Go string, so the C buffer may be freed straight afterwards and the Go string stays valid. Neither helper shares memory with the other side, which is exactly why both are safe and why both cost an allocation and a copy.
code
go · 10 linesfunc encode(in string) string {
cin := C.CString(in)
defer C.free(unsafe.Pointer(cin))
// the library documents that it returns malloc'd memory
cout := C.codec_encode(cin)
defer C.free(unsafe.Pointer(cout))
return C.GoString(cout)
}go deeper
Remember the pairing: every C.CString you create needs a matching C.free(unsafe.Pointer(...)). Being able to write those two lines together, in order, is what is being checked at this level.
Explain why the copy exists at all — Go strings are collector-managed, movable and not NUL-terminated — and describe the C heap as a region the collector cannot see. Know that C.GoString copies back rather than aliasing.
Demonstrate the ownership conversation: for every pointer crossing the boundary, say who allocated it and who frees it, and show how you encode that in the wrapper so callers never see a C pointer. The defer-in-a-loop trap should be something you catch in review.
Own the API shape that makes leaks structurally unlikely: a single package whose exported surface is pure Go, with all allocation and release paired inside it. Be ready to argue for that boundary against pressure to expose C handles for performance.
## Two different heaps A cgo program has two memory regions that behave nothing alike. The **Go heap** is managed by the garbage collector: you allocate by writing a composite literal or calling `make`, and you never free. The **C heap** is managed by `malloc` and `free`: you allocate explicitly and you free explicitly, and the collector has no idea it exists. `C.CString` is the bridge that allocates in the second one. ## What C.CString does ```go cs := C.CString("encode me") ``` Three things happen: `malloc` reserves `len(s)+1` bytes in the C heap, the bytes of the Go string are copied into it, and a terminating NUL byte is written so C's string functions know where the data ends. The result is a `*C.char` pointing at C memory. The copy is not an implementation detail you could optimise away. A Go string's bytes live in Go-managed memory that the collector may move or reclaim, and it is not NUL-terminated — Go strings carry an explicit length. Handing a C function a pointer into Go memory would violate both facts at once, so the copy is the design. ## Ownership Because the buffer came from `malloc`, **you own it**. The garbage collector will never reclaim it, no finalizer runs, and the memory stays allocated until the process exits or you free it: ```go cs := C.CString(s) defer C.free(unsafe.Pointer(cs)) ``` Two mechanical details bite people here. First, `C.free` only exists if the preamble includes `<stdlib.h>`; without it, cgo reports an undefined name. Second, `C.free` takes `unsafe.Pointer`, so the `*C.char` must be converted — that conversion is the one place many wrappers touch the `unsafe` package at all. Writing the `defer` on the line **immediately after** the allocation is a discipline, not a style preference. It survives later edits that add early returns between the two lines, which is precisely the situation where a hand-written free at the bottom of the function gets skipped. ## The trap with defer in a loop `defer` schedules the call for **function return**, not for the end of the iteration: ```go for _, name := range names { cs := C.CString(name) // wrong: nothing is freed until the loop's function returns defer C.free(unsafe.Pointer(cs)) C.process(cs) } ``` Over a million names that holds a million C buffers alive at once. The fixes are to free explicitly at the end of the iteration body, or to move the body into its own small function so the deferred free fires per call. ## The return trip: C.GoString ```go s := C.GoString(cp) ``` `C.GoString` reads from the pointer until it finds a NUL byte and copies those bytes into a newly allocated Go string. It does **not** alias the C buffer: after the copy the Go string is independent, so freeing the C pointer on the next line is correct and safe. That is what makes the wrapper pattern clean — copy in, call, copy out, free. When the C side hands you a length rather than a NUL terminator, `C.GoStringN(cp, C.int(n))` takes the count explicitly. ## Who frees what the C library returns The rule above covers the pointers *you* created. Pointers the C library *returns* are governed by that library's own contract, and there are only two possibilities, which you must read the documentation to distinguish: - The function returned `malloc`'d memory and expects you to free it. Copy it out with `C.GoString`, then `C.free(unsafe.Pointer(p))`. - The function returned a pointer into a static or internal buffer it still owns. Freeing it corrupts the C heap, and the buffer may be overwritten by the next call — so copy it out immediately with `C.GoString` and free nothing. Guessing wrong in the first direction leaks; guessing wrong in the second crashes, often far away from the mistake. When the documentation is silent, treat the pointer as borrowed and copy at once. ## Interior NUL bytes Go strings may contain a NUL byte in the middle; C strings cannot. `C.CString` copies all the bytes faithfully, but any C function using `strlen` sees only the prefix up to the first interior NUL. If the payload is arbitrary binary data rather than text, a length-carrying API is the right choice, not a `char*`. ## What an interviewer is listening for They want the ownership sentence stated plainly: *C.CString allocates with malloc and the Go collector never frees it*. Then they want the `defer C.free(unsafe.Pointer(...))` reflex, awareness that `C.GoString` copies, and honesty about reading the C library's ownership contract for returned pointers. A candidate who says "the garbage collector cleans it up" has the single most consequential fact backwards.
- Does C.GoString keep a reference to the C memory you pass it?No. It reads up to the terminating NUL and copies those bytes into a newly allocated Go string, so the result is independent of the C buffer and freeing that buffer immediately afterwards is correct. When the data is not NUL-terminated, `C.GoStringN` takes an explicit length instead.
- Why is defer C.free(unsafe.Pointer(cs)) inside a loop still a problem?Deferred calls run when the enclosing function returns, not at the end of each iteration, so a loop over a million items holds a million C buffers alive simultaneously. Either free explicitly at the bottom of the loop body, or extract the body into its own function so each call's deferred free fires promptly.
- A C function returns a char*. How do you decide whether to free it?Read the library's ownership contract — it is the only source of truth. If the function malloc'd the buffer, copy it out with `C.GoString` and then `C.free` it; if it returned a pointer into static or internal storage, freeing corrupts the heap, so copy immediately and free nothing. When the docs are silent, treat it as borrowed.
- Which header must the preamble include for C.free to resolve?`<stdlib.h>`, which declares both `malloc` and `free`. Without it cgo reports `C.free` as an undefined name even though `C.CString` compiles fine, because `C.CString` is generated by cgo itself while `C.free` is a genuine C library function that must be declared.
C.CString is like photocopying a page and handing the copy to someone in another building. They now hold paper nobody on your side tracks, and no cleaner of yours will ever throw it away.
saying these in an interview costs you the question
- Says the garbage collector reclaims C.CString memory
- Frees a pointer the C library still owns
- Passes a Go string's bytes straight to C instead of copying
- Puts defer C.free inside a hot loop
- Assumes C.GoString aliases the C buffer
- Forgets stdlib.h and blames cgo for undefined C.free