前面从编译链接的角度从新理解了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界定。
void va_start ( va_list ap, prev_param );
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
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(十分推荐)