def get_slice
a = StaticArray[1]
a.to_slice
end
b = get_slice
p! b # dangling pointer
p! b[0] # 0
def get_slice2
a = IO::Memory.new 10
a << "hello"
a.to_slice
end
b = get_slice2
GC.collect # gc does not work ?
p! b
p! String.new b # "hello"
I wouldn’t go so far as to say “Slice is unsafe” here. Slice
on its own isn’t inherently unsafe. You can pass any pointer you like for the buffer, so if you pass a pointer to stack-allocated memory (which is what StaticArray#to_slice
is doing), that is what is making it unsafe. Maybe there should be a warning in the StaticArray#to_slice
method documentation that warns you to only use that slice within (or downstream of) the method it’s created in.
If you need to return it from the method, use it in a captured block, or any place where you can’t guarantee it’ll be used while the current stack frame is still in scope, don’t use StaticArray#to_slice
— use a heap-allocated one instead. Those are safe because Crystal can make guarantees about heap memory that it can’t about stack memory.
It’s working, it’s just that you’re holding a reference to whatever you’re expecting to have been collected. It traverses pointer graphs, so if you’re expecting something to be collected and it isn’t, there may still be a transitive reference to it. But you can see that it does release the memory if you run it in a loop. If you want to be able to see when an object is collected, check out WeakRef
.
i update it to weak_ref, but still gc not work
require "weak_ref"
def get_slice2
a = WeakRef.new(IO::Memory.new)
a.value.not_nil! << "hello"
a.value.not_nil!.to_slice
end
b = get_slice2
GC.collect
p! b
p! String.new b # "hello"
maybe the memory not be overrided?
The GC does not necessarily collect the IO::Memory
and its buffer at the same time. It can if the IO::Memory
is the only object in your entire program that contains a reference to its buffer, but in your code there are 2 references to it — one in the IO::Memory
and one in the Slice
.
It works kinda like this:
class IO::Memory
def to_slice
Slice(UInt8).new(@buffer, bytesize)
end
end
struct Slice(T)
def initialize(@buffer : Pointer(T), @size : Int32)
end
end
Notice that to_slice
passes the buffer pointer to the Slice
. This is what’s happening in your code, and that’s what’s preventing the GC from collecting the buffer. It’s not a bug in the GC. The GC is doing exactly what it should in this scenario.
I misunderstood something myself here, as well. WeakRef
won’t actually help you visualize this because Pointer
s are allocated on the stack. That means a WeakRef
wrapping a Pointer
will never have a nil value
because the pointer instance is stored entirely within the WeakRef
, not on the heap. So I led you down the wrong path there.
in your code there are 2 references to it — one in the
IO::Memory
and one in theSlice
.
Pointer will inc reference count ?
pointer assigned by GC.malloc_atomic will increase reference count when copy ?
Crystal’s GC doesn’t use reference counting. It’s more like persistence by reachability, so any reference to GC-allocated memory as it traverses the heap will prevent the object at that memory location from being collected.
So yes, passing Pointer
s around will prevent GC.collect
from collecting them. Otherwise, you’ll run into use-after-free issues.
All pointers allocated by Crystal’s GC, so pointers returned from both malloc
and malloc_atomic
will be managed by the GC and any pointers to those buffers will prevent them from being GCed.
The difference is whether the GC looks inside those buffers for more pointers. The GC will look inside malloc
pointers for other GC-allocated pointers. It will ignore the contents of anything allocated with malloc_atomic
, but will still manage the buffer itself.
It’s like whether you iterate over an array or deal with the array as an opaque container.
Thank you, I finally understand.