一个printf的小例子
日期:2014-04-12 12:15:25
最后更新日期:2015-06-26 10:09:32
[code lang="cpp"]
//例1: test1.cpp
//g++ -Wall test1.cpp -o test1
#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
char *buf="迷失";
printf("%02x %02x %02x %02x %02x\n",buf[0],buf[1],buf[2],buf[3],buf[4]);
cout<<buf<<endl;
return 0;
}
[/code]
结果为:
[code lang="cpp"]
ffffffe8 ffffffbf ffffffb7 ffffffe5 ffffffa4 ffffffb1 00
迷失
[/code]
我本来的意图是想打印这个字节里面的存放的值,可编译器老是在做类型提升,将char转化int,所以就变成了开始的那个让人讨厌的ff。你可以用这行代码来看下类型提升效果:
[code lang="cpp"]
例2:
char a=1,b=2;
printf("%d\n",sizeof(a+b));
[/code]
这行输出结果为4。看来不写个编译器,真不知道里面做了些什么事。对于上面的hex,可以写个byte_to_hex的函数,这样就相当于把内存dump下来。不过,这个程序是怎么输出中文的?一个类似这样也能做到例1那样的事,这里我是把例1的输出用hex方式放到buf里面。也许在你的系统的hex值并不一样,这与locale有关,我的locale:LANG=zh_CN.UTF-8。 尝试一下,直接打印16进制0xe8,0xbf,0xb7,0xe5,0xa4,0xb1会出现什么效果。
[code lang="cpp"]
//例3: test2.cpp
//g++ -Wall test2.cpp -o test2
#include<iostream>
#include<stdio.h>
using namespace std;
int main()
{
char *buf3="\xe8\xbf\xb7\xe5\xa4\xb1";
printf("%s\n",buf3);
return 0;
}
[/code]
结果和想象中一样,还是输出了“迷失”。用strace ./test2追踪一下,这个程序的系统调用。得到如下:
[code lang="cpp"]
write(1, "\350\277\267\345\244\261\n", 7迷失) = 7
[/code]
描述符1,就是标准输出,怎么是"\350\277\267\345\244\261\n",而不是 0xe8 0xbf 0xb7 0xe5 0xa4 0xb1?后来gdb发现这原来是8进制。当时纳闷为什么传进去的是utf-8这种存储方式字节,而不是转好的unicode。后来饶有兴趣地在内核中追踪了一下,发现了一点端倪。虽然感觉函数看得并不是很明白,现将过程(整个方法)贴出如下,我是在早一段时间编译的比较新的内核,
[code lang="cpp"]
mooccf@DigitalSystemsVM:~/data/work$ uname -a
Linux DigitalSystemsVM 3.13.6 #1 SMP Mon Mar 10 05:39:17 GMT 2014 i686 i686 i686 GNU/Linux
[/code]
write调用对应于内核调用sys_write,这个算是常识吧。读者可以看下早期linux-0.11的代码就知道了,软中断对应一个中断向量表,中断向量表里面放的是处理对应中断号的函数地址。根据前面我的文章:捣鼓linux里面提到的方法-gdb vmlinux,在里面设置个断点:
[code lang="cpp"]
Reading symbols from /home/mooccf/data/work/vmlinux...done.
(gdb) b sys_write
Breakpoint 1 at 0xc1173b70: file /usr/local/src/linux-3.13.6/fs/read_write.c, line 514.
[/code]
sys_write调用vfs_write,此函数调用了具体的write函数:
[code lang="cpp"]
ret = file->f_op->write(file, buf, count, pos);
[/code]
这里输出是到tty设备,该函数也对应了tty设备的输出函数-tty_write。这个函数我是问人知道了,理论上可以一步步跟踪到整个调用序列,但何必呢?问下人或搜索下就知道了,主要是整个查找过程也是很繁琐的。
[code lang="cpp"]
(gdb) b tty_write
Breakpoint 2 at 0xc13cc410: file /usr/local/src/linux-3.13.6/drivers/tty/tty_io.c, line 1199.
[/code]
这个函数实际处理函数是:
[code lang="cpp"]
ret = do_tty_write(ld->ops->write, tty, file, buf, count);
[/code]
ld->ops->write也就是console write即con_write。
[code lang="cpp"]
(gdb) b con_write
Breakpoint 4 at 0xc13e3d20: file /usr/local/src/linux-3.13.6/drivers/tty/vt/vt.c, line 2673.
[/code]
而其真正函数为do_con_write,在这个函数里面,有个状态机,这个里面对utf8序列进行解析,然后刷屏。总结:输出到终端原理为,ch小于0x80,那么为acsii码直接输出,否则对其转义,根据utf8规则转化为偏移(机内码),然后刷屏输出,也就是刷像素点。