作业帮 > 综合 > 作业

编写print函数以取代标准库函数printf,在print函数中不许使用printf函数.(急)

来源:学生作业帮 编辑:大师作文网作业帮 分类:综合作业 时间:2024/11/11 10:09:18
编写print函数以取代标准库函数printf,在print函数中不许使用printf函数.(急)
编写print函数以取代标准库函数printf,在print函数中不许使用printf函数.(急)
在嵌入式系统中,串口常用来辅助调试输出一些调试信息.所以有必要实现串口的格式化输出功能,这可以由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; /* End of string */
if (c != '%') { /* Non escape cahracter */
sendchar(c);
cc=1; //cc保存当前循环发送的数据
continue;
}
w = f = 0; //f为格式 ,w为宽度
c = *str++;
if (c == '0') { /* Flag: '0' padding */
f = 1;
c = *str++; //等于c = *(str++);先取值,最后str++ ;f的第0位代表用0填充对齐 c = *(str++);
}
while (c >= '0' && c = 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;
/*
* an array type suitable for holding information needed by the macro va_arg
* and the function va_end. The called function shall declare a variable
* (referred to as ap) having type va_list. The variable ap may be passed as
* an argument to another function.
* Note: va_list is an array type so that when an object of that type
* is passed as an argument it gets passed by reference.
*/
#define va_arg(ap, type) __va_arg(ap, type)
/*
* The va_arg macro expands to an expression that has the type and value of
* the next argument in the call. The parameter ap shall be the same as the
* va_list ap initialised by va_start. Each invocation of va_arg modifies
* ap so that successive arguments are returned in turn. The parameter
* 'type' is a type name such that the type of a pointer to an object that
* has the specified type can be obtained simply by postfixing a * to
* 'type'. If type is a narrow type, an error is given in strict ANSI
* mode, or a warning otherwise. If the type is an array or function type,
* an error is given.
* In non-strict ANSI mode, 'type' is allowed to be any expression.
* Returns: The first invocation of the va_arg macro after that of the
* va_start macro returns the value of the argument after that
* specified by parmN. Successive invocations return the values of
* the remaining arguments in succession.
* The result is cast to 'type', even if 'type' is narrow.
*/
#define va_end(ap) ((void)(ap))
/*
* The va_end macro facilitates a normal return from the function whose
* variable argument list was referenced by the expansion of va_start that
* initialised the va_list ap. If the va_end macro is not invoked before
* the return, the behaviour is undefined.
* Returns: no value.
*/
使用方法:
在调用参数表之前,定义一个 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; /* End of string */
if (c != '%') { /* Non escape cahracter */
sendchar(c);
cc=1; //cc保存当前循环发送的数据
continue;
}
w = f = 0; //f为格式 ,w为宽度
c = *str++;
if (c == '0') { /* Flag: '0' padding */
f = 1;
c = *str++; //等于c = *(str++);先取值,最后str++ ;f的第0位代表用0填充对齐 c = *(str++);
}
while (c >= '0' && c = 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;
}