If you are using C code, you are using C's memory resource policy. So
the best approach is to use explicit calls to free. I agree that that
takes you back to the painful world of C's manual memory management,
but, after all, if you are using C then you can't pretend that you are
not using C.
That said, although Go is careful not to promise that it will actually
execute finalizers, in practice it does run them. Padding the Go
object won't make any difference as to when a finalizer is run. It
will be run in the next full GC cycle after the Go value is no longer
referenced. If there are times when the program knows that there is a
lot of C memory that is no longer required, it can help by calling
runtime.GC itself.
In practice I think the best approach is a hybrid: free the C memory
explicitly, but also add a finalizer. In the finalizer, free the C
memory, and log the fact that you are doing so, along with any
information that will help identify where the C memory was allocated.
Then periodically check your logs for cases where the finalizer ran,
and add the necessary calls to explicitly free the C memory manually.
Of course, when you free the C memory manually, don't forget to clear
the finalizer.
Ian