【详解】【四】制作一个与x86平台标准printf()功能相同的arm平台裸机printf()函数——printf()函数的正确建立方法和调用方式

四、(标准输出函数)printf()函数的正确建立方法和调用方式——从字符串/字符指针(所指向的字符为字符串首字符)作为printf()函数的参数的正确传递方法和使用格式入手
难点:1)递归调用的函数较多,函数间的
传递 参数的关系容易判断出错
说明:1)字符串在函数间的传递正确方式为:
          printf("This is www.100ask.org\n\r");
          puts("wakaka!\n\r");
          2)用到的函数原型:
          int printf(const char * format, ...);

          int puts(const char * s);

结论:
1、函数printf()常用的调用方法有:
1)static int my_printf(const char * fmt, va_list p); 例如:printf("char * s = %s\n\r", s);
2)printf("Test string = %s\n\r", "www.100ask.org");
2、函数printf()递归调用函数puts()的最终正确调用方法为:
1) puts(va_arg(p, char *));
或  2) puts(*(char **)p);

p += _INTSIZEOF(char *);

1、函数的递归调用的逻辑图如下:

 
 
/*======================================================================*/
探究正确的printf()函数使用方法,解析原因
使用情景:
static int my_printf(const char * fmt, va_list p)
{
	for(; *fmt; fmt++)
	{
		if(*fmt != '%')
		{
			putchar(*fmt);
			continue;
		}
		fmt++;							//此时,字符指针fmt指向格式符d/o/x/u/c/s;
		switch(*fmt)
		{
		case 's':	//递归调用函数puts();
		正确写法:  1) puts(va_arg(p, char *));
				或  2)	puts(*(char **)p);		
						p += _INTSIZEOF(char *);
		错误写法:	1)puts(p);
					2)puts((char *)p);时:→ Test string = p;
					3)puts(*(char *)p);
					4)puts(*p);		//警告:通过“puts”的arg 1可以使指针从整数中脱离出来。
					末尾添加:p += _INTSIZEOF(char *);
		}
	}
	return 0;
}
int printf(const char * format, ...)
{
	va_list p;
	va_start(p, format);	//指针p是可以指向【固定参数字符指针format和一众可变参数】的指针;
	my_printf(format, p);	//此时指针p为(char *)类型,指向printf()函数的第一个可变参数;
	va_end(p);
        return 0;
}
int my_printf_test(void)
{
	char * s = "Hello world!";
	printf("char * s = %s\n\r", s);
	printf("Test string	= %s\n\r",	"www.100ask.org");
        return 0;
}
//========================================================================

2、试数及论证过程如下:
【操作一:】    char * s = "Hello world!";
                        printf("char * s = %s\n\r", s);
解析:递归调用:函数my_printf_test()调用函数printf()函数,函数printf()调用my_printf(),
而函数my_printf调用函数puts(),函数puts()调用最底层函数putchar();
0、调用一级主函数【int main(void)】,主函数main()内,
执行:my_printf_test();
1、调用二级函数【int my_printf_test(void)】,函数my_printf_test()内,
执行:char * s = "Hello world!";    此时,定义字符指针char * s,并使其指向字符串"Hello world!"的首字符'H'; 
执行:printf("char * s = %s\n\r", s);    把指针变量s的值作为实参传递给函数printf()的第一个可变参数;
2、调用三级函数【int printf(const char * format, ...)】,函数printf()内部,
执行:va_list p;va_start(p, format);
定义固定参数字符指针format指向字符串"char * s = %s\n\r"; 定义字符指针char *p并操作其指向第一个可变参数参数s; 
执行:my_printf(format, p);
把字符指针format的值作为1号实参传递给函数my_printf();  指针p的值作为2号实参传递给函数my_printf();
3、调用四级函数【static int my_printf(const char * fmt, va_list p)】函数my_printf()内部,
定义1号形参const char * fmt = (三级printf()函数)format = (二级my_printf_test()函数)"char * s = %s\n\r";
1)调用最底层函数putchar()打印字符串内的每一个字符,当遇到格式符时,根据switch()选择语句执行相应的分支语句;
2)但各分支选择语句的相同作用都是调用puts()函数打印"Hello world!",
即: puts("Hello world!");
puts(s);
puts(*p);其中,p为本级函数可变参数p;
定义2号形参char* p = (三级printf()函数指针)p = (二级my_printf_test()函数字符指针)s = "Hello world!";
4、调用五级函数【int puts(const char * s);】函数puts()调用情况:略
5、调用六级最底层函数putchar(),略


【操作二:】    printf("Test string = %s\n\r", "www.100ask.org");
解析:在调用三级函数【int printf(const char * format, ...);】时,执行函数printf()的结果依然是
定义固定参数字符指针format指向字符串"char * s = %s\n\r"; 定义字符指针char *p并操作其指向第一个可变参数参数s;
其余步骤,与【一】等同。

问题1:操作:printf("char * s = %s\n\r", s); 是否就是操作:printf("char * s = %s\n\r", "Hello world!\n\r"); 答:是的

3、测试程序如下:

/*
2018-05-31
File:	my_printf.c
功能:
制作一个用于arm裸机平台的printf()函数,要求具备x86平台printf()函数的以下功能:
1)单纯的字符或字符串打印
2)可变参数----整型数据的格式打印:
			例程				%#_
	%d:		i = 377;			377;
	%o:		i = 571;			0571;
	%u: 	i = 377;			377;
	%x(小写):i = 179;			0x179;
	//%p:		p = 0018FF44;
	//%#p		p = 0X0018FF44;
	%c
	%s
3)可变参数----字符和字符串的打印
4)整型数据变量的格式打印
5)字符和字符串变量的打印
不考虑:
不考虑浮点数和结构体变量的输出
整型数据的格式不要前导码和最大宽度
*/
#include "s3c2440_soc.h"
#include "uart.h"
//==================================================================
#define 	_MAX_NUMBER_BYTE	64
//==================================================================
typedef 	char * 			va_list;
#define		_INTSIZEOF(n)	((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#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)

//==================================================================
unsigned char hex_tab[] = "0123456789abcdef";

/*函数:整型数据打印函数
 *方法及步骤:1)把原整型数据n,按照进制,转换成整形数据n
 *2)把数字m的位当作字符串倒着打印,打印顺序:符号位 → 最高位 → 次高位 → ··· → 十位 → 个位;
 */
static int out_num(long n, int base)		//形参为:1)整型数据n 2)进制base
{
	unsigned int m, unit;					//m为n的绝对值,unit数字n的位数字
	char buf[_MAX_NUMBER_BYTE];
	char * p = buf + sizeof(buf);			//数组buf[]的长度是否一定是按照四字节对齐?是否对齐,buf[]都代表整型数据的最大宽度,无影响
	*--p = '\0';
	
	if(n < 0)
		m = -n;
	else
		m = n;
	
	do
	{
		unit = m%base;				//*和--运算符虽然是同一优先级,但是结合方向为从右到左
		*--p = hex_tab[unit];				//把数字m一位一位塞进字符数组buf[]中
		m = m/base;
		//putchar(hex_tab[unit]);	//此语句会导致字符串倒着打印
	}while(m);
	if(n < 0)
		*--p = '-';
	
	return puts(p);
}

/*自制arm平台简易printf()函数主体部分*/
static int my_printf(const char * fmt, va_list p)	//指针p是可以指向【固定参数字符指针format和一众可变参数】的指针
{
	for(; *fmt; fmt++)
	{
		if(*fmt != '%')
		{
			putchar(*fmt);
			continue;
		}
		fmt++;							//此时,字符指针fmt指向格式符d/o/x/u/c/s;
		switch(*fmt)
		{
		case 'd':
				out_num(va_arg(p, int), 10);
				break;
		case 'o':
				out_num(va_arg(p, unsigned int), 8);
				break;
		case 'x':
				out_num(va_arg(p, unsigned int), 16);
				break;
		case 'u':
				out_num(va_arg(p, unsigned int), 10);
				break;
		case 'c':
				putchar(va_arg(p, int));
				break;
		case 's':						//puts(va_arg(p, char *));
				puts(*(char **)p);		//当puts(p);或puts((char *)p);时:→ Test string = p;
				//puts(*(char *)p);		//警告:传递“puts”的arg1使指针从整数变为无类型=====//
				p += _INTSIZEOF(char *);
				break;
		default:
				putchar(*(fmt - 1));
				putchar(*fmt);			//如果不是以上格式符,判定此格式符和之前的'%'无效,作为普通字符打印
				break;
		}
	}
	
	return 0;
}

/*自制arm平台简易printf()函数框架*/
int printf(const char * format, ...)
{
	va_list p;
	va_start(p, format);				//指针p是可以指向【固定参数字符指针format和一众可变参数】的指针
	my_printf(format, p);
	va_end(p);
	
	return 0;
}

/*自制arm平台简易printf()函数的测试程序*/
int my_printf_test(void)
{
	int i = 377, j = -528;
	char a = 'W';
	char * s = "Hello world!\n\r";
	int * p = &i;

	//1.纯数字、字符、字符串测试
/*	printf("//======================================\n\r");
	printf("This if www.100ask.org:	my_printf_test\n\r");
	
	printf("Test 正十进制→十进制		num = %d\n\r", 123456);
	printf("Test 正十进制→八进制 		num = %o\n\r", 123456);
	printf("Test 正十进制→十六进制	  	num = %x\n\r", 123456);
	printf("Test 负十进制→十进制		num	= %d\n\r", -123456);
	printf("Test 负十进制→无符号十进制	num	= %u\n\r", -123456);
	printf("Test 负十进制→十六进制		num	= %x\n\r", -123456);
	
	printf("Test 十六进制→十进制		num = %d\n\r", 0x55aa56ff);
	printf("Test 十六进制→十六进制		num = %x\n\r", 	0x55aa56ff);
	
	printf("Test char				= %c, %c\n\r",  'A', 'a');
	printf("Test string				= %s\n\r",		"www.100ask.org");
*/	
	//多组数据打印
	printf("num1 = %d, num1 = %d\n\r", 0x12, 0x23);
	printf("//======================================\n\r");
	
	//2、变量测试
	printf("int i = %d\n\r", i);
	printf("int j = %d\n\r", j);
	printf("char a = %c\n\r", a);
	printf("char * s = %s\n\r", s);
	printf("int * p = %p\n\r", p);
	//多组变量打印
	printf("i = %d, j = %d\n\r", i, j);
	printf("//======================================\n\r");

/**/	
	return 0;
}
/*
1、打印结果:
Wakakaka
//======================================
This if www.100ask.org: my_printf_test
Test 正十进制→十进制            num = 123456
Test 正十进制→八进制            num = 361100
Test 正十进制→十六进制          num = 1e240
Test 负十进制→十进制            num     = -123456
Test 负十进制→无符号十进制      num     = -123456
Test 负十进制→十六进制          num     = -1e240
Test 十六进制→十进制            num = 1437226751
Test 十六进制→十六进制          num = 55aa56ff
Test char                               = A, a
Test string                             = www.100ask.org
num1 = 18, num1 = 35
//======================================
int i = 377
int j = -528
char a = W
char * s = Hello world!

int * p = %p
i = 377, j = -528
//======================================
2、打印结果:

*/


其他程序如:start.S led.c  uart.c  lib1funcs.S   main.c  uart.h  my_printf.h  s3c2440soc.h 见本系列【一】

猜你喜欢

转载自blog.csdn.net/weixin_39420903/article/details/80539434