题记:本系列文章的目的是抛开书本从源代码和使用的角度分析Linux内核和相关源代码,byhankswang和你一起玩转linux开发
首先从我们的Hello World!说起:
#include <stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}
这段代码可能是大多数程序员或者学生的接触的首段代码,从老师的讲解到工作中的使用,甚至工作多年之后可能还有很多人不知道这段代码程序中printf到底是怎么运行的。
1.<stdio.h>头文件
在ubuntu12.04系统中 /usr/include/stdio.h 包含了上面代码中的头文件<stdio.h>, 其中对于printf的外部引用为:
359 /* Write formatted output to stdout.
360
361 This function is a possible cancellation point and therefore not
362 marked with __THROW. */
363 extern int printf (__const char *__restrict __format, …);
我们看到关于printf为外部引用,GCC在编译main.c文件的时候包含了头文件<stdio.h>,在运行这段代码的时候会调用libc.so库。
2.glibc对printf的实现
在glibc中printf的实现在源文件/stdio-common/printf.c中:
/* Write formatted output to stdout from the format string FORMAT. */
/* VARARGS1 */
int
__printf (const char *format, …)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
然后很多同学就该问了,printf和__printf是什么关系?其实文件printf.c中这段代码还有一次重要的别名替换:
ldbl_strong_alias (__printf, printf);
正是ldbl_strong_alias把__printf替换成了我们熟知的printf,并放到了libc的符号表中:
#define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)
# define strong_alias(original, alias)
\
.globl C_SYMBOL_NAME (alias) ASM_LINE_SEP
\
.set C_SYMBOL_NAME (alias),C_SYMBOL_NAME (original) ASM_LINE_SEP
\
.globl C_SYMBOL_DOT_NAME (alias) ASM_LINE_SEP
\
.set C_SYMBOL_DOT_NAME (alias),C_SYMBOL_DOT_NAME (original)
至此,我们引用的头文件和在程序运行是动态加载的动态库libc.so就连贯起来了。
3.printf是如何支持可变参数的
在printf的源码实现中我们可以看到,使用了va_list arg变量,并调用了va_start和va_end宏定义,对于va_*不清楚的可以百度一下。
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
所以对于printf中可变参数的支持原来是如此简单。
4.printf进阶问题
我们看到printf的实现是调用了vfprintf。对于格式化输出的函数包括:printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf。我们再研究一下vfprintf, vfprintf的源码如下:
__fortify_function int
vfprintf (FILE *__restrict __stream,
const char *__restrict __fmt, _G_va_list __ap)
{
return __vfprintf_chk (__stream, __USE_FORTIFY_LEVEL – 1, __fmt, __ap);
}
其中__vfprintf_chk的实现代码是:
int attribute_hidden __vfprintf_chk (FILE *s, int flag, const char *fmt, va_list ap) { return __nldbl___vfprintf_chk (s, flag, fmt, ap); }