Reputation: 787
Consider the following snippet in Go that uses cgo to wrap a C API:
/*
#include <myheader.h>
void MyCall(my_type* a, my_type* b);
*/
import "C"
// Go wrapper for native pointer to my_type.
// The struct can be assumed to have a finalizer set.
type MyType struct {
handle *C.my_type
}
func (a *MyType) MyCall(b *MyType) {
C.MyCall(a.handle, b.handle)
}
My question is, in function MyCall
is runtime.KeepAlive
required for variables a
and b
?
According to documentation, Go pointers passed as function arguments to C functions have the memory they point to implicitly pinned for the duration of the call. Does this extend to the parent structures of Go pointers passed as function arguments to C functions?
Upvotes: 0
Views: 79
Reputation: 55553
My reasoning is as follows: the citation you're considering
Go pointers passed as function arguments to C functions have the memory they point to implicitly pinned for the duration of the call.
is a red herring: it describes the memory, pointers passed to a C call refer to, not the memory these pointers occupy by themselves.
So, the citation says it's guaranteed that if the blocks of memory pointed at by a.handle
and b.handle
were allocated by the Go runtime,
these blocks are pinned and won't be GC'ed.
If they were allocated by other means —
such as calls to C.malloc
, they will not be pinned¹.
Now that we've moved that out of the way, let's consider your issue:
Does this extend to the parent structures of Go pointers passed as function arguments to C functions?
Note that when a call C.MyCall(a.handle, b.handle)
is made,
the function C.MyCall
receives two uintptr
-sized values taken from a.handle
and b.handle
, which have no connection to the variables a
and b
except in that they contain the same bit patterns as the handle
fields in these variables, respectively (that is because in Go, everything is passed by value, so values of these pointers — that is, the addresses they contain, — are copied to the call frame of the target function (or passed to it in CPU registers; these are all things peculiar to the platform's ABI, and are not really important)).
I hereby propose that the GC is free to collect a
and b
as "it sees" neither of these values is referenced neither by the call to C.MyCall
itself nor by the code past it (as there is no such code).
Let me again stress that even though a
and b
syntactically appear at the call site of C.MyCall
, these variables are not referenced by the call as the "dot plus field name" expressions will be evaluated before the call is made, and will result in two pointer values.
Basically, you can reason like the GC:
C.MyCall
.a
and b
except the call frame which is the parent of C.MyCall
.a
and b
, it won't do anything bad to the memory referenced by the handle
fields of those values: if a.handle
and b.handle
were the last references to these blocks, the blocks themselves will be eligible for collecting, otherwise they will survive.All-in-all, this is just my reasoning (that is, an opinion, however educated it may be). I still suggest posting to the mailing list as suggested by my comment).
¹ …which is not really important as the GC won't touch memory not allocated by the runtime's memory-manager.
Upvotes: 0
Reputation: 4610
The simple answer is “No”, because a
and b
are not reachable from C code.
The function call f(a.handle, b.handle)
, be it C or Go, passes copies of the values in a.handle
and b.handle
(which are pointers to C.my_type
structures), but other than that the garbage collector is free to release a
and b
when they are not references elsewhere.
Upvotes: 0