天天看點

c語言可變參數 printf,stm32上的c語言可變參數 實作自己的printf

先說明一下,開發平台win7,工具RVMDK(keil),硬體stm32f103ve,列印到超級終端

前兩天開始關注一下一直被擱在一邊的printf。。。其實應該有一個月前就有看了一下,調用C語言官方庫,實作可變參數printf向序列槽列印字元串,數字等,呵呵,提進階的~~

先是在百度上看到一篇有關C語言可變參數的文章,感覺不是很難,兩天前嘗試了一下(其實弄了很久)昨天終于成功了~~(J-Link一步一步跟蹤進去的)沒有調用C語言官方庫的,完全按照自己的了解寫的。呵呵,今天就順便發表一下博文。

可變參數函數聲明格式為:type func(type para1, ...);

省略号就表示後面可以沒有參數,一個參數,或者多個參數,而且類型不必事先确定。就像C語言官方庫的printf("hello%s",str);帶了一個可變參數,呵呵

編寫前的基礎知識。堆棧!!函數調用的時候,函數參數進棧順序是自右向左,例如,如果調用的時候隻有兩個參數,則para2先入棧,然後para1再入棧。多個參數也一樣,而且因為用C語言不用關心硬體結構,是以其他平台上也同樣适用(好像是這樣)。還有就是棧是連續的資料結構,兩個參數之間位址是相鄰的,是以隻要知道第一個參數位址,第二個參數位址就同樣可以擷取了~~~還有需要了解的就是函數傳遞參數的知識了。同樣用voidfunc(para1, para2, ...)說明一下。C語言為傳遞參數para1,para2,執行函數func(para1, para2, ...)時會為每個參數建立一個副本,_para1,_para2,這樣才不會影響para1,para2的内容。那麼和函數建立的這兩個副本就是我們需要用到的,進棧的參數了!!!想辦法擷取_para1位址,根據棧是連續的資料結構,就能擷取第二個參數para2的位址了~~

首先當然要先擷取第一個參數的位址了,可以嘗試用一下方法

void func(uint8_t* para1)

{

uint8_t *p;

p = (uint8_t*)(&para1); //這樣就擷取到進棧副本_para1的位址了

}

如果有兩個參數的

void func(uint8_t *para1, uint8_t *para2)

{

uint8_t *p, *test;

p = (uint8_t*)(&para1); //先擷取第一個參數進棧的位址

test = (uint8_t*)(&para2); //擷取第二個參數進棧的位址

p += 4; //同樣擷取到第二個參數進棧位址了!!! 不信可以自行測試

}

既然可以擷取到進棧參數在棧上的位址,那麼要如何利用呢?請看例子

uint8_t *str = "World"; //定義一個指向字元串的指針

void func(uint8_t *para1, ...); //聲明可變參數函數

void main()

{

func("Hello", p); //在主函數中測試,實際測試列印出的是"World"

}

void func()

{

uint8_t *p; //局部變量,用以擷取可變參數指針

uint8_t *next;

p = (uint8_t*)(&para1); //擷取第一個參數進棧指針

p += 4; //擷取第二個進棧參數指針

next = (uint8_t*)(*((uint32_t*)(next)));//此時next指向str位址,等同于str

USART_SendString(USART1, next); //我的stm32序列槽列印函數,列印的是str的内容,“World”

}

這樣成功地用到了可變參數了,呵呵,不過有一個缺點。就是必須知道參數的個數,像C語言官方庫中也是需要用到%s,%d等在printf中說明出可變參數類型,并确定了可變參數的個數。從%+‘x’可以看出,‘x’表示可變參數資料類型。我們在自己寫帶可變參數函數的時候也要在可變參數裡面想辦法得到參數個數,給出參數類型,例如%s就是一個很好了模闆了

嘗試優化一下,完成功能更加接近printf的函數。模仿C語言官方庫,在第一個參數裡面顯式或隐式給出參數個數和參數類型,請看下面例子

uint8_t *str = "World";

void func(uint8_t *para1, ...)

void main()

{

func("Hello %s", str);

}

void func(uint8_t *para1, ...)

{

uint8_t *p; //局部變量,用以擷取可變參數指針

uint8_t *next;//用以儲存指向可變參數的源位址

p = (uint8_t*)(&para1); //擷取第一個參數進棧指針

if(*para1 == '%')

{

para++;

switch(*para1)

{

case 's':

p += 4;

next = (uint8_t*)(*((uint32_t*)(next)));//此時next指向str位址,等同于str

while(*next)

{

USART_Sned_8b(USART1, *next);//我的stm32序列槽列印函數,列印一個字元*P

next++;指針移到下一位

}

break;

default:

USART_Send_8b(USART1. '%');//不是%s,先發送字元'%'

USART_Send_8b(USART1, *p);//不是's',發送該字元

para1++; 指針移到下一位

break;

}

}//if(*para1 == '%')

else

{

USART_Send_8b(USART1, *p);//不是'%',發送該字元

para1++; 指針移到下一位

}

上面函數就實作了可變參數%s了,同樣的道理,可以繼續添加如%d,%c等,前提是了解了上面的例子,呵呵

還是有點模糊的同學請自行充電。。。

下面給出我自己在stm32上封裝的函數void USART_printf(USART_TypeDef* USARTx, uint8_t* data, ...);

i的作用是解決第一次序列槽發送資料重第二個字元發送的bug

void USART_printf(USART_TypeDef* USARTx, uint8_t* data, ...)

{

uint8_t *position, *next, *next_addr;//position儲存第一個參數入棧位址,next指向下一個

static uint8_t i = 1; //解決bug,第一次序列槽發送資料重第二個字元發送

position = (uint8_t*)(&data);//擷取第一個參數入棧位址

next = position;//儲存在next

while(*data)//第二個參數,字元串

{

if(*(data - i) == '%' ) //判斷是否為'%'

{

data++; //指針移至下一個,為下一步判斷做準備

switch(*(data - i))//如果是'%'需要判斷下一個字元是s/d

{

case 's'://字元串處理

next += 4;//擷取下一個參數入棧位址

next_addr = (uint8_t*)(*((uint32_t*)(next)));//恢複第二個參數入棧錢位址

while(*next_addr)//發送字元串函數循環

{

USART_Send_8b(USARTx, *next_addr);//發一個字元

next_addr++; //指針移至下一位

}

data++; //跳過發送字元's'

break; //結束本次switch

case 'd': //整變變量處理

next += 4; //擷取下一個參數入棧位址

next_addr = NumberToString((uint16_t)(*((uint32_t*)(next))));//擷取數字轉換成字元後的位址

while(*next_addr) //發送轉換好的字元串循環

{

USART_Send_8b(USARTx, *next_addr);//發送一個字元

next_addr++; //指針移到下一位

}

data++; //跳過發送字元'd'

break; //結束本次switch

default : //'%'後面不是s/d

USART_Send_8b(USARTx, '%'); //發送'%'

USART_Send_8b(USARTx, *(data - i)); //發送'%'後面字元

break; //結束本次switch

} //switch(*(data - i))

}

else if(*(data - i) == 'n' ) //如果是換行字元

{

USART_Send_8b(USARTx, 'r'); //回到第一個字元位置

USART_Send_8b(USARTx, 'n'); //下一行

data++;

}

else //if(*(data - i) == '%' )

{

USART_Send_8b(USARTx, *(data - i)); //不是'%',發送該字元

data++; //指針移至下一位

}

} //while(*data)

if(i == 1)

{

i = 0;

}

USART_Send_8b(USARTx, 'r');

USART_Send_8b(USARTx, 'n');//收尾工作,換行

}