使用引用计数总是会有循环引用的问题,这样就必须单独的去处理循环引用的问题, 所以我想如果不在申请的堆内存里面增加引用计数来判断是否应该释放堆内存,而是在使用的指针上加一个标记来说明该变量在离开作用域的时候是否需要释放指针指向的内存。像下面这样:
type
Ptr*[T] = object
flag: int
p: ptr [T]
flag 是一个标志说明p指针指向的内存在变量离开作用域的时候是否应该被释放,1 代表释放,0 代表不需要操作 重写一下类型Ptr 的普通赋值操作,在赋值的时候不需要将flag 赋值为1,保证永远只有一个变量的flag为1,以免重复释放堆内存,(当更大作用域的变量向更小作用域的变量赋值的时候,就调用该方法) 加入方法movePower,用来再赋值的时候转移flag,(当更小作用域向更大作用域的变量赋值的时候调用该方法) 加入方法swapPower用来交换两个变量的指针和flag (当两个变量交换值的时候调用该方法) 这样在变量离开作用域的时候自动调用方法释放内存,用flag 标志来判断是否应该真的使用dealloc 来释放p 指针,只有flag 是1的才调用。 简单的伪代码
const OWN: int = 1
template isManagement*[T](p: Ptr[T]): bool =
(p.flag and OWN) == OWN
proc getPtr*[T](p: Ptr[T]): ptr [T] =
result = p.p
proc free*[T](p: Ptr[T]) {.inline, used.}=
if p.isManagement:
dealloc(p.getPtr)
proc `=destroy`[T](p: var Ptr[T]) =
p.free
proc initPtr*[T](): Ptr[T] {.inline.} =
result = Ptr[T](flag: OWN, p: cast[ptr T](alloc0(sizeof(T))))
proc intiNilPtr*[T](): Ptr[T] {.inline.} =
Ptr[T](flag: 0, p: nil)
proc `=`*[T](dest: var Ptr[T]; src: Ptr[T]) =
free[T](dest)
dest.flag = 0
dest.p = src.p
proc `[]`*[T](dest: Ptr[T]): var T =
result = dest.p[]
proc movePower*[T](dest: var Ptr[T]; src: var Ptr[T]) =
free[T](dest)
dest.flag = src.flag
dest.p = src.p
proc swapPower*[T](dest: var Ptr[T]; src: var Ptr[T]) =
dest.flag = src.flag xor dest.flag
src.flag = dest.flag xor src.flag
dest.flag = src.flag xor dest.flag
dest.p = src.p xor dest.p
src.p = dest.p xor src.p
dest.p = src.p xor dest.p
type
Test = object
data: int
next: Ptr[Test]
var test = initPtr[Test]()
test[].data = 1
test[].next = test
echo test.isManagement
var t = test
echo t.isManagement
test.free
t.free
现在我想请问这样会有问题吗? 这样我感觉就不会有循环引用的问题了(automatically translated):
So instead of adding a reference count to the requested heap memory to determine if the heap memory should be freed, I thought it would be useful to add a marker to the used pointer to indicate if the variable needs to be freed when it leaves scope. Something like the following:
type
Ptr*[T] = object
flag: int
p: ptr [T]
flag is a flag that indicates whether or not the existing variable pointed to by the p pointer should be freed when it leaves the scope, 1 means freed, 0 means no operation is needed. (call this method when a variable in a larger scope assigns a value to a variable in a smaller scope) Add the method movePower to transfer flags when assigning a value, (call this method when a variable in a smaller scope assigns a value to a variable in a larger scope). Add the method swapPower to swap pointers and flags of two variables (call this method when two variables swap values). The flag flag is used to determine whether the p pointer should actually be freed using dealloc, and is only called if the flag is 1. Simple pseudo-code
const OWN: int = 1
template isManagement*[T](p: Ptr[T]): bool =
(p.flag and OWN) == OWN
proc getPtr*[T](p: Ptr[T]): ptr [T] =
result = p.p
proc free*[T](p: Ptr[T]) {.inline, used.}=
if p.isManagement:
dealloc(p.getPtr)
proc `=destroy`[T](p: var Ptr[T]) =
p.free
proc initPtr*[T](): Ptr[T] {.inline.} =
result = Ptr[T](flag: OWN, p: cast[ptr T](alloc0(sizeof(T))))
proc intiNilPtr*[T](): Ptr[T] {.inline.} =
Ptr[T](flag: 0, p: nil)
proc `=`*[T](dest: var Ptr[T]; src: Ptr[T]) =
free[T](dest)
dest.flag = 0
dest.p = src.p
proc `[]`*[T](dest: Ptr[T]): var T =
result = dest.p[]
proc movePower*[T](dest: var Ptr[T]; src: var Ptr[T]) =
free[T](dest)
dest.flag = src.flag
dest.p = src.p
proc swapPower*[T](dest: var Ptr[T]; src: var Ptr[T]) =
dest.flag = src.flag xor dest.flag
src.flag = dest.flag xor src.flag
dest.flag = src.flag xor dest.flag
dest.p = src.p xor dest.p
src.p = dest.p xor src.p
dest.p = src.p xor dest.p
type
Test = object
data: int
next: Ptr[Test]
var test = initPtr[Test]()
test[].data = 1
test[].next = test
echo test.isManagement
var t = test
echo t.isManagement
test.free
t.free
Now I would like to ask if this would be a problem? This way I don't feel like there will be a circular reference problem
Seems like a worse version of UniquePtr. Worse because it uses up more storage and the way you store the flag on the stack can cause trouble.
But yes, unique pointers cannot create cycles.
for arm64 and x86_64 you can smuggle that flag in the top 16 bits.
import std/bitops
const ptrmask = 1'uint shl 63
proc `[]`[T](dest:Ptr[T]): var T =
cast[ptr T](cast[uint](dest.p).clearMasked(ptrmask))
proc initPtr[T]():Ptr[T] =
cast[ptr T](cast[uint](alloc0(sizeof T)).setMasked(ptrmask))
#etc
yes, I have done so.just need 1 bit. and You can choose which way to use it by compiling conditions.
const FLAG_BIT = (sizeof(int) * 8) - 1 # high bit 1 own
const OWN: int = -1 shl FLAG_BIT
const UN_OWN: int = not OWN
const HighBit{.booldefine.}: bool = false # true: just use the pointer high bit, false use the flag
type
Ptr*[T] = object
when not HighBit:
flag: int
p: ptr [T]
template mark[T](p: pointer): ptr[T] =
cast[ptr[T]](cast[int](p) or OWN)
template unmark*[T](p: ptr[T]): ptr[T] =
cast[ptr[T]](cast[int](p) and UN_OWN)
template isManagement*[T](p: Ptr[T]): bool =
when not defined(HighBit):
(p.flag and OWN) == OWN
else:
(cast[int](p) and OWN) == OWN
proc getPtr*[T](p: Ptr[T]): ptr [T] =
when not defined(HighBit):
result = p.p
else:
result = unmark(p.p)