210学习日记(4)_printf的实现

来源:互联网 发布:手机自动挂机赚钱软件 编辑:程序博客网 时间:2024/06/05 16:11

210学习日记(4)

--printf的实现

问:什么是可选参数?

答:比如函数int printf(const char * format, ...),那么参数format后面的都是可选参数(注:不包含format),即可以传入可以不传入的参数。

问:C函数是怎么被组织进C程序的?

答:C语言的函数是从下(低地址)向上(高地址)压入堆栈的,如下图所示:

栈底 高地址 

     | .......      

     | 函数返回地址 

     | .......       

     | 函数最后一个参数 

     | ......                        

     | 函数第一个可变参数       <--va_startap指向  

     | 函数最后一个固定参数

| ...... 

     | 函数第一个固定参数  

     栈顶 低地址

问:实现可选参数,需要做些什么呢?

答:解析如下:

1.关键的宏:

typedef int * va_list;  //va_list等价于int *即整型指针,该变量类型应该根据具体的架构(ARMX86)确定

#define va_start(ap, A) (ap = (int *)&(A) + 1) //(int *)&得到A所在的地址,并强制类型转换为int *,然后这   //个地址加上A的大小,则使ap指向第一个可变参数!!

#define va_arg(ap, T)  (*(T *)ap++) //先对指针ap(即地址)进行强制类型转换,转换为该变量实际的类型,   //然后ap(即当前地址)自加1(即加一个类型的大小,指向下一个可变参   //数的地址),这类应该注意的是,先使用后自加,然后取出该地址的值!!

#define va_end(ap)   ((void)0)

辅助理解(结合C函数是怎么被组织进C程序的)

1).va_start(arg_ptr, argN):使参数列表指针arg_ptr指向函数参数列表中的第一个可选参数,说明:argN是位于第一个可选参数之前的固定参数,(或者说,最后一个固定参数,或者说,…之前的一个参数),函数参数列表中参数在内存中的存放顺序与函数声明时的顺序是一致的。如果有一va_test()函数的声明是void va_test(char a, char b, char c, ),则它的固定参数(和在内存中存放的顺序)依次是a,b,c,最后一个固定参数argNc,因此就是va_start(arg_ptr, c)

2).va_arg(arg_ptr, type):返回参数列表中指针arg_ptr所指的参数,返回类型为type,并使指针arg_ptr指向参数列表中的下一个可选参数;

va_end(arg_ptr):清空参数列表,并置参数指针arg_ptr无效。说明:指针arg_ptr被置无效后,可以通过调用va_start()va_copy()恢复arg_ptr。每次调用va_start()va_copy()后,必须得有相应的va_end()与之匹配。参数指针可以在参数列表中随意地来回移动,但必须在va_start() … va_end()之间。

精简版prinf的例子:

typedef int * va_list;

#define va_start(ap, A) (ap = (int *)&(A) + 1)

#define va_arg(ap, T) (*(T *)ap++)

#define va_end(ap) ((void)0)

void putchar_hex(char c) //用于显示一字节的十六进制数,也供函数putint_hex()调用

{

char * hex = "0123456789ABCDEF"; //正确的定义和初始化字符串

//char hex[] = "0123456789ABCDEF"; //错误的定义和初始化字符串

putchar(hex[(c & 0xf0)>>4]); //显示高一位

putchar(hex[(c & 0x0f)>>0]); //显示低一位

}

void putint_hex(int a) 用于显示四字节的十六进制数

{

putchar_hex( (a>>24) & 0xFF ); //显示最高处的一字节的十六进制数

putchar_hex( (a>>16) & 0xFF );

putchar_hex( (a>>8) & 0xFF );

putchar_hex( (a>>0) & 0xFF );

}

char * itoa(int a, char * buf) //显示十进制数

{

int num = a;

int i = 0;

int len = 0;

do 

{

buf[i++] = num % 10 + '0'; //将数字转换成它的ASCII码,存放在字符串数组中

num /= 10;

} while (num); //直到十进制数变为0

buf[i] = '\0'; //给字符串加一个结束标志位

len = i;

for (i = 0; i < len/2; i++) //该循环用于对数组buf中的元素进行倒序排序

{

char tmp;

tmp = buf[i];

buf[i] = buf[len-i-1];

buf[len-i-1] = tmp;

}

return buf; //返回数组的首地址

}

int printf(const char * format, ...)

{

char c;

va_list ap; //定义一个int *型的指针ap

va_start(ap, format); //初始化ap,让它指向第一个可变参数的地址 

while ((c = *format++) != '\0') //开始解析printf函数中的字符串(即第一个固定参数)

{

switch (c) //while中,依次读出字符串中的字符,在这里依依地进行判断和解析

{

case '%': //如果字符是%,即格式申明符号,则解析该格式

c = *format++; //c等于%后的第一个字母

switch (c) //对上述字母进行解析

{

char ch;

char * p;

int a;

char buf[100];

case 'c': //如果是%c,即输出字母

ch = va_arg(ap, int); //获取当前可变参数的值,并指向下一个可变参数

putchar(ch); //调用底层(实际操作硬件的那层)来完成输出结果

break;

case 's': //如果是%s,即输出字符串

p = va_arg(ap, char *);

puts(p);

break;

case 'x': //如果是%x,即以十六进制输出

a = va_arg(ap, int);

putint_hex(a);

break;

case 'd': //如果是%d,即以十进制输出

a = va_arg(ap, int);

itoa(a, buf);

puts(buf);

break;

default:

break;

}

break;

default:

putchar(c); //输出字符串中为普通字符的字符

break;

}

}

return 0;

注意:

在实际给出的代码里面是用wy_printf来代替printf的,目的只是为了让编译通过,当然还有其他的解决办法,详细讲解,请看韦东山的自己写bootloader的视频。

注:

如有问题,请到韦东山LINUX视频讨论群里面,我们一起讨论学习,或者加我QQ317312379

原创粉丝点击