Should we explicitly cast from luaL_checkinteger() et al?

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Should we explicitly cast from luaL_checkinteger() et al?

Niccolo Medici
Greetings.

I've asked this question on StackOverflow (see link [1] bellow), and
@lhf suggested that I ask it here.

After I get answers here I'll summarize them on StackOverflow.

--- I'm hereby pasting my question: ---

Lua 5.3 deprecates luaL_checkint, luaL_checklong, luaL_optint,
luaL_optlong, which were just convenience macros calling
luaL_{check|opt}integer.

While we can still use them (because of -DLUA_COMPAT_5_2), they aren't
mentioned in the user manual and we're advised to use
luaL_{check|opt}integer "with a type cast".

Now, I'm not an expert in C and I was wondering:

(1) Is a cast needed in simple cases like the following?

    int i;
    i = (int)luaL_checkinteger(L, 1);

(2) If a cast isn't needed here, where *is* it needed?

(3) Why were those deprecated macros born in the first place if we can
do without them? In other words: what did they serve?

(4) Aren't we losing "documentation" by not having the words
"int"/"long" embedded in the function name?


[1] http://stackoverflow.com/questions/28098822/should-we-explicitly-cast-from-luas-lual-checkinteger-et-al

Reply | Threaded
Open this post in threaded view
|

Re: Should we explicitly cast from luaL_checkinteger() et al?

William Ahern
On Fri, Jan 23, 2015 at 01:05:48AM +0200, Niccolo Medici wrote:
<snip>

> Lua 5.3 deprecates luaL_checkint, luaL_checklong, luaL_optint,
> luaL_optlong, which were just convenience macros calling
> luaL_{check|opt}integer.
>
> While we can still use them (because of -DLUA_COMPAT_5_2), they aren't
> mentioned in the user manual and we're advised to use
> luaL_{check|opt}integer "with a type cast".
>
> Now, I'm not an expert in C and I was wondering:
>
> (1) Is a cast needed in simple cases like the following?
>
>     int i;
>     i = (int)luaL_checkinteger(L, 1);

No. As far as the C standard is concerned, it's not needed in such a case.
It behaves identically to an implicit conversion.

> (2) If a cast isn't needed here, where *is* it needed?

Assuming an architecture where sizeof (int) < sizeof (lua_Integer), then the
following would print two different results:

    printf("%zu\n", sizeof luaL_checkinteger(0, 0));
    printf("%zu\n", sizeof ((int)luaL_checkinteger(0, 0)));

That's one reason why a macro like luaL_checkint would use a cast.

Another reason is that some compilers might issue warnings about implicit
conversions, even if the code is 100% correct, yet remain silent if there's
an explicit cast. Normally this is a reason _not_ to cast! Casts should be
avoided because they can also cause some compilers to accept code which is
absolutely incorrect. But some consider it good etiquette for library
headers to use casts in situations similar to the above so developers who
have compiler diagnostics cranked all the way up so they don't think the
library is broken or otherwise have to deal with the diagnostic noise.

However, signed conversions (whether implicit or with a cast) can still lead
to undefined behavior at _runtime_, even if the code construct is
well-defined.

For example, assuming the same architecture as above, then
 
    int i;
    lua_pushinteger(L, (lua_Integer)INT_MAX + 1);
    i = luaL_checkinteger(L, -1);

leads to undefined behavior at runtime. That's because the value returned by
luaL_checkinteger cannot be represented as an int, and unlike unsigned types
C doesn't define or specify how to translate the value. You have to worry
about two kinds of behavior--during compilation and during runtime.

<snip>
> (4) Aren't we losing "documentation" by not having the words
> "int"/"long" embedded in the function name?

lua_Integer is not necessarily the same type as either int or long. For
example on Windows, where lua_Integer will default to a 64-bit type but
where long is 32 bits.

Note that while lua_tointeger and similar have runtime logic to check that
the integral part of a float is representable by a lua_Integer type,
luaL_optint, luaL_checkint, etc never had such logic. They just used
explicit casts. This is documented, but I think it gives people a false
sense of security. Arithmetic conversions can be problematic, and signed
conversions especially so. (Conversions to unsigned are guaranteed to
exhibit modulo behavior--i.e. twos complement for someone who insists on
conflating the hardware with the abstract machine.)

IMHO it's best to use lua_Integer as much as possible, and be careful where
you convert from lua_Integer to another type. Because most C implementations
don't do bounds checking (they can, believe it not, and some do), it's
important to familiarize oneself with arithmetic conversions. The C standard
is your friend, and unlike the C++ standard it's pretty easy to read and
understand.


Reply | Threaded
Open this post in threaded view
|

Re: Should we explicitly cast from luaL_checkinteger() et al?

Philipp Janda
Am 23.01.2015 um 04:37 schröbte William Ahern:

> On Fri, Jan 23, 2015 at 01:05:48AM +0200, Niccolo Medici wrote:
> <snip>
>> Lua 5.3 deprecates luaL_checkint, luaL_checklong, luaL_optint,
>> luaL_optlong, which were just convenience macros calling
>> luaL_{check|opt}integer.
>>
>> While we can still use them (because of -DLUA_COMPAT_5_2), they aren't
>> mentioned in the user manual and we're advised to use
>> luaL_{check|opt}integer "with a type cast".
>>
>
>[...]
>
>> (2) If a cast isn't needed here, where *is* it needed?
>
> Assuming an architecture where sizeof (int) < sizeof (lua_Integer), then the
> following would print two different results:
>
>      printf("%zu\n", sizeof luaL_checkinteger(0, 0));
>      printf("%zu\n", sizeof ((int)luaL_checkinteger(0, 0)));

I'll throw in two more examples:

     printf("%d\n", (int)luaL_checkinteger(L, 1));

(undefined behavior without the cast)

or:

     lua_Integer x = 0U + (int)luaL_checkinteger(L, 1);

(with cast: 0 <= x <= UINT_MAX, without cast: LUA_MININTEGER <= x <=
LUA_MAXINTEGER)

>
> <snip>
>> (4) Aren't we losing "documentation" by not having the words
>> "int"/"long" embedded in the function name?

The "int"/"long" is embedded in the cast instead, which is a normal
place for C programmers to look. Actually, we *gain* documentation,
because we have the target type *and* the internal Lua type mentioned
explicitly in the code.


Philipp