Lua
语言核心对内存分配不进行任何假设,它既不会调用 malloc
也不会调用 realloc
来分配内存。相反, Lua
语言核心只会通过一个分配函数( allocation function
)来分配和释放内存,当用户创建 Lua
状态时必须提供该函数。
luaL_newstate
是一个用默认分配函数来创建 Lua
状态的辅助函数。该默认分配函数使用了来自 C
语言标准库函数 malloc-realloc-free
,对于大多数应用程序来说,这几个函数(或应该是)够用了。但是,要完全控制 Lua
的内存分配也很容易,使用原始的 lua_newstate
来创建我们自己的 Lua
状态即可:
lua_State *lua_newstate (lua_Alloc f, void *ud);点击复制复制失败已复制
该函数有两个参数:一个是分配函数,另一个是用户数据。用这种方式创建的 Lua
状态会通过调用 f
完成所有的内存分配和释放,甚至结构 lua_State
也是由 f
分配的。
分配函数必须满足 lua_Alloc
的类型声明:
typedef void * (*lua_Alloc) (void *ud, void *ptr, size_t osize, size_t nsize);点击复制复制失败已复制
第一个参数始终为 lua_newstate
所提供的用户数据;第二个参数是正要被(重)分配或者释放的块的地址;第三个参数是原始块的大小;最后一个参数是请求的块大小。如果 ptr
不是 NULL
, Lua
会保证其之前被分配的大小就是 osize
(如果 ptr
是 NULL
,那么这个块之前的大小肯定是零,所以 Lua
使用 osize
来存放某些调试信息)。
Lua
语言使用 NULL
表示大小为零的块。当 nsize
为零时,分配函数必须释放 ptr
指向的块并返回 NULL
,对于所要求的大小(为零)的块。当 ptr
是 NULL
时,该函数必须分配并返回一个指定大小的块;如果无法分配指定的块,则必须返回 NULL
。如果 ptr
是 NULL
并且 nsize
为零,则两条规则都适用;最终结果是分配函数什么都不做,返回 NULL
。
最后,当 ptr
不是 NULL
并且 nsize
不为零时,分配函数应该像 realloc
一样重新分配块并返回新地址(可能与原地址一致,也可能不一致)。同样,当出现错误时分配函数必须返回 NULL
。 Lua
假定分配函数在块的新尺寸小于或等于旧尺寸时不会失败( Lua
在垃圾收集期间会压缩某些结构的大小,并且无法从垃圾收集时的错误中恢复)。
lua_newstate
使用的标准分配函数定义如下(从文件 lauxlibe.c
中直接抽取):
static void *l_alloc (void *ud, void *ptr, size_t osize, size_t nsize) { (void)ud; (void)osize; /* not used */ if (nsize == 0) { free(ptr); return NULL; } else return realloc(ptr, nsize); }点击复制复制失败已复制
该函数假设 free(NULL)
什么也不做,并且 realloc(NULL, size)
等价于 malloc(size)
。 ISO C
标准会托管这两种行为。
我们可以通过调用 lua_getallocf
恢复( recover
) Lua
状态的内存分配器:
lua_Alloc lua_getallocf (lua_State *L, void **ud);点击复制复制失败已复制
请记住,所有新的分配函数都有责任释放由前一个分配函数分配的块。通常情况下,新的分配函数是在旧分配函数的基础上做了包装,来追踪分配( trace allocation
)或同步访问堆( heap
)的。
Lua
在内部不会为了重用而缓存空闲内存。它假定分配函数会完成这种缓存工作;而优秀的分配函数确实也会这么做。 Lua
不会试图压缩内存碎片。研究表明,内存碎片更多是由糟糕的分配策略导致的,而非程序的行为造成的;而优秀的分配函数不会造成太多内存碎片。
对于已有的优秀分配函数,想要做到比它更好是很难的,但有时候也不妨一试。例如 Lua
会告诉你已经释放或者重新分配的块的原有大小。因此,一个特定的分配函数不需要保存有关块大小的信息,以此减少每个块的内存开销。
还有一种可以改善的内存分配的场景,是在多线程系统中。这种系统通常需要对内存分配函数进行线程同步,因为这些函数使用的是全局资源(堆)。不过,对 Lua
状态的访问也必须是同步的——或者更好的情况是:限制只有一个线程能够访问 Lua
状态。因此,如果每个 Lua
状态都从私有的内存池中分配内存,那么分配函数就可以避免线程同步导致的额外开销。