目次
1. 例: 関数とマクロを使用して 2 つの数値の大きい方の値を見つける
3. マクロは、関数ではできないことも実行できます (たとえば、マクロのパラメータを型にすることができます)。
序文
1. プログラム翻訳環境と実行環境
作成した C 言語コードを実行可能プログラム (.exe) にコンパイルしたいのですが、このプロセスでは、翻訳環境と実行環境の 2 つの異なる環境が必要になります。
1. 翻訳環境
この環境では、ソース コードが実行可能な機械語命令に変換されます。
2. 実行環境
実際の実行コードについては
1. 翻訳環境
VS は統合開発環境ですが、コンパイラの機能をいちいち観察するのは不便ですので、次に vscode で構築した gcc プラットフォーム上でデモを行います。
2. 実行環境
プログラム実行のプロセス:
1. プログラムをメモリにロードする必要があります。オペレーティング システムを使用する環境では、通常、オペレーティング システムによって完了しますが、独立した環境では、プログラムのロードを手動で調整する必要があるか、実行可能コードを読み取り専用メモリに配置することによって完了する場合があります。プログラムの実行が
開始され、main 関数が呼び出されます
。 3. プログラム コードの実行を開始します。このとき、プログラムはランタイム スタックを使用して、関数のローカル変数と戻りアドレスを保存します。プログラムは静的メモリも使用できます。静的メモリに格納された変数は、プログラムの実行中その値を保持します。
4. プログラムを終了します。main 関数を正常に終了します。予期せず終了する場合もあります
2. 前処理の詳細説明
1. 定義済みのシンボル
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSIC,其值为1,否则未定义
2.#define は識別子を定義します
#名前のものを定義する
#define で識別子を定義する場合、最後にセミコロンを追加する必要がありますか?
コード内で問題が発生する可能性があるため、セミコロンを追加しないことをお勧めします。
#undef
#define で定義された識別子を元に戻す
3.#define マクロを定義します
#define 名前(パラメータリスト)のもの
#define を使用すると、パラメータをテキストに置き換えることができます。この実装は、マクロまたは定義マクロと呼ばれることがよくあります (注: パラメータ リストの左括弧は、名前のすぐ隣になければなりません。この 2 つの間に空白がある場合、パラメータリストはものの一部として解釈されます。)
//求平方
#define SQUARE(x) ((x)*(x))
//求较大值
#define MAX(a, b) (((a)>(b))?(a):(b))
4.#置換ルールを定義する
プログラム内で #define シンボルとマクロを展開する場合は、いくつかの手順が必要です。
1. マクロを呼び出すと、最初にパラメーターがチェックされ、#define で定義されたシンボルが含まれているかどうかが確認されます。その場合は、最初に交換されます。
2. 置換テキストは、プログラム内の元のテキストの位置に挿入されます。マクロの場合、パラメータ名はその値に置き換えられます。
3. 最後に、結果のファイルが再度スキャンされ、#define で定義されたシンボルが含まれているかどうかが確認されます。その場合は、上記のプロセスを繰り返します。
知らせ:
1. 他の #define で定義されたシンボルは、マクロ パラメーターおよび #define 定義に出現できます。ただし、マクロの場合、再帰は発生しません。
2. プリプロセッサが #define で定義されたシンボルを検索する場合、文字列定数の内容は検索されません。
5.#と##
# の役割 (注意: ここでの「#」は「#define」の「#」ではありません)
#文字列にパラメータを挿入できます
マクロパラメータを対応する文字列に変換できます
## 関数
##両側のシンボルを 1 つのシンボルに結合できます
マクロ定義で分離されたテキストの断片から識別子を作成できるようになります。
6. 副作用のあるマクロパラメータ
マクロの定義内にマクロ パラメーターが複数回出現し、そのパラメーターに副作用がある場合、このマクロの使用時に危険が生じ、予期しない結果が生じる可能性があります。副作用は、式が評価されるときに発生する永続的な影響です。
3. マクロと関数の比較
1. 例: 関数とマクロを使用して 2 つの数値の大きい方の値を見つける
#include <stdio.h>
//宏的实现
#define MAX(a, b) (a)>(b)?(a):(b)
//函数的实现
int Max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
//输出较大值
int m1 = MAX(a, b);
printf("%d\n", m1);
int m2 = Max(a, b);
printf("%d\n", m2);
return 0;
}
你认为哪种实现方式更好呢?
現在のコードに関する限り、次の理由によりマクロ実装の方が優れています。
1.関数呼び出し(関数呼び出し前の準備:パラメータの転送、関数スタックフレーム領域の確保、初期化)および関数復帰(戻り値の処理、関数スタックフレームの破壊)にかかる時間は、実際の関数実行よりも短くなる場合があります。より多くの計算作業があるため、マクロはプログラムのサイズと速度の点で関数よりも優れています。
2. さらに重要なのは、関数のパラメータは特定の型として宣言する必要がある (型チェック) ため、関数は適切な型の式でのみ使用できます (たとえば、上記のコードの関数実装では、関数のサイズを比較することしかできません)。 2 つの int 型データ)、マクロは整数、long 整数、浮動小数点型、および > と比較できるその他の型に適用できます。マクロは型に依存しません。
2. マクロの欠点 (関数と比較して):
1. マクロを使用するたびに、マクロ定義コードのコピーがプログラムに挿入されるため、比較的短いマクロでない限り、プログラムの長さが大幅に増加する可能性があります。
2. マクロはデバッグできません。
3. マクロは型に依存しないため、厳密さが不十分です。
4. マクロはオペレーターの優先順位の問題を引き起こし、プログラムでエラーが発生しやすくなる可能性があります。
3. マクロは、関数ではできないことも実行できます (たとえば、マクロのパラメータをtypeにすることができます)。
#include <stdio.h>
#include <stdlib.h>
#define MALLOC(num, type) (type*)malloc(num * sizeof(type))
int main()
{
int* p1 = (int*)malloc(sizeof(int));
if (p1 == NULL)
{
//...
}
//
int* p2 = MALLOC(10, int);
if (p2 == NULL)
{
//...
}
return 0;
}
4. 命名規則
一般に、関数とマクロの使用構文は非常に似ています。言語自体は、この 2 つを区別するのに役立つわけではありません。私たちの普段の習慣の 1 つは次のようになります。
1. マクロ名をすべて大文字にする
2. 関数名にはすべて大文字を使用しないでください。
注: offsetof はマクロです
5. マクロと関数の比較
必要な関数が比較的単純な場合はマクロを使用して実装できますが、関数がより複雑な場合は関数を使用して実装することをお勧めします。
C\C++ では、キーワードinline (インライン)、インライン関数が導入されています。これには、関数とマクロの両方の利点があります。
4. コマンドライン定義と条件付きコンパイル
1. コマンドライン定義
多くの C コンパイラは、コマンド ラインでシンボルを定義する機能を提供します。
2. 条件付きコンパイル
プログラムをコンパイルするときに、ステートメント (ステートメントのグループ) のコンパイルを放棄する必要がある場合は、条件付きコンパイルを使用できます。
一般的な条件付きコンパイル ディレクティブ:
1.一般形式
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
5. ファイルの内容
#include ディレクティブにより、インクルードされたファイルは、#include ディレクティブが出現する場所に実際に出現するかのようにコンパイルされます。この置換メソッドは、#define の定義に似ています。プリプロセッサは、最初にこのディレクティブを削除し、インクルードされたファイルのコンテンツを使用します。交換。
1. ヘッダファイルのインクルード方法
本地文件包含 #include "filename.h"
查找策略:先在源文件所在的目录下查找,如果未找到该头文件
编译器就像查找库函数头文件那样,在标准路径下去查找
库文件包含 #include <filename.h>
查找策略:直接去标准路径下查找,如果找不到就提示编译错误
2. ネストされたファイルのインクルード
add.h は、test1.c、test2.c、および test.c の共通モジュールです。
test1.h と test2.h はパブリック モジュール add.h を使用します。
test.c にはヘッダー ファイル test1.h と test.h も含まれており、そのうち test2.h は test.h に含まれます。
最終的に、ヘッダー ファイル add.h が test.c に 3 回インクルードされました。
デモ コードをプリコンパイルするためにヘッダー ファイルを繰り返しインクルードする
6. その他の前処理命令
『C 言語の詳細分析』という本には別の表があります。