記錄一下 今天看到的關於printf的實現機制, 瞭解到了C語言中的可變參函數的一些實現機制。
我們經常調用的一個函數 printf()函數
這是在linux 下 man printf 的結果。
在printf 傳入的參數的第二個參數 數字1 就是一個可選的參數, 也是我們今天的主角可變參數 第一個強制參數後面的 三個點,就是可能存在的參數。
可變參數之所以能實現是靠的 C語言對於函數實現的 這裏的實現機制是這樣的 以下來自
https://blog.csdn.net/weixin_44692935/article/details/103001787
根據 壓棧和出棧的邏輯我們可以向函數內傳遞不確定個數的參數,當調用函數 fun(a,b,c,d) 的時候
參數 d ,c,b,a 依次壓入 棧 我們能直接確定的參數是強制參數 a ,根據a 的 地址以及a的數據類型(int char double 等等) 我們可以確定b 的地址,接着根據b的地址以及參數b的數據類型 我們可以確定下一個參數的地址。
根據我們上面說的這些 ,有一個信息至關重要,那就是我們可選參數的數量, 這個數量信息必須要體現在第一個強制參數裏面
還是拿 剛剛那位大佬的 那篇博客做例子 https://blog.csdn.net/weixin_44692935/article/details/103001787
他的這篇博客的示例代碼 第一個參數直接就代表着 傳入的可變參數數量。
我們在常用的 printf 中的第一部分字符串信息 直接就有 %d %c %s 等等 printf函數 通過分析這些字符串中的 % 後面接的 字符就可以確定 參數的數量 以及要顯示的格式 等信息。
我自己總結就是第一個參數一定不可缺少, 第一個參數一定要包含後面參數的個數的信息 。其餘的數據類型 也可以在函數定義的時候確定 。
關於自己寫 可變參數函數 大家可以參考百問網 提供的代碼
代碼如下
#include "my_printf.h"
//==================================================================================================
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
//#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )
//==================================================================================================
unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\
'8','9','a','b','c','d','e','f'};
static int outc(int c)
{
__out_putchar(c);
return 0;
}
static int outs (const char *s)
{
while (*s != '\0')
__out_putchar(*s++);
return 0;
}
static int out_num(long n, int base,char lead,int maxwidth)
{
unsigned long m=0;
char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf);
int count=0,i=0;
*--s = '\0';
if (n < 0){
m = -n;
}
else{
m = n;
}
do{
*--s = hex_tab[m%base];
count++;
}while ((m /= base) != 0);
if( maxwidth && count < maxwidth){
for (i=maxwidth - count; i; i--)
*--s = lead;
}
if (n < 0)
*--s = '-';
return outs(s);
}
/*reference : int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char *fmt, va_list ap)
{
char lead=' ';
int maxwidth=0;
for(; *fmt != '\0'; fmt++)
{
if (*fmt != '%') {
outc(*fmt);
continue;
}
//format : %08d, %8d,%d,%u,%x,%f,%c,%s
fmt++;
if(*fmt == '0'){
lead = '0';
fmt++;
}
lead=' ';
maxwidth=0;
while(*fmt >= '0' && *fmt <= '9'){
maxwidth *=10;
maxwidth += (*fmt - '0');
fmt++;
}
switch (*fmt) {
case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break;
case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break;
case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break;
case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break;
case 'c': outc(va_arg(ap, int )); break;
case 's': outs(va_arg(ap, char *)); break;
default:
outc(*fmt);
break;
}
}
return 0;
}
//reference : int printf(const char *format, ...);
int printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}
int my_printf_test(void)
{
printf("This is www.100ask.org my_printf test\n\r") ;
printf("test char =%c,%c\n\r", 'A','a') ;
printf("test decimal number =%d\n\r", 123456) ;
printf("test decimal number =%d\n\r", -123456) ;
printf("test hex number =0x%x\n\r", 0x55aa55aa) ;
printf("test string =%s\n\r", "www.100ask.org") ;
printf("num=%08d\n\r", 12345);
printf("num=%8d\n\r", 12345);
printf("num=0x%08x\n\r", 0x12345);
printf("num=0x%8x\n\r", 0x12345);
printf("num=0x%02x\n\r", 0x1);
printf("num=0x%2x\n\r", 0x1);
printf("num=%05d\n\r", 0x1);
printf("num=%5d\n\r", 0x1);
return 0;
}
#ifndef _MY_PRINTF_H
#define _MY_PRINTF_H
#include "uart.h"
#define __out_putchar putchar
#define MAX_NUMBER_BYTES 64
extern int my_printf_test(void);
int printf(const char *fmt, ...);
#endif /* _MY_PRINTF_H */
#define va_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) )
這句話比較難以理解 是一個逗號表達式 (表達式1,表達式2)
先去執行表達式1 然後執行表達式2 返回值是表達式2的運算結果
截圖來自韋東山老師的新一期視頻