题记:本系列文章的目的是抛开书本从源代码和使用的角度分析Linux内核和相关源代码,byhankswang和你一起玩转linux开发
轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.com
欢迎加入到CHLK - Linux开发交流群 QQ:327084515 讨论Linux开发相关问题
原创文章,转载请标明出处。
GNU系列之你所不知道的printf
上篇文章介绍了GNU的libc并引出了几个问题(《Linux从用户层到内核层系列 - GNU系列之glibc介绍》,URL:http://blog.csdn.net/byhankswang/article/details/9319099),本篇文章将从实例的角度分析并解决之前的问题。这边文章立志把printf的问题从用户层到库函数再到内核调用这个脉络彻底讲明白。
首先从我们的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);
}