For the record, allocating the memory as []unsafe.Pointer fixes the issues. (Working example below.)
That said, I'm not sure this is a future proof idea. It seems the GC handles it correctly now, but may change in the future. I'd greatly prefer to find a spec-compliant solution... package debug import ( "testing" "runtime" "unsafe" ) type Num struct { val uint64 ptr *uint64 } func Test(t *testing.T) { var sliceDataPtr unsafe.Pointer func() { n := uint64(123456789) numMemSize := 2 b := make([]unsafe.Pointer, numMemSize, numMemSize) sliceDataPtr = unsafe.Pointer(unsafe.SliceData(b)) numVal := (*Num)(sliceDataPtr) numVal.val = 123 numVal.ptr = &n escape(sliceDataPtr) // force on heap }() runtime.GC() numVal := (*Num)(sliceDataPtr) if numVal.val != 123 { t.Fatalf("v[%X]", numVal.val) } if *numVal.ptr != 123456789 { t.Fatalf("v[%X]", *numVal.ptr) } } var sink any func escape(x any) { sink = x sink = nil } On Friday, August 18, 2023 at 5:12:18 PM UTC+2 Tibor Halter wrote: > Hi Elias, thanks for chiming in! > > > The Go garbage collector is precise in that it only considers pointers > when determining memory that can be freed. > > Agreed, but it is not as precise on _what_ is a pointer. Specifically, > which matters? > - the type used upon allocating the memory, or > - the type used upon writing to it. > > Because *T or unsafe.Pointer is required upon writing to a memory location > for it to be marked as a pointer, I came to the conclusion that the type > upon writing is what matters. > > So then both types need to be pointers? > > Does that mean there is no way to define a struct-like memory layout > dynamically, that contains a mix of pointer and non-pointer types, in a way > that is supported by the GC? > (This is what I'm trying to do.) > > On Friday, August 18, 2023 at 3:49:03 PM UTC+2 ma...@eliasnaur.com wrote: > >> On Friday, 18 August 2023 at 05:57:38 UTC-6 Tibor Halter wrote: >> >> Thanks Ian! >> >> I have managed to strip it down to its essence, see code below. >> >> *Summary of what I'm doing* >> - reserve memory with make([]byte) >> - get pointer to underlying array >> - cast it to type Num >> - write pointer #2 into it >> - force GC() >> - GC does not follow pointer #2, memory is freed >> >> >> The Go garbage collector is precise in that it only considers pointers >> when determining memory that can be freed. In your scenario and >> demonstration program you explicitly store a pointer in a byte slice, >> effectively making it invisible to the collector. >> >> >> -- >> >> >> package debug >> >> import ( >> "testing" >> "runtime" >> "unsafe" >> ) >> >> type Num struct { >> numData *uint64 >> } >> >> func Test(t *testing.T) { >> n := uint64(123456789) >> numMemSize := 8 >> b := make([]byte, numMemSize, numMemSize) >> sliceDataPtr := unsafe.Pointer(unsafe.SliceData(b)) >> (*Num)(sliceDataPtr).numData = &n >> >> >> The pointer to &n stored in b here is effectively hidden from the >> collector. >> >> >> >> runtime.GC() >> >> >> With no more visible references to (the supposedly heap-allocated) n, it >> can be freed. >> >> >> >> act := (*Num)(sliceDataPtr).numData >> if *act != 123456789 { >> t.Fatalf("v[%X]", *act) >> } >> } >> >> >> Elias >> > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/11c27ff5-0e1b-42a7-9f00-6bda4f860f90n%40googlegroups.com.