Reputation: 23
I have code where I create userdata (a circle) which gets passed to Lua that I also store as a pointer in C++ and put into a vector. After I call lua_close(L)
, I try to delete the pointer, but this causes the program to crash. I also tried free, but that had the same result
I then learned from this post that Lua will automatically free userdata and therefore I do not need to delete the pointer. Is this true?
I am skeptical because after I close Lua using lua_close(), I can still access the circle, moving it, drawing it, etc.
If I don't delete the pointer to the userdata will this cause a memory leak?
I've tried deleting and freeing the pointer, doing it before I close Lua, but all just crashes
Here's how I create the userdata
int wrap_createCircle(lua_State* luaState){
float radius = lua_tonumber(luaState, 1);
CircleShape* circle = (CircleShape*)lua_newuserdata(luaState, sizeof(CircleShape));
new (circle) CircleShape(radius);
luaL_getmetatable(luaState, "CircleMetaTable");
lua_setmetatable(luaState, -2);
return 1;
}
Upvotes: 1
Views: 236
Reputation: 137
I think what you want is something like this, when the GC is triggered it will call the deleteCircleShape method which will delete the CircleShape* thus the CircleShapes destructor will be called and then lua will remove the pointer pointer once the GC returns.
P.S. if you have this pointer stored in a vector as well you can add code in the destructor (or deleteCircleShape method) to remove it from the vector so you don't get any null errors if you are looping said vector for drawing etc
#include <iostream>
#include "lua.hpp"
#define LUA_FUNC __declspec(dllexport)
class CircleShape {
float radius_;
public:
explicit CircleShape(const float radius) : radius_(radius) {
std::cout << "CircleShape Constructor Called" << std::endl;
}
~CircleShape() {
std::cout << "CircleShape Destructor Called" << std::endl;
}
[[nodiscard]] float getRadius() const { return radius_; }
};
LUA_FUNC int NewCircleShape(lua_State* L) {
const auto radius = static_cast<float>(luaL_checknumber(L, 1));
auto** p_circle_shape = static_cast<CircleShape**>(lua_newuserdata(L, sizeof(CircleShape*)));
*p_circle_shape = new CircleShape(radius);
luaL_getmetatable(L, "CircleMetaTable");
lua_setmetatable(L, -2);
return 1;
}
LUA_FUNC int DeleteCircleShape(lua_State* L) {
auto** p_circle_shape = static_cast<CircleShape**>(luaL_checkudata(L, 1, "CircleMetaTable"));
luaL_argcheck(L, *p_circle_shape != NULL, 1, "Error blah");
// if pointer is stored somewhere like a vector remove it now!!!
delete* p_circle_shape;
return 0;
}
LUA_FUNC int GetCircleShapeRadius(lua_State* L) {
auto** p_circle_shape = static_cast<CircleShape**>(luaL_checkudata(L, 1, "CircleMetaTable"));
const float radius = (*p_circle_shape)->getRadius();
lua_pushnumber(L, radius);
return 1;
}
extern "C" LUA_FUNC int luaopen_CircleShapeModule(lua_State * L) {
static constexpr luaL_Reg circle_shape_methods[] = {
{ "getRadius", &GetCircleShapeRadius },
{nullptr, nullptr}
};
static constexpr luaL_Reg circle_shape[] = {
{ "new", &NewCircleShape },
{nullptr, nullptr}
};
luaL_newlib(L, circle_shape);
luaL_newmetatable(L, "CircleMetaTable");
luaL_newlib(L, circle_shape_methods);
lua_setfield(L, -2, "__index");
lua_pushstring(L, "__gc");
lua_pushcfunction(L, DeleteCircleShape);
lua_settable(L, -3);
lua_pop(L, 1);
return 1;
}
Upvotes: 1
Reputation: 48662
When you call lua_newuserdata
, you're creating a full userdata. The pointer returned by that function will be freed automatically by Lua when the corresponding userdata object goes away, so there will not be a memory leak, and you should not try to free it yourself.
I am skeptical because after I close Lua using lua_close() I can still access the circle, moving it, drawing it, etc.
Use-after-free in C++ is Undefined Behavior, which means literally anything can happen, such as accesses succeeding as if the memory were still valid.
Another note about your function: the C++ standard says this under section 6.7.3 [basic.life]:
For an object of a class type, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression (7.6.2.8) is not used to release the storage, the destructor is not implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.
So you should probably add a __gc
metamethod that calls circle->~CircleShape();
.
Upvotes: 1