前些时间,朋友问了一些问题:
printf
是线程安全的吗?printf
的%s
格式输出,如果参数是其它类型的数据强制转换为char*
的,结果会怎么样?
Linux 是开源的,何不查看源码理解程序工作原理呢?我们也可以通过 gdb 调试手段(glibc / kernel)去深入理解源码的实现。
1. 问题
1.1. 是否线程安全
我们写个 demo,gdb 一下看看,printf 是否线程安全的。
1
2
3
4
5
6
7
/* gcc -g -O0 test_printf.cpp -o tp */
#include <stdio.h>
int main(int argc, char** argv) {
printf("hello %d.\n", 1);
return 0;
}
- gdb 调试 demo,发现 printf 的内部实现有锁操作,所以它是线程安全的。gdb 要支持调试进 glibc,系统需要安装一些插件才行。例如 Centos 的,可以参考视频:(Centos) Debugging in glibc with gdb
1
2
3
4
5
6
7
8
9
10
11
12
13
/* vfprintf.c. */
int vfprintf (FILE *s, const CHAR_T *format, va_list ap) {
...
/* Lock stream. */
_IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
_IO_flockfile (s);
...
all_done:
...
_IO_funlockfile (s);
_IO_cleanup_region_end (0);
return done;
}
- 查看 Linux 系统的 glibc 版本,然后到 glibc 下载地址 去下载对应版本的 glibc 源码去看看。
1
2
3
4
5
# uname -r
# 3.10.0-1062.18.1.el7.x86_64
[wenfh2020] ldd --version
ldd (GNU libc) 2.17
Copyright (C) 2012 Free Software Foundation, Inc.
- glibc 源码。
- Linux 内核源码,也有各种版本的 printf 的实现,有兴趣也可以去 github 找来看看。
1.2. 强制转换问题
- 64 位机器,int 大小是 4 bytes,而指针大小是 8 bytes。如果一个 int 类型的数据,被当作 char* 指针读取,那么将会多读 4 个字节数据,结果不可预料。
- 从源码上看
strnlen
是通过查找内存的 ‘\0’ 字符串结束符的。如果强制转换的内存数据,没有 ‘\0’ 结束符,那strnlen
就会出现问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/* https://github.com/torvalds/linux/blob/master/arch/x86/boot/printf.c */
int printf(const char *fmt, ...) {
char printf_buf[1024];
va_list args;
int printed;
va_start(args, fmt);
printed = vsprintf(printf_buf, fmt, args);
va_end(args);
puts(printf_buf);
return printed;
}
int vsprintf(char *buf, const char *fmt, va_list args) {
...
switch (*fmt) {
...
case 's':
s = va_arg(args, char *);
len = strnlen(s, precision);
if (!(flags & LEFT))
while (len < field_width--)
*str++ = ' ';
for (i = 0; i < len; ++i)
*str++ = *s++;
while (len < field_width--)
*str++ = ' ';
continue;
}
...
}
/* https://github.com/torvalds/linux/blob/master/arch/x86/boot/string.c */
size_t strnlen(const char *s, size_t maxlen) {
const char *es = s;
while (*es && maxlen) {
es++;
maxlen--;
}
return (es - s);
}
2. 其它的开源
例如字符串拷贝,也可以从 Linux 内核源码去找。
1
2
3
4
5
6
7
8
9
/* https://github.com/torvalds/linux/blob/master/lib/string.c */
char *strcpy(char *dest, const char *src) {
char *tmp = dest;
while ((*dest++ = *src++) != '\0')
/* nothing */;
return tmp;
}
3. 小结
- 深入看内核源码是一个好习惯。
- 要熟悉调试和看源码解决问题的方法和思路。