硬件平台:jz2440
软件平台:Ubuntu16.04 arm-linux-gcc-3.4.5
源码位置: https://github.com/lian494362816/C/tree/master/2440/009_uart_printf
因为程序目前处于裸板阶段,只能输出字符串,没有C语言的printf函数可以调用。但是在调试程序时,想像C语言一样调用printf来调试,因此只能自己来实现了。
C语言中,printf函数的原型为:
int printf(const char *format, ...);
参数有2个, “const char *format” 和 “…”,这个“…”就是可变参数,下面先讲解一下如何识别这个可变参数。
参数的传递会顺序的放到栈里面,而printf函数可获取的参数只有 “const char *format” 和 “…”,不过format刚好指向了第1个参数的首地址,因此可以通过format地址的移动来找到后续的几个参数。
先通过1个简单的程序了解如何通过format这个参数找到后续的参数。请结合程序下面的图片来理解,必须理解这个程序才可继续往后看,否则会懵逼。
程序是实现自己的printf函数,把传进去的字符串、整数、结构体、字符、浮点数依次打印出来
01_printf.c
#include
struct person{
char *name;
int age;
char score;
int id;
};
int my_printf(const char *format, ...)
{
/* 指针p指向format的地址 */
char *p = (char *)&format;
int arg2 = 0;
struct person arg3;
char arg4 = 0;
double arg5;// must be duoble
/* format指向了第1个参数的首地址
所以直接输出format就可以"abcd" */
printf("arg1:%s\n", format);
/* 第1参数是字符串 "abcd", p的值加上sizeof(char *)就可以移动到第2个参数 */
p += sizeof(char *);
arg2 = *((int *)p);
printf("arg2:%d\n", arg2);
/* 第2个参数是整数 123, p的值加上sizeof(int)就可以移到第3个参数 */
p += sizeof(int);
arg3 = *((struct person *)p);
printf("arg3:name:%s,age:%d,score:%c, id:%d\n", arg3.name, arg3.age, arg3.score, arg3.id);
/* 第3个参数是结构体, p的值加上sizeof(struce person)就可以移到第4个参数 */
p += sizeof(struct person);
arg4 = *((char *)p);
printf("arg4:%c\n", arg4);
/* 第4个参数是字符 C, 这里需要4对齐,所以加上((sizeof(char) + 3) & ~3)
就可以移到第5个参数 */
p += ((sizeof(char) + 3) & ~3);
/* 这里需要转化成double, 否则数据会出错 */
arg5 = *((double *)p);
printf("arg5:%lf\n", arg5);
return 0;
}
int main(int argc, char *argv)
{
struct person per = {"Black", 26, 'A', 53};
//字符串 int 结构体 char double
my_printf("abcd", 123, per, 'C', 4.321);
return 0;
}
01_printf.c 中,获取参数的都分成2个小步骤:
1)通过sizeof来偏移地址,如 p += sizeof(char *);
2)通过指针强制转换来取值, 如arg2 = *((int *)p);
是否有办法将2个步骤合二为一,这里可以通过va_start, va_end, va_arg来实现,所需要的头文件为#include
void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
01_printf.c 中,一开始我们用char *p = (char *)&format; 来获得format的首地址。这里可以通过
va_start 来代替。
1)先定义va_list 变量:va_list p;
2)将format和定义好的va_list p带入va_start: va_start(p, format);
char *p = (char *)&format 就等价于 va_start(p, format);
不过va_start事实上还会把p的值指向下一个参数,所以va_start等于做了2个步骤
1)char *p = (char *)&format
2)p += sizeof(char *);
获取第2个参数的2个小步骤可由va_arg来代替:
1)p += sizeof(char *);
2)arg2 = *((int *)p);
等价于 arg2= vs_arg(p, int);
不过对于第2个参数来说,地址的偏移是由va_start(p, format)完成了,所以并不需要再做p += sizeof(char *)了,而是把地址偏移到下个参数。所以vs_arg(p, int) 实际等价于:
1)arg2 = *((int *)p);
2)p += sizeof(int);
va_star和va_arg的函数可能有点绕,但总结起来就是2个功能:
1)获取当前参数的值
2)把地址偏移到下1个参数
调用va_start(p, format)时,p已经指向到了第2个参数的位置(第1个参数就是format)
调用arg2= vs_arg(p, int)时,p已经指向到了第3个参数的位置
下面贴出整体替换的代码,自己好好分析一下
03_printf.c
#include
#include
struct person{
char *name;
int age;
char score;
int id;
};
//use va_list va_start va_arg va_end
int my_printf(const char *format, ...)
{
va_list p;
int arg2 = 0;
struct person arg3;
char arg4 = 0;
double arg5;// must be duoble
//arg1
printf("arg1:%s\n", format);
//char *p = (char *)&format;
//p += sizeof(char *);
va_start(p, format);
//arg2
//arg2 = *((int *)p);
//p += sizeof(int);
arg2 = va_arg(p, int);
printf("arg2:%d\n", arg2);
//arg3
//arg3 = *((struct person *)p);
//p += sizeof(struct person);
arg3 = va_arg(p, struct person);
printf("arg3:name:%s,age:%d,score:%c, id:%d\n", arg3.name, arg3.age, arg3.score, arg3.id);
//arg4
//arg4 = *((char *)p);
//p += ((sizeof(char) + 3) & ~3);
arg4 = va_arg(p, int); //must be int, because alignment is 4
printf("arg4:%c\n", arg4);
//arg5
//arg5 = *((double *)p);
arg5 = va_arg(p, double);
printf("arg5:%lf\n", arg5);
va_end(p);
return 0;
}
int main(int argc, char *argv)
{
struct person per = {"Black", 26, 'A', 53};
my_printf("abcd", 123, per, 'C', 4.321);
return 0;
}
由于跑裸板程序时,并没有C库可以调用,所以va_star, va_arg, va_end这3个函数需要自己实现。在网上收到这3个函数的原型,原来全是宏,下面对这些宏进行分析说明
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 )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ):
为了实现sizeof(int)对齐也就是4对齐,所以才有(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) 这一串操作。就等价于实现了4对齐的一个sizeof宏。
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ):
如同前面说的,va_start实现了把ap偏移到下1个参数的功能。
这里先讲1个前提知识,请看下面test.c的代码,最终A的值是2。如果是#define A (1, 2, 3) 那么最终A的值会是3,C语言就有这么一个小规则。
test.c
#include
#define A (1,2)
int main(int argc, char *argv)
{
printf("A=%d\n", A);
return 0;
}
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ):
这个宏实现了2个功能,第1就是参数强制转换成对应的类型,第2就是将地址偏移到下1个参数。
*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t))可以拆开成2个步骤:
ap = ap + _INTSIZEOF(t)
*(t *)(ap - _INTSIZEOF(t))
将这2个步骤带到va_arg(ap,t)中
#define va_arg(ap,t) (ap = ap + _INTSIZEOF(t), *(t *)(ap - _INTSIZEOF(t)))
ap = ap + _INTSIZEOF(t) 把ap地址偏移到了下1个参数
*(t *)(ap - _INTSIZEOF(t)) 把ap地址减去_INTSIZEOF(t)再强制转换,此时ap还是偏移在下1个参数不过 *(t *)(ap - _INTSIZEOF(t)) 整体作为了宏的值
还可以写成这样
#define va_arg(ap,t) (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
__out_putchar©函数就是就是putchar,也就是前篇文章实现的函数
https://blog.csdn.net/lian494362816/article/details/85083263
最后printf函数的实现我就不讲解了,自己看看源码分析分析。
#define __out_putchar putchar
int putchar(int c)
{
while (!(UTRSTAT0 & 0x4))
{
//nothing
}
UTXH0 = (unsigned char )c;
}
my_printf.c
#include "my_printf.h"
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_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
static unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',
'8','9','a','b','c','d','e','f'};
static int outc (int c)
{
__out_putchar(c);
return 0;
}
static int outs (const char *s)
{
while(*s != '\0')
{
__out_putchar(*s++);
}
return 0;
}
static int out_num(long n, int base, char lead, int maxwidth)
{
unsigned long num = 0;
char buf[MAX_NUMBER_BYTES];
char *s = buf + sizeof(buf);
int count = 0;
int i = 0;
*--s = '\0';
num = n;
if(n < 0)
{
num = -n;
}
do{
*--s = hex_tab[num % base];
count ++;
}while((num /= base) != 0);
if (maxwidth && count < maxwidth)
{
for (i = maxwidth - count; i; i--)
{
*--s = lead;
}
}
if(n < 0)
{
*--s = '-';
}
outs(s);
return 0;
}
static int my_vprintf(const char *fmt, va_list ap)
{
char lead = ' ';
int maxwidth = 0;
for (; *fmt != '\0'; fmt++)
{
if (*fmt != '%')
{
outc(*fmt);
continue;
}
/*format : %08d, %8d,%d,%u,%x,%f,%c,%s*/
fmt++;
if ('0' == *fmt)
{
lead = '0';
fmt ++;
}
lead = ' ';
maxwidth = 0;
while(*fmt >= '0' && *fmt <= '9')
{
maxwidth += (*fmt - '0');
fmt ++;
}
switch(*fmt)
{
case 'd':
out_num(va_arg(ap, int), 10, lead, maxwidth);
break;
case 'o':
out_num(va_arg(ap, unsigned int), 8, lead, maxwidth);
break;
case 'u':
out_num(va_arg(ap, unsigned int), 10, lead, maxwidth);
break;
case 'x':
out_num(va_arg(ap, unsigned int), 16, lead, maxwidth);
break;
case 'c':
outc(va_arg(ap, int));
break;
case 's':
outs(va_arg(ap, char *));
break;
default:
outc(*fmt);
break;
}
}
return 0;
}
int printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}