printf函数的重定向实现
(2014-09-05 13:50:29)
标签:
佛学
在嵌入式系统中,串口常用来辅助调试输出一些调试信息。所以有必要实现串口的格式化输出功能,这可以由3种方法实现(就我目所知).
1)使用系统库函数printf(),这就需要重载输入,输出函数int fputc(int ch, FILE *f);int fgetc(FILE *f).
2)使用sprintf()函数将数据格式化到数组,然后将数组输出.也可以使用vsprintf().
3)自己写类似printf()函数.
这里假设已经编写了基本的输入输出函数如下:
int sendchar(int ch); // 发送字符
int sendstr(char *str);//发送字符窜
int getkey(void); // 接受字符
1)第一种方法的实现
比较简单只要实现以下2个函数:
int fputc(int ch, FILE *f) {
return (sendchar(ch));
}
int fgetc(FILE *f) {
return (getkey());
}
使用方法:
#include
void main()
{
...
printf("%s,%d,%x","hello",0x10,0x10);//输出: hello,16,10
...
}
2)第二种方法的实现
void Uart_Printf(const char *fmt,...)
{
va_list ap;
char string[256];
va_start(ap,fmt);
vsprintf(string,fmt,ap);
Uart_SendString(string);
va_end(ap);
}
3)第三种方法的实现
int myprintf (const char* str,...)
{
va_list arp;
uint8 c, f, r;
ULONG val;
char s[16];
int i, w, res, cc;
va_start(arp, str);
for (cc = res = 0; cc != EOF; res += cc) {
c = *str++;
if (c == 0) break;
if (c != '%') {
sendchar(c);
cc=1; //cc保存当前循环发送的数据
continue;
}
w = f = 0; //f为格式 ,w为宽度
c = *str++;
if (c == '0') {
f = 1;
c = *str++; //等于c = *(str++);先取值,最后str++ ;f的第0位代表用0填充对齐 c = *(str++);
}
while (c >= '0' && c <= '9') {
w = w * 10 + (c - '0'); //计算对齐宽度
c = *str++;
}
if (c == 'l' || c=='L') {
f |= 2; c = *str++; //f的第二位代表Long前缀
}
if (c == 's' || c == 'S') {
cc = sendstr(va_arg(arp, char*));//发送字符窜,cc=放松字符个数
continue;
}
if (c == 'c' || c == 'C') {
sendchar(va_arg(arp, int)); //char 改 int
cc = 1;
continue;
}
r = 0;
if (c == 'd' || c =='D') r = 10; //r代表进制
if (c == 'u' || c == 'U') r = 10;
if (c == 'X' || c == 'x') r = 16;
if (r == 0) break;
if (f & 2) {
val = (ULONG)va_arg(arp, long); //获取Long型参数,arp指向下一个参数
} else { //没有'l'标志
val = (c == 'd') ? (ULONG)(long)va_arg(arp, int) : (ULONG)va_arg(arp, unsigned int);
//不管是int 还是 unsigned int 都转化为 unsigned long
}
if (c == 'd') {//如果是'd'前缀就要判断刚获取的参数是正还是付
if (val >= 0x80000000) { //最高位为1说明是负的
val = 0 - val;
f |= 4;//f的第三位代表"+/-"
}
}
i = sizeof(s) - 1; s[i] = 0;//i=15
do {
c = (uint8)(val % r + '0');//r代表进制
if (c > '9') c += 7; //对于16进制 转换到对应的'abc...'需要+7 :'\58'+7='65'='a'
s[--i] = c; //从后面开始保存
val /= r;
} while (i && val);
if (i && (f & 4)) s[--i] = '-';//是负数添加'-'号
w = sizeof(s) - 1 - w;
while (i && i > w) s[--i] = (f & 1) ? '0' : ' ';
cc = sendstr(&s[i]);
}
va_end(arp);
return (cc == EOF) ? cc : res;
}
变参数表的调用形式以及原理:
参数在内存中存放格式:大多数情况堆栈是从高到低生长,函数参数入栈时后面的参数先入栈。参数弹出顺序与入栈顺序相反。
举个例子如下:
void func(int x, float y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
下面是MDK 里面重要的几个宏定义如下:
typedef struct __va_list { void *__ap; } va_list;
#define va_arg(ap, type) __va_arg(ap, type)
#define va_end(ap) ((void)(ap))
使用方法:
在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。
va_list的"等价"替换:
va_list arp; <==> {char *arp = 0;}
va_start(arp, str);<==>{ arp = (char *)&str; arp += sizeof(str); }
sendstr(va_arg(arp, type));<==> { sendstr( *(type*)arp); arp += sizeof(type);}
va_end(arp); <==>{ arp = 0;}
因此上述函数可以改为:
int myprintf ( const char* str, ...)
{
//va_list arp;
char *arp = 0;
uint8 c, f, r;
ULONG val;
char s[16];
int i, w, res, cc;
//va_start(arp, str);
arp = (char *)&str;
arp += sizeof(str);
for (cc = res = 0; cc != EOF; res += cc) {
c = *str++;
if (c == 0) break;
if (c != '%') {
sendchar(c);
cc=1; //cc保存当前循环发送的数据
continue;
}
w = f = 0; //f为格式 ,w为宽度
c = *str++;
if (c == '0') {
f = 1;
c = *str++; //等于c = *(str++);先取值,最后str++ ;f的第0位代表用0填充对齐 c = *(str++);
}
while (c >= '0' && c <= '9') {
w = w * 10 + (c - '0'); //计算对齐宽度
c = *str++;
}
if (c == 'l' || c=='L') {
f |= 2; c = *str++; //f的第二位代表Long前缀
}
if (c == 's' || c == 'S') {
//cc = sendstr(va_arg(arp, char*));//发送字符窜,cc=放松字符个数
sendstr( *(char **)arp);
arp += sizeof(char *);
continue;
}
if (c == 'c' || c == 'C') {
// sendchar(va_arg(arp, int)); //char 改 int
sendchar(*((int *)arp));
arp += sizeof(int);
cc = 1;
continue;
}
r = 0;
if (c == 'd' || c =='D') r = 10; //r代表进制
if (c == 'u' || c == 'U') r = 10;
if (c == 'X' || c == 'x') r = 16;
if (r == 0) break;
if (f & 2) {
//val = (ULONG)va_arg(arp, long); //获取Long型参数,arp指向下一个参数
val=(ULONG)(*((long *)arp));
arp += sizeof(ULONG);
} else { //没有'l'标志
val = (c == 'd') ? (ULONG)(long)va_arg(arp, int) : (ULONG)va_arg(arp, unsigned int);
//不管是int 还是 unsigned int 都转化为 unsigned long
}
if (c == 'd') {//如果是'd'前缀就要判断刚获取的参数是正还是付
if (val >= 0x80000000) { //最高位为1说明是负的
val = 0 - val;
f |= 4;//f的第三位代表"+/-"
}
}
i = sizeof(s) - 1; s[i] = 0;//i=15
do {
c = (uint8)(val % r + '0');//r代表进制
if (c > '9') c += 7; //对于16进制 转换到对应的'abc...'需要+7 :'\58'+7='65'='a'
s[--i] = c; //从后面开始保存
val /= r;
} while (i && val);
if (i && (f & 4)) s[--i] = '-';//是负数添加'-'号
w = sizeof(s) - 1 - w;
while (i && i > w) s[--i] = (f & 1) ? '0' : ' ';
cc = sendstr(&s[i]);
}
//va_end(arp);
arp = 0;
return (cc == EOF) ? cc : res;
}
分享:
喜欢
0
赠金笔
加载中,请稍候......
评论加载中,请稍候...
发评论
登录名: 密码: 找回密码 注册记住登录状态
昵 称:
评论并转载此博文
发评论
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。