技術共有

【C言語】前処理の詳細解説(前編)

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. 定義済みのシンボル

C言語での設定がいくつかあります定義済みのシンボル、直接使用できます。事前定義されたシンボルは次のとおりです。在预处理期间处理的

__FILE__    //进行编译的源文件
__LINE__	//文件当前的行号
__DATE__	//文件被编译日期
__TIME__	//文件被编译那一瞬的时间
__STDC__	//如果编译器遵循ANSI C(标准C),其值为1,否则未定义(报错)
  • 1
  • 2
  • 3
  • 4
  • 5

  
例:

#include<stdio.h>

int main()
{
	printf("%sn", __FILE__);
	printf("%dn", __LINE__);
	printf("%sn", __DATE__);
	printf("%sn", __TIME__);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
操作結果:

ここに画像の説明を挿入します

ここで、VS は ANSI C (標準 C) を完全にはサポートしていないことに言及したいと思います。 gccgcccc ANSI C (標準 C) をサポート
  

二、# 定義する 定義するdee 定数(シンボル)を定義する

基本的な構文:

# define name stuff
  • 1

例えば:

#define MAX 100
#define reg register  // 为 register 这个关键字创建一个简短的名字
#define do_forever for(;;)  //用更形象的符号来替换一种实现
#define CASE break;case		//在写 case 语句的时候自动吧 break 写上

//如果定义的 stuff 过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)
#define DEBUG_PRINT printf("file:%stline:%dt 
							date:%sttime:%sn" ,
							__FILE__,__LINE__ , 
							__DATE__,__TIME__ )
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 2 番目の文は怠惰ですが、なぜですか?
  • 3番目の文 のために のためにまたは 円形初始化、判断、调整都可以省略, しかし、判定を省略した場合、判定条件は常に真であることを意味します。無限ループ
  • トラブルを引き起こしやすいため、4 番目のゲームではこれを行わないほうがよいでしょう。
  • 5番目の文行継続文字その本質は分岐後のトラブルを防ぐことです。转义戻る回车符 , そのため、復帰は復帰ではなくなります。行継続文字の後には何も入力できません。“” Enter キーを押すだけです。そうしないと、次のコード行には続きません。

  
ここで問題が発生します: # を使用してください 定義する 定義するdee 識別子を定義する場合、最後に追加する必要がありますか?

例えば:

#define MAX 1000
#define MAX 1000;

int main()
{
	int n = MAX;
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

ここに画像の説明を挿入します

上記のコードに加えて、、少し冗長に思えますが、プログラムの実行には影響しません。
追加するようです あるいは追加しないでください 大丈夫でしょうか?
本当にそうなのか?
  
次の例を見てみましょう。

//例一
int main()
{
	printf("%dn", MAX);
	return 0;
}

//例二
int mian()
{
	int max = 0;
	if (1)
		max = MAX;
	else
		max = 1;
	return 0
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

交換後:

printf("%dn", 1000;);
  • 1

印刷する1000;どういう意味ですか?

if (1)
	max = 1000;
	;
else
	max = 1;
  • 1
  • 2
  • 3
  • 4
  • 5

他にel 誰と合わせる?
  
ほら、これには何か問題があるので、使用 # 定義する 定義するdee インジケーターを定義するときは、インジケーターの後に追加しないでください。

  

三つ、# 定義する 定義するdee マクロを定義する

  # 定義する 定義するdee このメカニズムには次の条項が含まれています。允许把参数替换到文本中、この実装はよく呼ばれます大きい ( マルコ ) ( マルコ )rcoまたは マクロを定義する (マクロを定義する) (マクロを定義する)deeクロ
マクロと上記のマクロ定義識別子の違いは次のとおりです。マクロにはパラメータがあります
  
マクロの宣言方法は次のとおりです。

#define name(parament - list) stuff
  • 1

それらの中の一つ パラメント パラメントp1つのr午前et - リスト リストlst(パラメータリスト) は、以下に表示される可能性のあるシンボルのカンマ区切りリストです。 もの ものstあなたff 真ん中
注記: パラメント パラメントp1つのr午前et - リスト リストlst(パラメータリスト)左括弧必ず一緒にいる 名前 名前ナムe の隣に、間に空白が存在する場合、引数リストは次のように解釈されます。 もの ものstあなたff の一部。
  

例:

//实现一个宏,计算一个数的平方
#define SQUARE(x) x*x

int main()
{
	int a = 5;
	int ret = SQUARE(a);
	printf("%dn", ret);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
操作結果:

ここに画像の説明を挿入します

ご覧のとおり、5 の 2 乗は正しく計算されています。

しかし実際には、上記のコードには問題があります。次のコード スニペットを参照してください。

int a = 5;
printf("%dn", SQUARE(a + 1));
  • 1
  • 2

  
操作結果:

ここに画像の説明を挿入します

なぜそうなるのでしょうか? 5+1の結果は36と6です ∗ * 6 は 36 になるはずです。どうやって 11 を得たのですか?

問題はマクロにあることはわかっています宏是直接替换の場合、上記のコードを直接置き換えた結果は次のようになります。

printf("%dn", a+1*a+1);
  • 1

5 + 6 + 1、結果は当然 11

この問題は、マクロ定義の両側に括弧を追加することで簡単に解決できます。

#define SQUARE(x) (x)*(x)
  • 1

では、この定義で問題はないのでしょうか?次のマクロ定義を見てみましょう。

#define DOUBLE(X) (X)+(X)
  • 1

前の問題を回避するために定義内で括弧を使用しましたが、このマクロにより新たなエラーが発生する可能性があります。

int a = 5;
printf("%dn", 10 * DOUBLE(a));
  • 1
  • 2

  
操作結果:

ここに画像の説明を挿入します

出力結果は100ではなく55となります。理由は上記と同様です。 優先的な質問
  
解決:

#define DOUBLE(X) ((X)+(X))
  • 1

要約すると、マクロを使用する場合括弧を決してケチらないでくださいマクロを使用するときに、パラメーター内の演算子または隣接する演算子間の予期しない相互作用を回避するため。

  

4. 副作用のあるマクロパラメータ

マクロ パラメータがマクロ定義内で複数回出現する場合、パラメータに副作用場合、このマクロを使用すると危険にさらされる可能性があり、その結果、不可预测結果として。副作用は、式が評価されるときの永続的な効果です。
  
例えば:

x + 1; //不带副作用
x++//带副作用
  • 1
  • 2

次の MAX マクロは、副作用のあるパラメーターによって引き起こされる問題を示しています。

#define MAX(a,b) ((a) > (b) ? (a):(b))

int main()
{
	int x = 5;
	int y = 8;
	int z = MAX(x++, y++);
	printf("x=%d, y=%d, z=%dn", x, y, z);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  
操作結果:

ここに画像の説明を挿入します

なぜそうなるのでしょうか?一緒に分析しましょう

z = ((X++) > (y++) ? (x++) : (y++))
  • 1
  • まず判断を下します。 xxバツ++付き ええええ++判定、後置++なので判定する場合 xxバツ は5、 ええええ 8、8 &gt; 5
  • 審査後 xxバツ 6、 ええええ 9人分
  • 次に実行します ええええ ++、接尾辞 ++ なので、結果は 9 になります。
  • 続いて ええええ 自動インクリメントを実行し、 ええええ 最終結果は 10 です

私達はします xxバツ そして ええええ マクロに渡されると、特に結果が変わります。 ええええ、2回変更した後、あなたはひどくはないと言いました
副作用のあるパラメータがマクロに渡され、そのパラメータがマクロ内で複数回出現する場合、パラメータの副作用も複数回発生します。
  

5. マクロ置換のルール

プログラム # で展開 定義する 定義するdee シンボルとマクロを定義するには、いくつかの手順が必要です

  • マクロを呼び出すときは、まず参数进行检查# が含まれているかどうかを確認してください 定義する 定義するdee 定義済み标识符 。その場合は、最初に置き換えられます
  • 置換テキストは次のとおりです被插入マクロパラメータ名をその値で置き換えて、プログラム内の元の場所にコピーします。
#define MAX(a,b) ((a) > (b) ? (a):(b))
#define M 10

int main()
{
	int x = 5;
	int z = MAX(x, M);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • MAX ( x , M ) MAX(x, M)バツ(バツ,) 真ん中 んん まず 10 に置き換えられ、元の文字列に 10 が挿入されます。 んん 位置
  • 最後に、結果が再度スキャンされ、# に起因するものが含まれているかどうかが確認されます。 定義する 定義するdee 定義されたシンボルがある場合は、上記のプロセスを繰り返します

上記のコードの MAX は # でも表されます 定義する 定義するdee 定義済み大きい 。前回の検査でパラメータMが交換されており、今回交換することになりました。
   MAX ( x , 10 ) MAX(x, 10)バツ(バツ,10) に置き換えられます ( ( x ) &gt; ( 10 ) (( x ) &gt; ( 10 )((バツ)>(10) ? ? ? ( x ) : ( 10 ) ) ( x ):(10))(バツ):(10))
  
確かに、宏里面嵌套宏それもいいです

MAX(x, MAX(2, 3))
  • 1

このとき、まずパラメータ内のマクロを置き換えてから、マクロ全体を置き換えます。
しかし、これに注意してください不是递归、これは別のマクロへの引数として機能する単なるマクロです。递归是宏内部又调用了宏本身
また、プリプロセッサが # を検索するときも 定義する 定義するdee シンボルを定義するときは、文字列定数の内容は検索されません
それはどういう意味ですか?栗をあげればわかります。

#define M 10

int main()
{
	int x = 5;
	int z = MAX(x, MAX(2, 3));
	printf("M = %d", x);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

上記のコードではprintf("M = %d", x);真ん中 んん 10には置き換えられません

  

6. マクロと関数の比較

上記のマクロは、2 つの数値の大きい方の値を見つけるために使用されます。完全に関数として記述することができます。

int Max(int x, int y)
{
	return x > y ? x : y;
}
  • 1
  • 2
  • 3
  • 4

それらはすべて同じ機能を達成することがわかりました。ただし、「2 つの数値のうち大きい方を見つける」という機能の場合、マクロを書くと、更有优势いくつかの
  
理由は 2 つあります。

  • のために使用される调用函数そして函数返回コードこの小さな計算を実際に実行するよりも時間がかかる場合があります (関数を呼び出すとき)スタックフレームを作成する )。したがって、マクロ比率関数はプログラム内にあります规模そして速度今まで一番
  • さらに重要なことには函数的参数必须声明类型これにより、関数は適切なタイプの式でのみ使用されます。それどころか、このマクロは多くの型に適用できます。整数、長整数、浮動小数点などを使用できます。
    > 比べる。マクロのパラメータは、タイプに依存しない

将来的には常にマクロを使用するのでしょうか?実際、マクロは次の目的でのみ使用されます。簡単な計算、マクロと比較して、複雑で大規模な操作や関数には適していません。 短所

  1. マクロが使用されるたびに、マクロ定義コードのコピーがプログラムに挿入されます。マクロが比較的短い場合を除き、大幅度增加程序的长度
  2. マクロは没法调试
  3. マクロは型に依存しません。つまり、不够严谨
  4. マクロにより操作の優先順位の問題が発生し、プログラムが容易出错

しかし、関数ではできないことがマクロでできることもあります。
  
例えば:

int* p = (int*)malloc(10 * sizeof * (int));
  • 1

私たちはこのように書くのは好きではありません マルロック マルロックlloc 機能が面倒なのでやりたい大小そして类型渡してスペースを空ける

Malloc(10, int);
  • 1

関数でこれができるのでしょうか?いや、だって関数は型を渡すことができません
マクロではできないので、マクロでもそれができますチェックしないでくださいあなたのパラメータタイプ

#define Malloc(n, type) (type*)malloc(n * sizeof(type))

int main()
{
	int* p = Malloc(10, int);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

  
マクロと関数の比較:

属性# 定義する 定義するdeeマクロを定義する関数
コード長使用するたびに、非常に小さなマクロを除いて、プログラムにマクロ コードが挿入されるため、プログラムの長さは大幅に増加します。関数コード値は 1 か所に表示され、関数が使用されるたびに、その場所にある同じコードが呼び出されます。
実行速度もっと早く関数の呼び出しと戻りのオーバーヘッド (スタック フレームを開く) が追加されるため、遅いものから始めましょう。
演算子の優先順位マクロ パラメーターは、周囲のすべての式のコンテキストで評価されます。括弧が含まれていない場合、隣接する演算子の優先順位によって予期せぬ結果が生じる可能性があるため、マクロをより多くの括弧で記述することをお勧めします。関数パラメーターは、関数が呼び出されたときに 1 回だけ評価され、その結果値が関数に渡されるため、式の評価結果がより予測しやすくなります。
副作用のあるパラメータパラメータはマクロ内の複数の位置に置換される可能性があります。マクロのパラメータが複数回評価される場合、副作用のあるパラメータ評価により予期しない結果が生じる可能性があります。関数のパラメーターはパラメーターを渡すときに 1 回だけ呼び出されるため、結果の予測が容易になります。
パラメータの種類マクロのパラメータは、パラメータの操作が正当である限り、どのタイプのパラメータでも使用できます。関数のパラメーターは型に関連しています。パラメーターの型が異なる場合は、同じタスクを実行する場合でも、異なる関数が必要になります。
デバッグマクロはデバッグに不便です関数は段階的にデバッグ可能
再帰マクロは再帰的にはできません関数は再帰的に使用できます

それぞれの利点を組み合わせる方法はありますか?
C++で導入インライン関数 インライン インラインle ——マクロと関数の両方の利点を兼ね備えています
マクロと同じくらい高速に実行されますが、関数と同じ効果があります。