printf的返回值大部分情况下是没有必要关注的, 因为关注了也不会带来好处, 每次都关注的高成本也许会让你放弃.
然而,它的返回值却值得研究.
Q: printf("猫")返回值是多少?
A:
printf_ret.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#ifndef PD
#define PD(str) printf(#str ":%d\n", (str))
#endif
int main(int argc, char *argv[])
{
int ret;
ret = printf("123");
PD(ret);
ret = printf("猫");
PD(ret);
return 0;
}
运行结果:
123ret:3
猫ret:3
源代码默认编码是UTF-8, "猫"的UTF-8格式是3字节, 所以返回3.
Q: 为什么printf返回的是字节数, 不是字符数?
A: 从c语言的角度, 它不应该和所谓的"字符"打交道, 因为"字符"可能有N种编码格式,对应的字节数都可能不一样, libc也不应该
为不同编码格式决定返回值, 否则会让libc变成一个大杂烩. 如果真的要这个返回值, 也应该为此单独写一个API.
Q: "猫"是如何被存储的?
A: lldb调试读到的数据如下:
可以看到, "猫"被存储为e7 8c ab (LSB --> MSB), 查询UTF-8编码表可以对上.
Q: 我们知道了printf返回值的原理, 我们是不是可以动态修改数据?
A:
是的, 修改数据为"ab", 返回值变成了2, 结果如下:
Q: 在编译的静态二进制文件中如何找到"猫"的存储位置?
A:
MachOView:
Q: 如何在libc看到printf返回值是如何计数的?
A:
printf最终会call到vfprintf, return计数默认按字节计算(如下的ch和fmt分别是char和char *类型, 数据默认为1字节).
Q: 如何构造出printf的返回值和预期不一致的情形?
A:
利用printf不支持的格式串让内部不能正常计数输出的字节数.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#ifndef PD
#define PD(str) printf(#str ":%d\n", (str))
#endif
int main(int argc, char *argv[])
{
int ret;
ret = printf("%j123"); // %j is a wrong format!
PD(ret);
return 0;
}
结果:
ret:0
所以, 这个时候可以看到, 返回值有的时候还是有作用的, 为了检测输出的数据而做防范, 通过它确保输出是否有异常.
Q: 除了上面为了检测异常输出的情况, 返回值真的没有用处了吗?
A: 用户空间透过sysfs等机制得到kernel信息时,不可避免地需要用"printf系列/scanf系列"的返回值,而这个时候,正是利用printf系列返回值最佳的时机: 因为无法保证内部组装的字符串都是常量, 一旦涉及到格式, 依赖程序员"直接"统计将是无法保证的任务.
如下是linux cpufreq driver显示cpus信息利用scnprintf/sprintf计数统计值.
Q: C语言有没有内部计数printf的信息?
A: %n 可以记录当前已经输出的字节数, 且不额外占用统计值.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#ifndef PD
#define PD(str) printf(#str ":%d\n", (str))
#endif
#ifndef PD2
#define PD2(str1, str2) \
printf(#str1 ":%d, " #str2 ":%d\n", (str1), (str2))
#endif
int main(int argc, char *argv[])
{
int ret;
int n;
ret = printf("123%n4", &n);
PD2(ret, n);
return 0;
}
结果:
作者: 陈曦
环境: MacOS 10.14.5
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
转载请注明出处