_IOC定义的不严谨性

C语言中默认类型转换绝对是一个坑. 最近项目中有以下一段code, 隐藏了一个很严重的BUG.
int v4l2_ioctl(int requet, void *arg) {
        if (UVCIOC_CTRL_QUERY == request) {
        /* do something */
        }
}

实际执行过程发现, UVCIOC_CTRL_QUERY == request 永远为false, 即使request == UVCIOC_CTRL_QUERY.

运行环境:
Ubuntu12.04 x86_64

写了一段测试code: .. sourcecode:

int main(int argc, char **argv)
{
        int request = UVCIOC_CTRL_QUERY;

        if (UVCIOC_CTRL_QUERY == request)
                fprintf(stdout, "equal\n");
        else
                fprintf(stdout, "different\n");
        return 0;
}

编译, 运行, 得到的结果是 different, 应该是默认转换出了问题. 查看UVCIOC_CTRL_QUERY的定义, 所用到的宏主要定义在 <linux/uvcvideo.h><sys/ioctl.h>.

#define _IOC_TYPECHECK(t) (sizeof(t))
# define _IOC_WRITE     1U
# define _IOC_READ      2U
#define _IOC(dir,type,nr,size) \
        (((dir)  << _IOC_DIRSHIFT) | \
         ((type) << _IOC_TYPESHIFT) | \
         ((nr)   << _IOC_NRSHIFT) | \
         ((size) << _IOC_SIZESHIFT))
#define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define UVCIOC_CTRL_QUERY       _IOWR('u', 0x21, struct uvc_xu_control_query)

将UVCIOC_CTRL_QUERY 展开, 得到的定义是:

(3U << 30) | ((sizeof(struct uvc_xu_control_query)) << 16) | ('u' << 8) | (0x21)

计算得到的结果为 0xC0107521

在64位机器上, sizeof的返回类型为 long unsigned int. 所以UVCIOC_CTRL_QUERY的类型为 long unsigned int. 所以UVCIOC_CTRL_QUERY的实际值为 0x00000000C0107521

在上述code发生了两次默认转换:

  1. requet = UVCIOC_CTRL_QUERY
    • long unsigned int 转换为int
    • long unsigned int 的长度为64 bits, int 为32 bits, 直接截断
    • 转换后 request=-1072663263(0xC0107521) , 小于 0
  2. UVCIOC_CTRL_QUERY == request
    • requet 转换为 long unsigned int
    • 转换后为 0xFFFFFFFFC0107521
    • UVCIOC_CTRL_QUERY != 0xFFFFFFFFC0107521

request 参数是传给系统调用ioctl的第二个参数, 而ioctl的定义为:

int ioctl(int d, int request, ...)

所以将 requet 定义为unsigned int 也不合适.

无论在64位还是32位的机器上, _IOC的定义应该为uint32_t, 所以_IOC的定义应该加强制类型转换.

#define _IOC(dir,type,nr,size) \
        (uint32_t) (    \
        (((dir)  << _IOC_DIRSHIFT) | \
         ((type) << _IOC_TYPESHIFT) | \
         ((nr)   << _IOC_NRSHIFT) | \
         ((size) << _IOC_SIZESHIFT)))