_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发生了两次默认转换:
-
requet = UVCIOC_CTRL_QUERY
- long unsigned int 转换为int
- long unsigned int 的长度为64 bits, int 为32 bits, 直接截断
- 转换后 request=-1072663263(0xC0107521) , 小于 0
-
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)))