定义C宏实现CPU字节序值转换 编译报错与do while(0)异常求解
需要定义宏将无符号值转换为与当前CPU默认字节序相反的格式,未做宏封装时相关逻辑运行正常,封装为宏后调用时编译器抛出implicit declaration(隐式声明)错误。已知cpu_to_be32及同类函数属于内核空间函数。
初始错误宏实现如下:
#define be32_or_le32(conv) do { #ifdef __LITTLE_ENDIAN \ conv = cpu_to_be32(conv); \ #elif __BIG_ENDIAN \ conv = cpu_to_le32(conv); \ #endif } while (0) u32 x = 0x12345678; /* Convert the CPU's default byteorder to the opposite one */ be32_or_le32(x); /* Implict decleration */
后续更新:将条件编译移到宏定义外侧后,去掉do {} while (0)包裹时宏可正常运行,但添加do..while(0)包裹后就会抛出错误,对应代码如下:
#ifdef __LITTLE_ENDIAN #define be32_or_le32(conv) do { conv = cpu_to_be32(conv); } while (0) #elif __BIG_ENDIAN #define be32_or_le32(conv) do { conv = cpu_to_le32(conv); } while (0) #endif int __init sys_kernelmod_init(void) { u32 conv; u32 x = 0x12345678; /* Convert the CPU's default byteorder to the opposite one */ conv = be32_or_le32(x); ... }
第一版宏的语法错误
C预处理规则明确要求:#ifdef/#elif/#endif这类预处理指令必须出现在行首(允许前面有空白字符),且不能写在宏定义的替换文本内部。
第一版写法里,预处理器会把#ifdef __LITTLE_ENDIAN直接当成宏替换文本的普通内容,不会执行条件编译逻辑。宏展开后这些预处理指令会被插到函数代码里,成为完全非法的C语法,编译器解析混乱后就会抛出包括隐式声明在内的各种连锁错误。
把条件编译逻辑移到宏定义外侧的写法是正确的,这也是内核代码里定义同类宏的标准写法。
第二版宏加do{}while(0)后报错的原因
核心问题是**do { ... } while(0)是C语言中的语句,不是表达式,没有返回值,不能出现在赋值运算符的右侧**。
调用时写的conv = be32_or_le32(x);在宏展开后会变成:
conv = do { x = cpu_to_be32(x); } while (0);
这是完全不符合C语法的代码:赋值号右侧要求是可求值的表达式,循环语句不能作为右值使用。编译器解析到此处时语法结构已经崩溃,后续抛出的隐式声明错误只是语法解析失败后的连带报错,并非真的缺少函数声明。
去掉do{}while(0)包裹后,宏展开为conv = x = cpu_to_be32(x);,属于合法的连续赋值表达式,因此可以正常运行,但这种写法存在两个隐患:
- 如果宏参数传入带副作用的表达式(如
i++、带返回值的函数调用),参数会被重复求值,引发非预期行为 - 如果宏在无花括号的
if/else分支中调用,会出现逻辑悬挂问题
方案1:保持语句类宏的标准do{}while(0)写法
这是内核中最常用的宏写法,调用时直接传入要修改的变量,不需要额外赋值:
#ifdef __LITTLE_ENDIAN #define be32_or_le32(conv) do { (conv) = cpu_to_be32(conv); } while (0) #elif __BIG_ENDIAN #define be32_or_le32(conv) do { (conv) = cpu_to_le32(conv); } while (0) #endif // 调用示例 u32 x = 0x12345678; be32_or_le32(x); // 直接修改传入的变量
宏参数两侧加括号是为了避免运算符优先级引发的错误。
方案2:使用GNU C语句表达式实现可赋值的表达式类宏
Linux内核默认支持GNU C扩展,如果你需要宏可以直接放在赋值号右侧,可以用语句表达式实现,同时用临时变量避免参数重复求值的问题:
#ifdef __LITTLE_ENDIAN #define be32_or_le32(conv) ({ \ typeof(conv) _tmp = (conv); \ cpu_to_be32(_tmp); \ }) #elif __BIG_ENDIAN #define be32_or_le32(conv) ({ \ typeof(conv) _tmp = (conv); \ cpu_to_le32(_tmp); \ }) #endif // 调用示例 u32 x = 0x12345678; u32 conv = be32_or_le32(x); // 可直接赋值
语句表达式的返回值为大括号内最后一个语句的求值结果,完全符合表达式的使用规则。
内容的提问来源于stack exchange,提问作者ripab

