【C言語】変数パラメータ一覧


序文

変数パラメータリストは配列のように使用されます。関数スタックフレームを調べた場合、スタック領域で定義されたスペースで実際に継続的にアクセスされていることがわかりますが、中央部分での直接アクセスはサポートされていません。


免責事項:以下のすべてのテストは、x86、vs2013で実行されました。

1.可変パラメータリストとは何ですか?


ここに画像の説明を挿入
最初のC言語の最初のレッスンでは、可変パラメーターリストに触れました。printfのプロセスでは、通常、多数のパラメーターを渡して印刷することができますが、彼がどのようにそれを行うかはわかりません。今日分析してみましょう。



第二に、変数パラメータリストの使用方法


まず、windows.hのヘッダーファイルを導入し
、次に次のマクロを導入する必要があります。ここではその機能について簡単に説明しますが、後で詳しく説明します。ここでは、誰もが簡単に始められるようにしています。

typedef char* va_list;  //类型的重定义

#define _ADDRESSOF(v) (&(v))//一个取地址的宏。

1._ADDRESSOF:入力変数のアドレスを取得します。

#define _INTSIZEOF(n) \
 ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))




2. _INTSIZEOF:このマクロ関数は、nの型を4の倍数に丸めます。

#define _INTSIZEOF(n)\
  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

次のコードで説明します。

#pragma pack(1)//设置默认对其数为1
struct A
{
    
    
	char ch[11];
};

int main()
{
    
    
	printf("int : %d\n", _INTSIZEOF(int));
	printf("double: %d\n", _INTSIZEOF(double));
	printf("short: %d\n", _INTSIZEOF(short));
	printf("float: %d\n", _INTSIZEOF(float));
	printf("long long int: %d\n", _INTSIZEOF(long long int));
	printf("struct A:%d\n", _INTSIZEOF(struct A));
	return 0;
}

結果:
ここに画像の説明を挿入


3. __crt_va_start_a:変数vのアドレスを取得し、char *にキャストしてから、vタイプをポイントしてカウントします。つまり、最初の変数パラメーターリストで変数を見つけます。

#define __crt_va_start_a(ap, v) \
((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))



4. __crt_va_arg:apをポイントして、事前にアクセスする次の場所を指定し、現在アクセスしているコンテンツを返します。+ =の後のapはアクセスされる次のアドレスを指しますが、返されるコンテンツは現在のアドレスであることに注意してください。

#define __crt_va_arg(ap, t)   \
      (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))



5. __crt_va_end:apをNULLに設定します。

#define __crt_va_end(ap)\
 ((void)(ap = (va_list)0))

次に、次の定義を見ていきます。
#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end

テスト:配列に格納されていない最大のデータセットを見つけて返します。

int Find_Max(int num, ...)
{
    
    
	//定义一个char* 的变量arg
	va_list arg;
	//将arg指向第一个可变参数
	va_start(arg, num);
	//将max置成第一个可变参数,然后arg指向下一个可变参数
	int max = va_arg(arg, int);
	//循环num-1次,访问完剩下的可变参,找到最大的赋值给max
	for (int i = 1; i < num; ++i)
	{
    
    
		int r;
		if (max < (r = va_arg(arg, int)))
		{
    
    
			max = r;
		}
	}
	//将arg指针变量置成NULL,避免野指针
	va_end(arg);
	return max;
}
int main()
{
    
    
	int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
	printf("ret :%d\n", ret);
	return 0;
}

結果:
ここに画像の説明を挿入


3.マクロの詳細な分析


Linuxでのプロセスアドレス空間は上位から下位に配置されていますが、VSでのメモリは下位バイトから上位バイトであるため、スタックはLinuxで描画されたものとは異なりますが、すべて次の方向に拡張に向けられています。低いアドレス。これは誰もが理解できるようにするためのものです。

Linuxプロセスアドレス空間の概略図:
ここに画像の説明を挿入
コードスタックフレームの概略図:
ここに画像の説明を挿入


暗黙の型変換


たとえば、次のコードを実行するときに、char型でパラメーターを渡しても、関数本体がintステップで取得されている場合、この時点でエラーが発生しますか?

#include<stdio.h>
#include<windows.h>
int Find_Max(int num, ...)
{
    
    
	//定义一个char* 的变量arg
	va_list arg;
	//将arg指向第一个可变参数
	va_start(arg, num);
	//将max置成第一个可变参数,然后arg指向下一个可变参数
	int max = va_arg(arg, int);
	//循环num-1次,访问完剩下的可变参,找到最大的赋值给max
	for (int i = 1; i < num; ++i)
	{
    
    
		int r;
		if (max < (r = va_arg(arg, int)))
		{
    
    
			max = r;
		}
	}
	//将arg指针变量置成NULL,避免野指针
	va_end(arg);
	return max;
}

int main()
{
    
    
	char a = '1'; //ascii值: 49
	char b = '2'; //ascii值: 50
	char c = '3'; //ascii值: 51
	char d = '4'; //ascii值: 52
	char e = '5'; //ascii值: 53
	int ret = Find_Max(5, a, b, c, d, e);
	//int ret = Find_Max(5, 0x1, 0x2, 0x3, 0x4, 0x5);
	printf("ret :%d\n", ret);
	system("pause");
}

回答:
いいえ、スタックをプッシュするときにパラメーターがレジスターを通過するため、32ビット未満のレジスターは4バイトです。
ここに画像の説明を挿入

スタックをプッシュするときのアセンブリ:最初のものはmovではなく、アセンブリ言語データ転送命令MOVの変形であるmovsxです。符号拡張し、送信します。つまり、化粧品の強化です。
ここに画像の説明を挿入
同じことが当てはまります。floatを使用してパラメーターを渡し、2語の長さを使用して移動します。問題はありません。
ここに画像の説明を挿入
要約
したがって、関数本体(Find_Max)内の長さとしてint / doubleを使用することに慣れており、パラメーターを渡すときにchar / short/floatおよびその他のタイプを使用できます。

注:
64ビットでの定義は32ビットでの定義とは大きく異なります。

なぜ4バイトアラインメント:
前述のように、短整数型では、スタックをプッシュする過程でシェーピングプロモーションが発生するため、スタックフレームから対応するデータを取得するには、対応する方法に従ってデータを抽出する必要があります。

_INTSIZEOFの数学的理解:

_INTSIZEOF(n)は、次のことを意味します。x> = n && x%4 == 0を満たす最小数xを計算します。ここで、nはsizeof(n)の値を表します。つまり、型のサイズはnの整数倍に揃える必要があり、最小値はnより小さくすることはできません。

4バイトの配置は栗です:
n%4 == 0、次にret = n;
n%4!= 0、次にret =(n + 4-1)/ 4 * 4;
(n + 4-1)/ 4--- > nが1から4であるとすると、(n + 4 --1)/ 4の結果はすべて1であり、対数4を掛けると、4つのアライメントの最小アライメント数になります。これ4个数值范围最小对齐倍数同じ値に制御できます。

(n + 4 -1)/ 4 * 4、/ 4は実際にはバイナリシーケンスを右に移動し、* 4はバイナリシーケンスを左に移動していることがわかります。これは実際には、最も低い2つの位置を設定するためのものです。 0にすると、次のように簡略化できます:
(n + 4 -1)&〜3、これはソースコードの定義です!

#define _INTSIZEOF(n)\
  ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )



2つの機能の認識


printfの理解:
上記の例では、マクロは実際のパラメーターの数と実際のパラメーターのタイプを判別できないため、printfには、パラメーターの数とパラメーターのタイプを判別するメソッドが必要です。実際には%c、%d、%lfです。ここで、%%を除く%の数は実際にパラメーターの数を通知し、%c、%dは実際に対応するタイプを示します。


execシリーズの理解:プロセス制御
ここに画像の説明を挿入
では、実際にはexecveを呼び出すシステムコールは1つだけであり、他の関数execveは最終的にexecve関数を呼び出すと説明されていましたが、パラメーターからこれまでのプロセスをどのように実現するのでしょうか。回答:実際、nullにアクセスするまでは、渡されたパラメーターの数をカウントでカウントでき、タイプは間違いなくchar *であるため、strlenを使用して所要時間を計算できます。(ただし、2つのchar配列は通常8バイト以上離れています)lv


要約する

これで、この章のパラメータのリストは終わりです。次の号では、関数スタックフレームを再理解し、ブログを公開する可能性があります。ここで、ブロガーは皆さんに明けましておめでとうございます!
一键三连一键三连一键三连一键三连一键三连

おすすめ

転載: blog.csdn.net/weixin_52344401/article/details/122764234