print.h
- #ifndef __PRINT_H_
- #define __PRINT_H_
- void print(char* fmt, ...);
- void printch(char ch);
- void printdec(int dec);
- void printflt(double flt);
- void printbin(int bin);
- void printhex(int hex);
- void printstr(char* str);
- #define console_print(ch) putchar(ch)
- #endif /*#ifndef __PRINT_H_*/
上面print函數爲全功能的打印函數,可以實現類似printf的功能,printch實現單個字符的打印、printdec實現十進制格式數字的打印,printflt實現浮點數的打印,printbin實現二進制格式數字的打印,printhex實現十六進制格式數字的打印,printstr實現字符串的打印,console_print函數是串口單字符打印函數的宏定義,這裏暫時用PC終端單字符打印函數putchar代替。在實際嵌入式環境下,替換成串口單字符打印函數即可。
print.c
- #include <stdio.h>
- #include <stdarg.h>
- #include "print.h"
- int main(void)
- {
- print("print: %c\n", 'c');
- print("print %d\n", 1234567);
- print("print: %f\n", 1234567.1234567);
- print("print: %s\n", "string test");
- print("print: %b\n", 0x12345ff);
- print("print: %x\n", 0xabcdef);
- print("print: %%\n");
- return 0;
- }
- void print(char* fmt, ...)
- {
- double vargflt = 0;
- int vargint = 0;
- char* vargpch = NULL;
- char vargch = 0;
- char* pfmt = NULL;
- va_list vp;
- va_start(vp, fmt);
- pfmt = fmt;
- while(*pfmt)
- {
- if(*pfmt == '%')
- {
- switch(*(++pfmt))
- {
- case 'c':
- vargch = va_arg(vp, int);
- /* va_arg(ap, type), if type is narrow type (char, short, float) an error is given in strict ANSI
- mode, or a warning otherwise.In non-strict ANSI mode, 'type' is allowed to be any expression. */
- printch(vargch);
- break;
- case 'd':
- case 'i':
- vargint = va_arg(vp, int);
- printdec(vargint);
- break;
- case 'f':
- vargflt = va_arg(vp, double);
- /* va_arg(ap, type), if type is narrow type (char, short, float) an error is given in strict ANSI
- mode, or a warning otherwise.In non-strict ANSI mode, 'type' is allowed to be any expression. */
- printflt(vargflt);
- break;
- case 's':
- vargpch = va_arg(vp, char*);
- printstr(vargpch);
- break;
- case 'b':
- case 'B':
- vargint = va_arg(vp, int);
- printbin(vargint);
- break;
- case 'x':
- case 'X':
- vargint = va_arg(vp, int);
- printhex(vargint);
- break;
- case '%':
- printch('%');
- break;
- default:
- break;
- }
- pfmt++;
- }
- else
- {
- printch(*pfmt++);
- }
- }
- va_end(vp);
- }
- void printch(char ch)
- {
- console_print(ch);
- }
- void printdec(int dec)
- {
- if(dec==0)
- {
- return;
- }
- printdec(dec/10);
- printch( (char)(dec%10 + '0'));
- }
- void printflt(double flt)
- {
- int icnt = 0;
- int tmpint = 0;
- tmpint = (int)flt;
- printdec(tmpint);
- printch('.');
- flt = flt - tmpint;
- tmpint = (int)(flt * 1000000);
- printdec(tmpint);
- }
- void printstr(char* str)
- {
- while(*str)
- {
- printch(*str++);
- }
- }
- void printbin(int bin)
- {
- if(bin == 0)
- {
- printstr("0b");
- return;
- }
- printbin(bin/2);
- printch( (char)(bin%2 + '0'));
- }
- void printhex(int hex)
- {
- if(hex==0)
- {
- printstr("0x");
- return;
- }
- printhex(hex/16);
- if(hex < 10)
- {
- printch((char)(hex%16 + '0'));
- }
- else
- {
- printch((char)(hex%16 - 10 + 'a' ));
- }
- }
編譯執行結果如下:
- print: c
- print: 1234567
- print: 1234567.123456
- print: string test
- print: 0b1001000110100010111111111
- print: 0xabcdef
- print: %
如上所示,print函數實現了類似printf的功能。
總結:
1、函數參數的傳遞原理
函數參數是以數據結構:棧的形式存取,從右至左入棧,因此棧底高地址,棧頂低地址,舉個例子如下:
void func(int x, float y, char z);
那麼,調用函數的時候,實參 char z 先進棧,然後是 float y,最後是 int x,因此在內存中變量的存放次序是 x->y->z,因此,從理論上說,我們只要探測到任意一個變量的地址,並且知道其他變量的類型,通過指針移位運算,則總可以順藤摸瓜找到其他的輸入變量。
2、下面是 <stdarg.h> 裏面重要的幾個宏定義如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
實現步驟:
a、va_list 是一個字符指針,可以理解爲指向當前參數的一個指針,取參必須通過這個指針進行。
在調用參數表之前,定義一個 va_list 類型的變量,(假設va_list 類型變量被定義爲ap);
b、然後應該對ap 進行初始化,讓它指向可變參數表裏面的第一個參數,這是通過 va_start 來實現的,第一個參數是ap 本身,第二個參數是在變參表前面緊挨着的一個變量,即“...”之前的那個參數;
c、接着調用va_arg,它的第一個參數是ap,第二個參數是要獲取的參數的指定類型,然後返回這個指定類型的值,並且把 ap 的位置指向變參表的下一個變量位置。
d、 獲取所有的參數之後,我們有必要將這個 ap 指針關掉,以免發生危險,方法是調用 va_end,他是輸入的參數 ap 置爲 NULL。