Linux从用户层到内核层系列 - GNU系列之你所不知道的printf

题记:本系列文章的目的是抛开书本从源代码和使用的角度分析Linux内核和相关源代码,byhankswang和你一起玩转linux开发

轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.comLinux从用户层到内核层系列 - GNU系列之你所不知道的printf

欢迎加入到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);
}







Linux从用户层到内核层系列 - GNU系列之你所不知道的printf

上一篇:爱在watir(8)————细枝末节(完)


下一篇:Directshow 捕捉视频入门篇