摘要
1.my_printf函数的学习
2.x86平台下printf函数的实现
3.arm平台下printf函数的实现
1.my_printf函数
相关的文件: my_printf.c; my_printf.h
printf函数规则(此处使用是自己定义,并非stdio.h中直接引用)
1.未遇到%,直接输出字符,如num=,
2.遇到%,处理格式字符
my_printf.c:
#include "my_printf.h"
//==================================================================================================
typedef char * va_list;
/* va_start,va_arg, va_end,宏的用法在可变参数函数中有具体介绍*/
#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 )
//==================================================================================================
unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
'8','9','a','b','c','d','e','f'};
/* __out_putchar是宏定义putchar
实际会调用uart.c中的putchar函数来输出字符 */
static int outc(int c)
{
__out_putchar(c);
return 0;
}
/* 输出字符串 */
static int outs (const char *s)
{
while (*s != '\0')
__out_putchar(*s++);
return 0;
}
/* 根据数字n, 进制base,前导码lead,最大长度maxwidth,构造需要得到的字符串 */
static int out_num(long n, int base,char lead,int maxwidth)
{
unsigned long m=0;
/* 数字字符串的长度最大为MAX_NUMBER_BYTES=64个 */
/* s指向buf数组的最后 */
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
int count=0,i=0;
*--s = '\0';
/* 传入负值时取绝对值,最后在前面输出'-' */
if (n < 0){
m = -n;
}
else{
m = n;
}
/* 根据进制得到每一位,存放在s中 */
/* 权重低的会先存放在s中,存放完成之后s-- */
do{
*--s = hex_tab[m%base];
count++; /* 计算数字的个数 */
}while ((m /= base) != 0);
/* 根据数字的最大长度设置前导码 */
if( maxwidth && count < maxwidth){
for (i=maxwidth - count; i; i--)
*--s = lead;
}
if (n < 0)
*--s = '-';
/* 输出数字字符串 */
return outs(s);
}
/*reference : int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap)
{
char lead=' '; /* 前导码默认是空格 */
int maxwidth=0; /* 字符串的最大长度 */
/* 在fmt字符串中寻找到%符号 */
for(; *fmt != '\0'; fmt++)
{
if (*fmt != '%') {
outc(*fmt);
continue;
}
lead=' ';
maxwidth=0;
//format : %08d, %8d,%d,%u,%x,%f,%c,%s
fmt++; /* fmt指向%的下一个字符 */
/* 判断前导码是不是'0' */
if(*fmt == '0'){
lead = '0';
fmt++;
}
/* 设置最大输出长度 */
while(*fmt >= '0' && *fmt <= '9'){
maxwidth *=10;
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;
}
//reference : int printf(const char *format, ...);
int printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}
int my_printf_test(void)
{
printf("This is www.100ask.org my_printf test\n\r") ;
printf("test char =%c,%c\n\r", 'A','a') ;
printf("test decimal number =%d\n\r", 123456) ;
printf("test decimal number =%d\n\r", -123456) ;
printf("test hex number =0x%x\n\r", 0x55aa55aa) ;
printf("test string =%s\n\r", "www.100ask.org") ;
printf("num=%08d\n\r", 12345);
printf("num=%8d\n\r", 12345);
printf("num=0x%08x\n\r", 0x12345);
printf("num=0x%8x\n\r", 0x12345);
printf("num=0x%02x\n\r", 0x1);
printf("num=0x%2x\n\r", 0x1);
printf("num=%05d\n\r", 0x1);
printf("num=%5d\n\r", 0x1);
return 0;
}
my_printf.h:
#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H
#include "uart.h"//在x86平台时注释掉,因为直接用main调用
#include <stdio.h>//由于要用到底层的putchar,在arm平台时注释,保留上一句,因为uart.h中有定义putchar
#define __out_putchar putchar
#define MAX_NUMBER_BYTES 64
extern int my_printf_test(void);
int printf(const char *fmt, ...);
#endif /* _MY_PRINTF_H */
2.x86平台下printf函数的实现
相关的文件:main.c ; my_printf.c; my_printf.h
#include <stdio.h>
#include "my_printf.h"
int main(int argc,char **argv)
{
my_printf_test();
return 0;
}
编译运行:
3.arm平台下printf函数的实现
在在串口编程目录下添加以下三个文件:
添加后
新建source insight工程,并添加以上文件,并修改main函数和makefile
注意: 由于目前只用了4K的RAM,还没有学习使用内存,因此在编译出printf.bin文件之后会发现文件远大于4K
查看反汇编代码可以发现,只读数据段与数据段之间差别较大,因此需要在Makefile中强制指定数据段的地址,根据自己实际的反汇编确定即可
/* Makefile */
# 注意: 由于目前只用了4K的RAM,还没有学习使用内存,因此在编译出printf.bin文件之后会发现文件远大于4K
# 查看反汇编代码可以发现,只读数据段与数据段之间差别较大,因此需要在Makefile中强制指定数据段的地址,根据自己实际的反汇编确定即可
objs = uart.o main.o start.o lib1funcs.o my_printf.o
A = printf
all:$(objs)
arm-linux-ld -Ttext 0 -Tdata $^ -o $(A).elf #指定了代码段和数据段的地址
arm-linux-objcopy -O binary -S $(A).elf $(A).bin
arm-linux-objdump -D $(A).elf > $(A).dis
%.o:%.c
arm-linux-gcc -c -o $@ $<
%.o:%.S
arm-linux-gcc -c -o $@ $<
clean:
rm *.o *.elf *.bin