4

If I create a userdata object and stash it in a table, then get a reference to it in C/C++, for how long is that reference valid? Is the reference in C/C++ guaranteed to be valid for as long as the userdata is held in the table in Lua? Or is there a risk that the Lua runtime will move the userdata object, invalidating the C/C++ reference to it?

Here's what I'm doing:

// Initially, the stack contains a table
class Foo { ... };
lua_pushstring(L, "my_userdata");
void* mem = lua_newuserdata(L, sizeof(Foo));
new (mem) Foo();
lua_settable(L, -3);

// Later:
lua_pushstring(L, "my_userdata");
lua_gettable(L, -2);
Foo *foo = (Foo*)lua_touserdata(L, -1);
lua_pop(L, 1);
// How long will this pointer be valid?

Am I better off using operator new and a light userdata here?

Tom
  • 7,269
  • 1
  • 42
  • 69

2 Answers2

4

The reference (or pointer since Lua is written in C) will remain valid for the lifetime of the userdata.

Lua's chief architect addressed this on the Lua-l mailing list:

Quote: Apr 18, 2006; Roberto Ierusalimschy

The caution is about strings, not about userdata (although we actually did not say that explicitly in the manual). We have no intention of allowing userdata addresses to change during GC. Unlike strings, which are an internal data in Lua, the only purpose of userdata is to be used by C code, which prefer that things stay where they are :)

You can control the lifetime of a userdata by anchoring it in the state:

There are several reasons you may prefer a full userdata over a lightuserdata:

  • Full userdata may have its own uservalue and metatable (all lightuserdata shares the same metatable)
  • Finalization though the __gc metamethod
  • Several convenience API functions for working with userdata (luaL_newmetatable, luaL_setmetatable, etc.)

A common way of creating userdata from a class in C++ is to use the pointer-to-pointer idiom:

class Foo { ... };

static int new_Foo(lua_State *L) {
    // begin userdata lifetime
    Foo **ud = static_cast<Foo **>(lua_newuserdata(L, sizeof *ud)); 
    luaL_setmetatable(L, "Foo");

    // begin C++ object lifetime  
    *ud = new Foo();
    return 1;
}

// __gc metamethod
static int delete_Foo(lua_State *L) {  
    Foo **ud = static_cast<Foo **>(luaL_checkudata(L, 1, "Foo"));

    // end C++ object lifetime
    delete *ud;

    // end userdata lifetime
    return 0;
}
Community
  • 1
  • 1
Adam
  • 3,053
  • 2
  • 26
  • 29
  • 1
    Thanks, great answer. Is there any problem with instead saying `void * men = lua_newuserdata(L, sizeof (Foo)); Foo* f = new (men) Foo ();` ? – Tom Aug 04 '16 at 07:10
  • 1
    @tom the problem is who will call `delete`? There are two distinct lifetimes; the userdata (managed with C's `realloc`), and the C++ object (managed with `new`/`delete`). With a placement `new` the memory is shared, Lua will happily collect it and you'll have undefined behaviour. The pointer-to-pointer idiom allows both these lifetimes to coexist. Lua manages its memory with `realloc` and in a `__gc` metamethod you can call `delete` on your C++ object. – Adam Aug 04 '16 at 15:03
  • 3
    You could call the destructor manually in the `__gc` metamethod. That's normal anyway if you use placement new ... – siffiejoe Aug 04 '16 at 15:25
3

It is valid until the Lua garbage collector determines that the table (or table element) is no longer in use anywhere and can be safely deleted. Using metamethod, Lua will notify you when the garbage collection occurs.

http://pgl.yoyo.org/luai/i/lua_newuserdata

https://www.lua.org/pil/29.html

Sven Nilsson
  • 1,861
  • 10
  • 11