编写适合自己需要的printf()函数

转到这里时做了修改,添加了itoa函数.  by Garfield)

对于嵌入式软件的开发人员而言,“printf调试(printf-debugging)”这个术语描述了将调试字符串从嵌入式目标空闲的串口压出,并在运行于宿主工作站的终端模拟器上显示结果的常见方法。 
出于这个目的,许多程序员更喜欢使用有名的printf() C语言库函数,因为它在将文本输出和数据组合成单个函数调用上具有灵活性。但是,不在嵌入式系统中使用printf()无外乎两个常见的原因:不是因为printf()太慢了,就是因为它太大了。


事实上,很容易就会碰到这些局限性。如果你看一下标准C语言库的规范,原因就很明显了。Printf()必须处理大量的数据格式,包括字符串、字符、(各种长度的有符号和无符号)数字,以及浮点值。此外,格式字符串可以包括用于更改文本对齐、基数、间距、字段宽度和精度的调节器和指示器。很清楚的是,任何支持整个规范的代码都会是冗长和繁重的。

嵌入式系统库的提供商意识到了这个问题,于是提供了只使用整数的printf()实现。这就通过去掉不必要的浮点支持而稍稍改善了这种状况。但是即使如此,这些实现对于许多缺乏内存的嵌入式系统来说仍然太大了。

在你每天的编程工作中,你到底需要多少printf()的规范?可能只需要“%s”、“%d”,以及“%x”?所以还有另一个选择:编写能够满足需要的你自己的最小printf()函数。

这会带来相当大的好处。例如,在最近一个项目里,我替换掉了一个由制造商所提供的printf()库,它需要超过20 KB的内存(这超过了可用内存的一半),而换上了一个小巧的只支持必要特性的自定义版本,它只需要不到500字节的内存空间。

小巧的printf()
  
下面是替换一个非常基本的printf()的例子:

/*反转字符数组s*/
void reverse(char *s)
{
    int c, i, j;
    
    for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}
/*将整数n转换为字符数组存入s*/
void itoa(long n, char *s)
{
    int i, sign;
    /*最小负数单独处理*/
    if (n == 0x80000000) {
        strcpy(s, "-2147483648" ;
        return;
    }
    if ((sign = n) < 0) {
        n = -n;
    }
    i = 0;
    do {
        s[i++] = n % 10 + '0';
    }while ((n /= 10) > 0);
    if (sign < 0) {
        s[i++] = '-';
    }
    s[i] = '/0';
    reverse(s);
}
/*格式化输出,用法同printf,只支持%s和%u两种格式转换*/
void intprintf(const char *fmt, ...)
{
    const char *s;
    int d;
    char buf[16];
    va_list ap;
    va_start(ap, fmt);
    while (*fmt) {
        if (*fmt != '%') {
            putchar(*fmt++);
            continue;
        }
        switch (*++fmt) {
            case 's':
                s = va_arg(ap, char *);
                for ( ; *s; s++) {
                    putchar(*s);
                }
                break;
            case 'd':
                d = va_arg(ap, int);
                itoa(d, buf);
                for (s = buf; *s; s++) {
                    putchar(*s);
                }
                break;             
            default:  
                putchar(*fmt);
                break;
        }
        fmt++;
    }
    va_end(ap);



下面是一些值得注意的事情:

为了节省空间,这个简单的printf()只支持“%s”和“%d”格式的分类符,而不需要任何指示器或者其他调节器。扩展这个函数以支持其他分类符是相对较简单的。 
C语言的标准要求printf()应该返回输出字符串的数量。这个被返回的值通常被忽略掉了,所以这个轻型的printf()就不会劳心去计算它了,而是返回一个伪值。 
一般来说,putchar()这个函数会将字符发送到串口,更通行的方法是通过由中断驱动的串口输出缓冲区以避免延迟。 
首标文件<stdarg.h>是一个标准的库首标文件,它为访问传递给printf()的变量自变量列表提供小巧的宏,通常是通过系统堆栈。 
更进一步优化
  
要注意,控制权在你手里,你可以在感觉需要的情况下做出任何优化,并轻易地添加你所需要的特性。

下面是你可以考虑的几种优化方法:

添加一个#define,它会控制%d是否将整数显示为十进制或者十六进制数。如果你使用简单的位移(bit-shift)而不是长的划分来实现到十六进制数(等同于使用“%x”分类符)的转换,那么这一转换在大多数处理器上会快得多。然后你就能够在编译的时候为“%x”取得输出格式:快速的十六进制数或者缓慢的十进制。 
将整数到文本冗长的转换转移到工作站上。例如,当碰到“%x”分类符的时候,嵌入式处理器能够轻易地发送一个未使用的ASCII码,比如说0x01,并跟有原始的整数字节。不幸的是,这种方法需要一个在你工作站上运行的自定义终端程序,以解码它所获得的字节流,并在显示字符串之前实现必要的整数到文本的转换。  
编写一个完整的printf函数并不是一件简单的任务,因为它需要处理各种不同的格式指示符、可变参数列表等情况。下面是一个简化版的printf函数的示例,它只支持%d和%s两种格式指示符,并且只能输出到标准输出流中: ```c++ #include <stdio.h> #include <stdarg.h> void my_printf(const char *format, ...) { va_list args; va_start(args, format); while (*format) { if (*format == '%') { format++; if (*format == 'd') { int num = va_arg(args, int); printf("%d", num); } else if (*format == 's') { const char *str = va_arg(args, const char*); printf("%s", str); } } else { printf("%c", *format); } format++; } va_end(args); } int main() { my_printf("Hello, %s! Your age is %d.\n", "Alice", 25); return 0; } ``` 在这个示例中,我们使用了可变参数列表和va_list系列宏来处理函数的可变参数。具体来说,va_start宏用于初始化可变参数列表,va_arg宏用于读取可变参数列表中的参数,va_end宏用于结束可变参数列表的处理。在处理格式指示符时,我们使用了if-else语句来判断不同的格式指示符,并使用printf函数来输出相应的参数。需要注意的是,我们在处理完一个格式指示符后,需要将format指针向后移动一位,以便继续处理下一个格式指示符。 需要注意的是,这个示例只是一个非常简单printf函数的实现,实际的printf函数要比这个复杂得多。因此,在实际的开发中,我们应该尽量使用标准库中提供的printf函数来输出格式化数据,而不是自己编写printf函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值