前面从编译链接的角度从新理解了hello world代码,这里还有个printf这个函数很神奇。参数可变。下载glibc库

/stdio-common$ cat printf.c可以看到printf的源码

/* Copyright (C) 1991-2013 Free Software Foundation, Inc.
   This file is part of the GNU C Library.

   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.

   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */

#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>

<span style="color:#FF0000;">#undef printf

/* 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;
}</span>

#undef _IO_printf
ldbl_strong_alias (__printf, printf);
/* This is for libg++.  */
ldbl_strong_alias (__printf, _IO_printf);

这里面ldbl_strong_alias是替换语句

这里调用fprintf,看看fprintf.c函数中相关的代码

# define vfprintf _IO_vfprintf_internal

vfrintf代码涉及很多,另外开专题看看。

这里看看va_list va_start等东西的实现

根据前面学习的编译链接的知识,函数之间传递,是通过栈来完成,对于未知参数个数的,在编译时,是需要编译器提前解释的。如同#define宏定义一样,这样先确定参数个数才能编译成汇编语言。书《c标准库》将这是宏。

在stdarg.h中看到

#define va_start(v,l)	__builtin_va_start(v,l)
#define va_end(v)	__builtin_va_end(v)
#define va_arg(v,l)	__builtin_va_arg(v,l)
typedef __gnuc_va_list va_list;
typedef __builtin_va_list __gnuc_va_list;

在glibc中没有找到这个__builtin_va_list,

在c标准库找到在stdarg.h中有定义

typedef char* va_list;

---------------------------------------------先看看例子-----------------------------

add.c

cat add.c 
#include<stdarg.h>

int add(int n,...)
{
	<span style="color:#FF0000;">va_list ap;</span>
	<span style="color:#FF0000;">va_start(ap,n);</span>
	int temp=0;
	int i;
	for(i=0;i<n;i++)
	temp+=<span style="color:#FF0000;">va_arg(ap,int)</span>;
    <span style="color:#FF0000;">va_end(ap);</span>
    return temp;
}
main.c
 cat main.c 
#include<stdio.h>
extern int add(int,...);

int main()
{
	int s= add(3,1,2,3);
	printf("va_add is %d\n",s);
	
}

va_list是一个宏,由va_start和va_end界定。

typedefchar* va_list;
  void va_start ( va_list ap, prev_param );
  type va_arg ( va_list ap, type );
  void va_end ( va_list ap );
其中,va_list是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。


<Step 1> 在调用参数表之前,应该定义一个 va_list类型的变量,以供后用(假设这个 va_list 类型变量被定义为ap);
<Step 2> 然后对 ap进行初始化,让它指向可变参数表里面的第一个参数。这是通过 va_start 来实现的,其第一个参数是 ap本身,第二个参数是在变参表前面紧挨着的一个变量;
<Step 3> 然后是获取参数,调用 va_arg。它的第一个参数是ap,第二个参数是要获取的参数的指定类型,并返回这个指定类型的值,同时把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap指针关掉,以免发生危险,方法是调用 va_end。它是将输入的参数 ap 置为NULL,应该养成获取完参数表之后关闭指针的习惯。

上面是c标准库的理论说法,下面通过编译链接看看

gcc -E add.c -o add.i

cat add.i
# 1 "add.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "add.c"
# 1 "/usr/lib/gcc/i686-linux-gnu/4.6/include/stdarg.h" 1 3 4
# 40 "/usr/lib/gcc/i686-linux-gnu/4.6/include/stdarg.h" 3 4
<span style="color:#FF0000;">typedef __builtin_va_list __gnuc_va_list;</span>
# 102 "/usr/lib/gcc/i686-linux-gnu/4.6/include/stdarg.h" 3 4
<span style="color:#FF0000;">typedef __gnuc_va_list va_list;</span>
# 2 "add.c" 2

int add(int n,...)
{
 <span style="color:#FF0000;">va_list</span> ap;
 <span style="color:#3333FF;">_<span style="color:#330099;">_builtin_va_start</span></span>(ap,n);
 int temp=0;
 int i;
 for(i=0;i<n;i++)
 temp+=<span style="color:#000099;">__builtin_va_arg(</span>ap,int);
 <span style="color:#000066;">__builtin_va_end</span>(ap);
 return temp;
}

va_宏都已替换

gcc -g -S add.c -o add.s

gcc -c add.s -o add.o

objdump -S add.o

add.o:     file format elf32-i386


Disassembly of section .text:

00000000 <add>:
#include<stdarg.h>

int add(int n,...)
{
   0:	55                   	push   %ebp
   1:	89 e5                	mov    %esp,%ebp
   3:	83 ec 10             	sub    $0x10,%esp
	va_list ap;
	<span style="color:#CC0000;">va_start(ap,n);
   6:	8d 55 0c             	lea    0xc(%ebp),%edx
   9:	8d 45 f4             	lea    -0xc(%ebp),%eax
   c:	89 10                	mov    %edx,(%eax)</span>


    int temp=0;
   e:	c7 45 f8 00 00 00 00 	movl   $0x0,-0x8(%ebp)
	int i;
	for(i=0;i<n;i++)
  15:	c7 45 fc 00 00 00 00 	movl   $0x0,-0x4(%ebp)
  1c:	eb 12                	jmp    30 <add+0x30>
	<span style="color:#FF0000;">temp+=va_arg(ap,int);
  1e:	8b 45 f4             	mov    -0xc(%ebp),%eax
  21:	8d 50 04             	lea    0x4(%eax),%edx
  24:	89 55 f4             	mov    %edx,-0xc(%ebp)
  27:	8b 00                	mov    (%eax),%eax
  29:	01 45 f8             	add    %eax,-0x8(%ebp)
{</span>
	va_list ap;
	va_start(ap,n);
	int temp=0;
	int i;
	for(i=0;i<n;i++)
  2c:	83 45 fc 01          	addl   $0x1,-0x4(%ebp)
  30:	8b 45 fc             	mov    -0x4(%ebp),%eax
  33:	3b 45 08             	cmp    0x8(%ebp),%eax
  36:	7c e6                	jl     1e <add+0x1e>
	temp+=va_arg(ap,int);
	va_end(ap);
	return temp;
  38:	8b 45 f8             	mov    -0x8(%ebp),%eax
}
  3b:	c9                   	leave  
  3c:	c3                   	ret    

--------------------------------------------------------------------------------------------------------

汇编目前看不出编写思想,换个方向试试

对几个宏的展开

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) //第一个可选参数地址
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //下一个参数地址
#define va_end(ap) ( ap = (va_list)0 ) // 将指针置为无效
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) 
为了测试_INTSIZEOF(n),编写test.c

 cat test.c 
#include<stdio.h>
#include<stdlib.h>
#define _intsizeof(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

int main()
{
	printf("test _intsizeof %d\n",_intsizeof(int));
	printf("test _intsizeof %d\n",_intsizeof(double));
	printf("test _intsizeof %d\n",_intsizeof(char));
	return 0;
}

 ./a.out 
test _intsizeof 4
test _intsizeof 8
test _intsizeof 4

因为这里涉及到内存对其。

测试va_start va_arg va_end,编写c测试代码

#include<stdio.h>
#include<stdlib.h>
typedef char *   va_list;
#define _intsizeof(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))
#define va_arg_o(ap,t)    ( *(t *)((ap += _intsizeof(t)) - _intsizeof(t)) )
<span style="color:#FF0000;">#define va_arg(ap,t,r)    do{ap += _intsizeof(t);  printf("ap3 %p\n",ap); \
			      printf("ap4 %p\n",ap-_intsizeof(t)); \
			   r=*(t*)(ap-_intsizeof(t));  }while(0);</span>
<span style="color:#CC0000;">/*此处将增加一个参数,方便测试地址。_intsizeof(n)是计算当前数据类型的占内存大小*/</span>
<span style="color:#FF0000;">/*在调用va_arg时,ap已经指向新的变参地址,这个宏有两个作用,将ap指向下一个参数地址,同时又返本变参的数*/</span>
#define va_start(ap,v) ( ap = (va_list)&v + _intsizeof(v) )
<span style="color:#FF0000;">/*va_start()是获取最后一个固定参数的地址,然后加上所占内存大小,地址对齐后,正好定位到第一个变参地址*/</span>
#define va_end(ap)    ( ap = (va_list)0 )

int foo(int a,...)
{
	va_list ap;
	printf("parmN %p\n",&a);
	va_start(ap,a);
	printf("ap1 %p\n",ap);
	printf("ap1 %c\n",*(char*)ap);
	char b;
	<span style="color:#330099;">va_arg(ap,char,b);</span>
	printf("ap2 %p\n",ap);
	int  c;
	<span style="color:#330099;">va_arg(ap,int,c);</span>
	printf("ap6 %p\n",ap);
	printf("b %c\n",b);
	printf("c %d\n",c);
	va_end(ap);
}

int foo_o(int a,...)
{
	va_list ap;
	printf("parmN %p\n",&a);
	va_start(ap,a);
	printf("ap1 %p\n",ap);
	printf("ap1 %c\n",*(char*)ap);
	char b;
	b=va_arg_o(ap,char);
	printf("ap2 %p\n",ap);
	int  c;
	c=va_arg_o(ap,int);
	printf("ap6 %p\n",ap);
	printf("b %c\n",b);
	printf("c %d\n",c);
	va_end(ap);
}
int main()
{
	printf("test _intsizeof %d\n",_intsizeof(int));
	printf("test _intsizeof %d\n",_intsizeof(double));
	printf("test _intsizeof %d\n",_intsizeof(char));
	
	foo(1,'b',3);
	printf("---------------------\n");
	foo_o(1,'b',3);
	return 0;
} 

运行

./a.out 
test _intsizeof 4
test _intsizeof 8
test _intsizeof 4
parmN 0xbf878180
ap1 0xbf878184
ap1 b
ap3 0xbf878188
ap4 0xbf878184
ap2 0xbf878188
ap3 0xbf87818c
ap4 0xbf878188
ap6 0xbf87818c
b b
c 3
---------------------
parmN 0xbf878180
ap1 0xbf878184 <span style="color:#000066;">-->av_start之后</span>
ap1 b
ap2 0xbf878188 <span style="color:#000066;"><span style="background-color: rgb(255, 255, 255);">-->av_arg之后</span></span>
ap6 0xbf87818c <span style="color:#000066;">-->av_arg之后</span>
b b
c 3



----------------------------参考资料----------------

http://www.tuicool.com/articles/uaqmQbm

http://www.cnblogs.com/diyunpeng/archive/2010/01/09/1643201.html(十分推荐)

更多相关文章

  1. linux 下命令行传参数问题
  2. 自己实现的C++智能指针的功能代码和测试用例
  3. linux 输入参数利用getopt、struct option、getopt_long、getopt
  4. 使用Bash编写Linux Shell脚本-9. 参数和子壳
  5. linux-参数-argparse模块-(未完待续)
  6. 在PreparedStatement中重用参数?
  7. LINUX下用SHELL脚本执行带输入输出参数的ORACLE存储过程并得到结
  8. C#中操作Oracle时的SQL语句参数的用法
  9. 彻底理解初始化参数SERVICE_NAMES和客户端TNS中的SERVICE_NAME

随机推荐

  1. php命令行下相对路径问题的解决方法
  2. php的use和require的区别
  3. php实现简单MVC
  4. PHP数组合并之array_merge和数组相加
  5. PHP操作数据库
  6. php代码连不上mysql
  7. php final关键字的应用
  8. php错误屏蔽
  9. PHP开发APP接口全过程(二)
  10. 30 个 php 操作 redis 常用方法代码示例