文章目录

Lua实现中如果没有定义LUA_NANTRICK这个宏,那么他的TValus结构是这么定义的:

typedef struct {
    union {
        GCObject *gc;
        void *p;
        lua_CFunction f;
        numfield
    } value_;
    int tt_;
} TValues;

其中numfield一般是double类型的数据,value存储的是值的实际值,tt代表了值的类型。按double为8个字节,4字节对齐的话那么这么TValus结构的大小为12个字节。

为了减少TValus结构的大小,在编译的时候可以定义LUA_NANTRICK这个宏,那么TValues结构的定义就变成了:

typedef struct {
    union {
        union {
            union {
                GCObject *gc;
                void *p;
                lua_CFunction f;
            } value_;
            int tt_;
        } i;
        double d__;
    } v;
} TValues;

在这里还要说明系统是是按小头处理多字节数值类型数据的,如果是大头系统则将tt和value的定义顺序调换。现在TValues结构的大小就变成了8个字节。

那么Lua是怎么区分数值类型和其他非数值类型的呢?在解答这个问题之前先要说一下浮点数的表示方法,大多数系统都遵循IEEE 754标准表示浮点数。以8字节的双精度浮点数以及小头系统为例,双精度浮点数的二进制表示如下:

double

因为是小头系统,所以T占据0~51比特位,E占据52~62比特位,S占据第63比特位。IEEE 754规定:

  • 如果E全为1,且T不为0,则表示NaN(not a number)
  • 如果E全为1,且T为0,则表示Inf,+Inf还是-Inf由S决定
  • 如果E、T全为0,则表示0,+0还是-0由S决定

正是利用了这个规范,在定义了LUANANTRICK这个宏的情况下,Lua的实现还是能够区分数值类型和非数值类型,对于非数值类型都会执行一个操作(tt | NNMARK)

#define NNMARK        0x7FF7A500

这个标记在内存中的表示为:

0000 0000 1010 0101 1110 |1111 1111 111|0

可见这个NNMARK是IEEE 754规范中的NaN,因为Lua中的数据类型8个比特就能表示,所以就利用tt_中的低8位用来区分分数值类型中的其他类型,而高24位用于区分数值类型和非数值类型。

拿到一个TValues结构先查看高4个字节,即tt,通过tt的高24位判断是否NaN,如果是NaN则再通过低8位判断具体类型,如果是数值则读取d__。

文章目录

举个栗子……