s3c2440学习之路-008 uart实现printf函数

  1. 基本原理
  2. 源码

硬件平台:jz2440
软件平台:Ubuntu16.04 arm-linux-gcc-3.4.5
源码位置: https://github.com/lian494362816/C/tree/master/2440/009_uart_printf

1.基本原理

因为程序目前处于裸板阶段,只能输出字符串,没有C语言的printf函数可以调用。但是在调试程序时,想像C语言一样调用printf来调试,因此只能自己来实现了。

C语言中,printf函数的原型为:

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

1.1 可变参数"…"

参数有2个, “const char *format” 和 “…”,这个“…”就是可变参数,下面先讲解一下如何识别这个可变参数。
参数的传递会顺序的放到栈里面,而printf函数可获取的参数只有 “const char *format” 和 “…”,不过format刚好指向了第1个参数的首地址,因此可以通过format地址的移动来找到后续的几个参数。

先通过1个简单的程序了解如何通过format这个参数找到后续的参数。请结合程序下面的图片来理解,必须理解这个程序才可继续往后看,否则会懵逼。

程序是实现自己的printf函数,把传进去的字符串、整数、结构体、字符、浮点数依次打印出来

01_printf.c

#include <stdio.h>

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;
}

在这里插入图片描述

在这里插入图片描述

1.2 获取地址的替代函数

01_printf.c 中,获取参数的都分成2个小步骤:
1)通过sizeof来偏移地址,如 p += sizeof(char *);
2)通过指针强制转换来取值, 如arg2 = *((int *)p);

是否有办法将2个步骤合二为一,这里可以通过va_start, va_end, va_arg来实现,所需要的头文件为#include <stdarg.h>

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 <stdio.h>
#include <stdarg.h>

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;
}

1.3 arg_xx的函数原型分析

由于跑裸板程序时,并没有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 <stdio.h>
#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)))

2.源码

__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;

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值