可変パラメータテーブル関数の原理と実現(printf実現)


大丈夫な時、printfを考えてみると、どんな大まかな原理なのか分からないことに気づいた.文章を見つけたが、自分の理解も加えて、この文章を書いた.
原文住所:http://www.cnblogs.com/liyou_blog/archive/2010/09/01/1814663.html
ANSI Cのライブラリ関数printfの正しい形式は以下の通りです.
  int printf(char *fmt, ...);
ここで、省略記号は、パラメータテーブルの数とタイプが可変であることを示します.省略記号はパラメータテーブルの末尾にのみ表示されます.このような可変パラメータテーブルを持つ関数をどのように実現しますか?「The C programme Language」では、次のような例示的な関数が実装されています.
  void minprintf(char *fmt, ...);
まずその実現方式を見て、それからその実現原理を研究して、関数minprintfはフォーマット文字列とパラメータだけを処理して、フォーマット変換は関数printfを呼び出すことによって実現します.
  

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

void minprintf(char *fmt, ...)
{
    va_list ap; //           //   ,va_list    ,     ,  va_start, va_arg   va_end
    char *p, *sval;
    int ival;
    double dval;

    va_start(ap,fmt); // ap         
    for (p = fmt; *p; p++)
    {
        if (*p != '%') 
        {
            putchar(*p);
            continue;
        }
        switch( *++p )
        {
            case 'd':
                ival = va_arg(ap,int);
                printf("%d",ival);
                break;
            case 'f':
                dval = va_arg(ap,double);
                printf("%f",dval);
                break;
            case 's':
                for (sval = va_arg(ap,char *); *sval; sval++)
                putchar(*sval);
                break;
            default:
                putchar(*p);
                break;
        }
    }
    va_end(ap);
}
上記のコードの標準ヘッダファイル「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 );
  va_listは実は文字ポインタタイプで、コードに変数を宣言します.この変数は、各パラメータを順番に参照します.コードでこのタイプの変数をap、すなわち「パラメータポインタ」と宣言します.マクロva_startはapを最初の無名パラメータを指すポインタに初期化します.1つ目のパラメータはap自体であり、2つ目のパラメータは、パラメータテーブルの前に隣接する変数、すなわち「...」の前のパラメータである.apを使用する前に、マクロを1回呼び出す必要があります.パラメータテーブルには、少なくとも1つの有名なパラメータ(「char*fmt」)、va_が含まれている必要があります.startは最後の有名なパラメータを起点とします.
次にva_を呼び出すargはパラメータを取得し、その最初のパラメータはapであり、2番目のパラメータは取得するパラメータの指定タイプであり、この指定タイプの値を返し、apの位置を変参テーブルの次の変数の位置に指す.最後にすべてのパラメータを取得した後、問題が発生しないようにapポインタをオフにする必要があります.va_を呼び出す方法です.endは、入力したパラメータapをNULLに設定します.
 
関数パラメータの伝達原理
関数パラメータは、データ構造でスタックとしてアクセスし、右から左に順にスタックに入ります.パラメータはメモリのスタックスペースに格納され、関数を実行するときに最後からスタックに入ります.スタック底高アドレス、スタック頂低アドレス、例えば関数void foo(int x,float y,char z);では、関数を呼び出すとき、実パラメトリックchar zはスタックに進み、float y、最後にint xとなるため、メモリにおける変数の格納順序はx->y->z(低アドレスから高アドレスまでの順序)であるため、いずれかの変数のアドレスを知り、他の変数のタイプを知る限り(ポインタシフト演算を正確に実現することができる)、藤に沿って瓜を触ることで、他の入力変数を見つけることができます.検証のサンプルコードは次のとおりです.出力は1、2、3、4です.
  

void fun(int a, ...)
{
    int i;
    int *temp = &a;
    temp++;
    for (i=0 ; i<a; ++i)
    {
        printf("%d ,",*temp);
        temp++;
    }
}

int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    int d = 4;
    fun(4,a,b,c,d);
    return 0;
}
最後に、可変パラメータテーブルを使用して、実際に使用される関数を実装します.
int max(int nNum, ...)
{
	va_list ap;
	va_start(ap, nNum);//   ap
	
	int maxNum = va_arg(ap, int);//          

	int num;
	for(int i = 1; i < nNum; i++)//
	{
		num = va_arg(ap, int); //       
		if(num > maxNum)
		{
			maxNum = num;
		}
	}

	va_end(ap);
	return maxNum;


}
int main()
{

	printf("%d", max(5, 20, 11, 23, 5, 10));
}
プログラム実行出力 :  
23
注意:
    マクロ定義により、変長パラメータテーブルが実装されるため、次のような問題が発生する可能性があります.
    .       ,           ,                   (      float,
      int      ),               。
    .                ,           ,              。