自定义printf处理uint64_t转字符串遇坏数据及sizeof输出异常
问题详情
我在做业余内核开发,目前在处理内存映射,自己实现了printf()函数。其中%llu的处理代码是这样的:
uint64_t llu = va_arg(parameters, unsigned long long); char buf[21] = {0}; char* ptr = (char*)(&buf[21]); if (llu == 0) { *--ptr = '0'; } while (llu) { *--ptr = (char)('0' + (llu %10)); llu /= 10; } size_t len = strlen(ptr); if (maxrem < len){ // TODO: Set errno to EOVERFLOW. return -1; } if (!print(ptr, len)) return -1; written += len; format++;
%hhu的处理逻辑和这个完全一样,只是用了4字节的缓冲区,循环3次。但运行printf("%hhu %llu\n", 25, 25ULL)时,uint64_t的输出后面会跟着两个乱码字符╛);另外,我定义了一个__attribute__((packed))的结构体mem_map_blk,成员是uint64_t+uint64_t+uint32_t+uint32_t,理论上大小是24字节,但打印sizeof(mem_map_blk)的时候得到了一个超大的数32886564585496。
下面是最小复现代码(在qemu-system-x86_64上内核输出不一样,但问题依然存在):
#include <iostream> #include <cstdio> #include <cstdint> void ull_format(unsigned long long llu){ char buf[21] = {0}; char* ptr = (char*)(&buf[21]); if (llu == 0) { *--ptr = '0'; } while (llu) { *--ptr = (char)('0' + (llu%10)); llu /= 10; } printf("%s ", ptr); } struct __attribute__((packed)) mem_map_blk { uint64_t base; uint64_t length; uint32_t type; uint32_t ACPI_attribs; }; int main() { ull_format(25); ull_format(sizeof(mem_map_blk)); }
问题1:%llu输出后乱码的解决方法
原因
你写的char buf[21] = {0};只会把缓冲区的第一个元素设为0,剩下的20个元素都是内存里的垃圾值。当你从&buf[21]往前写字符时,生成的字符串ptr后面没有正确的终止符,strlen(ptr)会一直读到内存中第一个0为止,自然就把后面的垃圾字符带进去了。
修复
要么确保整个缓冲区都被初始化为0,要么手动给字符串加终止符:
- 方法一:用memset清空整个缓冲区
char buf[21]; memset(buf, 0, sizeof(buf));
- 方法二:直接计算字符串长度,避免依赖strlen(更安全)
// 循环结束后直接计算长度,不用strlen size_t len = &buf[21] - ptr; // 手动添加终止符 ptr[len] = '\0';
注意%hhu的处理逻辑也有同样的问题,要一起修复。
问题2:结构体sizeof值异常的解决方法
原因
核心是可变参数的类型不匹配。sizeof的返回值类型是size_t,如果你的目标平台上size_t和unsigned long long的宽度不一样(比如32位平台上size_t是32位),用va_arg(parameters, unsigned long long)去取size_t类型的参数,就会读错内存位置,得到一堆垃圾值。
另外在内核的printf里,用%llu去打印size_t类型的值,本质就是类型不匹配,必然出问题。
修复
- 给你的printf添加
%zu格式说明符的支持,专门处理size_t类型:
// 处理%zu的分支 size_t sz_val = va_arg(parameters, size_t); // 然后用和%llu类似的逻辑转字符串,注意缓冲区大小要匹配size_t的最大位数
- 临时方案:打印
sizeof结果时,显式转成unsigned long long:
ull_format((unsigned long long)sizeof(mem_map_blk));
- 检查内核中可变参数的取参逻辑,确保每次
va_arg的类型和实际传入的参数类型严格一致,不然会破坏参数栈,后续所有取参都会错。
内容的提问来源于stack exchange,提问作者Carlos Overstreet

