printf() と同様の関数を組み込みで実装します。

C 言語の学習のほとんどは printf("hello world") から始まり、printf への習熟度が最も高くなります。組み込みプログラミングでは、printf 関数を実装する非常に標準的な方法は、putch を実装し、対応するシリアル ポート出力をバインドすることです。 、ボー レートを設定し、シリアル ポートを有効にします。mircolib を使用するとより効果的ですが、プロジェクトの実践では、使用には他の要件があります。

組み込みインターフェースのリソースは比較的逼迫しており、一般的な CPU にも 4 つのシリアル ポートがあり、多くのペリフェラルが搭載されていることが多く、1 つのシリアル ポートをデバッグ用に独立して使用すると、IO リソースとコストの無駄が耐えられないため、再利用するしかありません。一般的なシリアル ポート データ送信関数のインターフェイスは usartSend(char buf[],size_t len);//送信するデータと長さを示します。これは周辺機器と通信するためのインターフェイス要件を十分に満たすことができますが、デバッグ時に非常に不便です。複数のfloat変数の値を見たい場合、直接出力することはできず、以前の方法ではsprintf()形式に変換して再度出力する方法でしたが、この方法だと最低3行は書かなければなりませんデバッグ用のコードを追加すると、送信が完了するまで遅延が発生し、5 ~ 6 行のコードが必要になります。実装して1年が経ち、デバッグ時のコード量を減らすために方法を変更することにしました。

方法は次のとおりです (GNU コンパイラのサポートが必要ですが、GNU コンパイラは keil に統合されており、非常に使いやすいです)。

 (1) __attribute__ を使用して format 属性を拡張します拡張構文については、この記事 ( GNU C Extended Grammar_風のような Hang Ge のブログ - CSDN Blog )を参照してください。

まず例を示します。 void LOG(const char * fmt,...) __attribute__((format(printf(1,2)));

この属性は、printf 関数のパラメータ形式に従って LOG 関数のパラメータをチェックするようにコンパイラに指示します。... は可変パラメータを意味するので、可変パラメータを読み取って使用するとどうなるでしょうか? 読む。

(2) 関数の実装では、カプセル化されたマクロを使用してパラメータリストを取得できます。ヘッダファイル <stdarg.h> には 4 つの便利なマクロが提供されています。それぞれ va_list、va_start、va_arg、va_end です。

va_list: 変数のタイプ、変数パラメータを解析するための va_list タイプの変数の作成に使用されます va_list args;
va_start(args, fmt): パラメータ fmt のアドレスに従って、fmt の後のパラメータのアドレスを取得し、それをargs ポインタ変数。

C ライブラリ マクロ void va_start(va_list ap, last_arg) は、va_arg および va_endマクロと 一緒に使用されるap 変数を  初期化します 。last_arg は 、関数に渡される最後の既知の固定引数、省略記号の前の引数です。

このマクロは、  va_arg および va_end を使用する 前に呼び出す必要があります。

va_arg(args,int): va_arg マクロと va_list 変数を使用してパラメータリストの各項目にアクセスします。int は sizeof (int) の長さを自動的に増やすことを意味します。他のドキュメントを参照すると、int と double の 2 種類だけのようですサポートされているのは整数です。char であるか short であるかに関係なく、すべて int です。浮動小数点型は double であり、float を使用すると望ましい結果が得られません。
va_end(args): マクロ va_end を使用して、va_list 変数に割り当てられたメモリをクリーンアップし、NULL をポイントします。

以下は、double 型の変数パラメーターを走査して、すべての値を返す合計演算を実装する例です。(int 型の例は他の投稿に詳しく書かれています)。

void *fun01(double num, ...)
{
	int i;
	double res = 0;
	va_list v1;				//v1实际是一个字符指针,从头文件里可以找到 
	
	va_start(v1, num);		//使v1指向可变列表中第一个值,即num后的第一个参数 
	
	printf("*v = %lf\n",(double)*v1);
	
	for(i = 0; i < (int)num; i++)	//num 是为了防止下标超限 
	{
		res += va_arg(v1, typeof(num));		//该函数返回v1指向的值,并是v1向下移动一个int的距离,使其指向下一个int 
		printf("res = %lf, v1 = %p\n",res, v1); 
	} 
	va_end(v1);				//关闭v1指针,使其指向null
	return &res;
}

(3) フォーマットされた出力を実現する パラメータがわかったら、出力をフォーマットすることができます 当初、sprintf 関数を使用して以下のパラメータを処理していましたが、結果は常に間違っていました。調べて考えた結果、このライブラリには vprintf シリーズである va_list の変数を処理するための特別な関数が用意されていることがわかりました。

C 言語の printf 関数ファミリーのメンバー:

#include <stdio.h>

int printf(const char *format, ...); //標準出力に出力
int fprintf(FILE *stream, const char *format, ...); //ファイルに出力
int sprintf(char *str, const char *format, ...); //文字列 str
int snprintf(char *str, size_t size, const char *format, ...); //以下の関数
                                     のサイズに応じて文字列 str に出力
  
これは、関数が呼び出されたときに、上記に対応する変数が va_list 呼び出しによって置き換えられることを除いて、上記の 1 対 1 対応と同じです。関数が呼び出される前に、ap は va_start() マクロを通じて動的に取得されます。

#include <stdarg.h>

i nt vprintf(const char *format, va_​​list ap);
int vfprintf(FILE *stream, const char *format, va_​​list ap); int vsprintf(char *str, const char *format, va_​​list ap);
int vsnprintf(char *str, size_t サイズ, const char *format, va_​​list ap);

したがって、次のバージョンがあります。

void __attribute__((format(printf(1,2)))) my_printf(char *fmt, ...)
{
    va_list args;
    va_start(args,fmt);
    vprintf(fmt,args);
    va_end(args);
}

(4) 関数組み込み移植。vprintf を vsnprintf に置き換えて、組み込みシリアル ポート送信関数を呼び出すだけで、上記のバージョンはほぼ使用できます。

int vsnprintf (char * sbuf, size_t n, const char * format, va_​​list arg );

パラメータ sbuf: フォーマットされた文字列結果をキャッシュするために使用される文字配列

パラメータ n: vsnprintf は結果の最後に \0 も追加するため、バッファ sbuf に出力される最大文字数を n-1 に制限します。フォーマット文字列の長さが n-1 より大きい場合、余分な部分は破棄されます。フォーマット文字列の長さが n-1 以下の場合、フォーマット可能な文字列は完全にバッファ sbuf に出力されます。通常、ここで渡される値は sbuf バッファの長さです。

パラメータ形式: 形式制限された文字列

パラメータ arg: 可変長の引数リスト

戻り値: sbuf に正常に出力された文字数 (最後に追加された \0 を除く)。書式解析が失敗した場合は、負の数値が返されます。

そこで、次のバージョンが作成されました。

#include "stdio.h"
#include "stdarg.h"
#include "string.h"

void __attribute__((format(printf(1,2)))) my_printf(char *fmt, ...)
{
#ifdef    __DEBUG
    char sendbuf[512]={0};
    va_list args;
    va_start(args,fmt);
    vsnprintf(sendbuf,sizeof(sendbuf),fmt,args);
    va_end(args);
    Usart(sendbuf,strlen(sendbuf));    // 调用串口发送函数,实际情况改动
    delayms(strlen(sendbuf));           // 延时确保发送结束,以9600波特率为参考
#endif
}

上記のコードにおいて、__DEBUG はデバッグマクロを意味しており、プログラムをリリースする際にはこのマクロを閉じるだけで十分です。一般的なグローバル デバッグ マクロは、次の図に示すように定義されます。

 概要: 可変パラメータ機能により、組み込みシステムでおなじみの printf 関数が実現され、正式にリリースされたシリアルポート送信機能と競合しませんが、その代償としてメモリを多く消費します。マクロはキャンセルしても大丈夫です。 LAをリリースするとき。

学習過程で可変パラメータマクロも見かけましたが、おそらくこんな感じでしょう。

 ## がコネクターであることを理解していれば、その意味が理解できます。

おすすめ

転載: blog.csdn.net/weixin_41579872/article/details/128117784