C言語での#defineと前処理の詳細説明

目次

#定義する

#defineの使い方 

 #define 置換ルール

 # と ## の役割

副作用のあるマクロ引数

マクロと関数 

#undef

ファイルには含まれています 

 ヘッダー ファイルがインクルードされる方法は次のとおりです。

ネストされたファイルには以下が含まれます


C 言語では、プログラムの実行時にプリコンパイル、コンパイル、アセンブリ、リンクという 4 つのステップが実行されます。プログラムがプリコンパイルされると、プログラムは前処理され、いくつかの前処理命令が実行されて置き換えられます。

事前定義されたシンボル:

__FILE__ //コンパイル用のソースファイル

__LINE__ //ファイルの現在の行番号

__DATE__ //ファイルがコンパイルされた日付

__TIME__ //ファイルがコンパイルされた時刻

__STDC__ //コンパイラが ANSI C に従っている場合、その値は 1 であり、それ以外の場合は未定義です

これらの事前定義されたシンボルは言語に組み込まれています。

これらは、ファイルのさまざまな情報を理解するためにプログラムで出力できます。

#include<stdio.h>

int main(void)
{
	printf("file:%s line:%d\n", __FILE__, __LINE__);
}

しかし、今日注目するのは #define 前処理ディレクティブです。! 

#定義する

#define 定義識別子:構文: #define 名前(マクロ名) スタッフ(マクロ値)

栗を取ります:

#define MAX 1000

#define reg register //キーワード register の短縮名を作成します

#define do_forever for(;;) //実装をより鮮明なシンボルに置き換えます

#define CASE Break;case //case文を書くときに自動的にbreakを書きます。 

#define DEBUG_PRINT printf("ファイル:%s\tline:%d\t \

日付:%s\t時刻:%s\n" ,\

__ファイル__、__LINE__、\

__日付時刻__ )   

 // 定義内容が長すぎる場合は、最後の行を除いて複数行に分けて記述することができます。各行の後にはバックスラッシュ (行継続文字) が続きます。

ここで 1 つの点を強調したいのですが、定義ロゴの後に追加しないでください

 もし:

#define MAX 1000;
if(condition)
 max = MAX;
else
 max = 0;
//在预处理时MAX会被替换
//就会变成下面代码:
if(condition)
 max = 1000;;
else
 max = 0;

ここで構文エラーが発生します。 

#defineの使い方 

 最初の構文は、シンボリック定数を定義するものです。

#define N 100
#define MAX 10000

N または MAX を使用すると、すべてのマクロ名がマクロ値に置き換えられます。後で #undef N (後述) または #undef MAX が発生した場合、この定義で定義された値は削除されます。

2 番目の構文は、シンボリック関数を定義する (マクロを定義する) ために使用されます。

#define メカニズムには、パラメーターをテキストに置換できるようにする機能が含まれており、この実装はマクロまたは定義マクロと呼ばれることがよくあります。

マクロの宣言方法は次のとおりです。

#define name(parament-list) のもの

parament-list は、スタッフに現れる可能性のあるシンボルのカンマ区切りのリストです。

知らせ:

パラメータリストの開き括弧は名前のすぐ隣になければなりません。2 つの間に空白が存在する場合、引数リストは引数の一部として解釈されます。

#define SQUARE( x ) x * x
//这个宏接收一个参数 x

int main()
{
    SQUARE( 5 );

    return 0;
}
//置于程序中,预处理器就会用下面这个表达式替换上面的表达式:5*5

これは最も基本的なマクロですが、マクロ内のシンボルの優先順位によってエラーが発生することがよくあります。

#define SQUARE( x ) x * x
//这个宏接收一个参数 x

int main()
{
   int a = 5;
   printf("%d\n" ,SQUARE( a + 1) );

    return 0;
}

一見すると、このコードは値 36 を出力すると思うかもしれません。実際には 11 が出力されます。どうしてこれなの?

テキストを置換する場合、パラメータ x は a + 1 に置換されるため、このステートメントは実際には printf ("%d\n",a + 1 * a + 1 ); となり、結果は 5+1*5+ 1= となります。 11.

この問題は、マクロ定義に 2 つの括弧を追加することで簡単に解決できます: #define SQUARE(x) (x) * (x)

数値式の評価に使用されるすべてのマクロ定義は、マクロの使用時に引数内の演算子または隣接する演算子間の予期しない相互作用を避けるために、この方法で括弧で囲む必要があります。

#define には多くの使用法がありますが、ここでは一般的な概要を説明します。

1. 静的配列の長さを示します

#define N 100 文字 arr[N];

2. 文字定数を定義する

#STOPFLA '#' を定義します

3. 文字列定数を定義する

#define PRINT "hello world."

4. マクロ(単純な関数)を定義する

#define ADD(X) ((X)+(X))

 #define 置換ルール

#define を展開してプログラム内のシンボルとマクロを定義する場合は、いくつかの手順が必要です。

1. マクロを呼び出すと、最初にパラメーターがチェックされ、#define で定義されたシンボルが含まれているかどうかが確認されます。「はい」の場合、最初に置き換えられます。

2. 置換テキストは、元のテキストの代わりにプログラムに挿入されます。マクロの場合、パラメータ名はその値に置き換えられます。

3. 最後に、結果のファイルが再度スキャンされ、#define で定義されたシンボルが含まれているかどうかが確認されます。「はい」の場合は、上記の処理を繰り返します。

知らせ:

1. 他の #define で定義されたシンボルは、マクロ パラメーターおよび #define 定義に出現できます。しかし、マクロでは再帰は発生しません。2. プリプロセッサが #define で定義されたシンボルを検索する場合、文字列定数の内容は検索されません。

 # と ## の役割

まず、このコードを見てみましょう。

char* p = "hello ""bit\n";
printf("hello"" world\n");
printf("%s", p)

ここで、printf はこの文字列を出力します。では、文字列にパラメータを挿入するにはどうすればよいでしょうか? 

# を使用して、マクロ パラメータを対応する文字列に変換します。

int i = 10;
#define PRINT(FORMAT, VALUE)\
 printf("the value of " #VALUE "is "FORMAT "\n", VALUE);
...
PRINT("%d", i+3);

 コード内の #VALUE は、プリプロセッサによって "VALUE" として処理されます。最終出力は次のようになります: i+3 の値は 13。

## の役割:

## 両側のシンボルを 1 つのシンボルに結合できます。これにより、マクロ定義で分離されたテキストの断片から識別子を作成できます。

#define ADD_TO_SUM(num, value) \
 sum##num += value;
...
ADD_TO_SUM(5, 10);//作用是:给sum5增加10.

このような接続では、有効な識別子が得られる必要があります。それ以外の場合、結果は未定義です。

副作用のあるマクロ引数

マクロ パラメータがマクロの定義内で複数回出現する場合、そのパラメータに副作用がある場合、このマクロの使用は危険であり、予測できない結果が生じる可能性があります。副作用は、式が評価されるときに発生する永続的な影響です。

例: x+1;//副作用なし x++;//副作用あり

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

#define MAX(a, b) ( (a) > (b) ? (a) : (b) )
...
x = 5;
y = 8;
z = MAX(x++, y++);
printf("x=%d y=%d z=%d\n", x, y, z);//输出的结果是什么?

z = ( (x++) > (y++) ? (x++) : (y++));

したがって、出力結果は次のようになります: x=6 y=10 z=9

置換により変数の値が複数回操作されるためです。

したがって、マクロを使用するときは、++、-- の使用を避ける必要があります。

マクロと関数 

マクロは通常、単純な操作を実行するために使用されます。たとえば、2 つの数値のうち大きい方を見つけるには、

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

では、このタスクに関数を使用してみてはいかがでしょうか?

1. 関数を呼び出したり関数から返したりするコードには、この小さな計算作業を実際に実行するのにかかる時間よりも時間がかかる場合があります。したがって、プログラムのサイズと速度の点では、マクロの方が関数よりも優れています。

2. さらに重要なのは、関数のパラメータを特定の型として宣言する必要があることです。したがって、関数は適切な型の式でのみ使用できます。逆に、このマクロは、> で比較できる整数、長整数、浮動小数点型などの型にどのように適用できますか。マクロはタイプに依存しません。

マクロの欠点: もちろん、関数と比較すると、マクロにも欠点があります。

1. マクロが使用されるたびに、マクロ定義コードのコピーがプログラムに挿入されます。マクロが比較的短い場合を除き、プログラムの長さが大幅に長くなる可能性があります。

2. マクロはデバッグできません。 

3. マクロはタイプに依存しないため、厳密さが十分ではありません。

4. マクロは演算子の優先順位に問題を引き起こす可能性があり、プログラミングでエラーが発生しやすくなります。

マクロは、関数ではできないことを実行できることがあります。たとえば、マクロパラメータには型を指定できますが、関数には型を指定できません。

マクロと関数の比較:

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

命名規則:

一般に、関数マクロの使用構文は非常に似ています。したがって、言語自体はこの 2 つを区別するのに役立ちません。

次に、私たちの通常の習慣の 1 つは、すべてのマクロ名を大文字にするが、関数名をすべて大文字にするわけではありません。

#undef

このコマンドはマクロ定義を削除するために使用されます。

#undef 名前

// 既存の名前を再定義する必要がある場合は、最初に古い名前を削除する必要があります。

ファイルには含まれています 

#include ディレクティブによって別のファイルがコンパイルされる可能性があることはすでにわかっています。#include ディレクティブに実際に表示される場所と同じです。

この置換の仕組みは簡単です。プリプロセッサはまずディレクティブを削除し、それをインクルード ファイルの内容に置き換えます。このようなソース ファイルは 10 回インクルードされ、実際には 10 回コンパイルされます。

 ヘッダー ファイルがインクルードされる方法は次のとおりです。

ローカルファイルには以下が含まれます

#include "ファイル名"

検索方法: まず、ソース ファイルが存在するディレクトリを検索します。ヘッダー ファイルが見つからない場合、コンパイラは、ライブラリ関数のヘッダー ファイルを検索するのと同じように、標準の場所でヘッダー ファイルを検索します。見つからない場合は、コンパイル エラーが表示されます。

ライブラリファイルには以下が含まれます

#include<stdio.h>

ヘッダー ファイルを見つけるには、標準パスに直接移動して見つけます。見つからない場合は、コンパイル エラーが表示されます。

ライブラリファイルに「」の形式は使用できますか?

答えは「はい」です。ただし、この方法では検索効率が低くなりますし、ライブラリ ファイルかローカル ファイルかを区別するのは当然容易ではありません。

ネストされたファイルには以下が含まれます

 このようなシナリオが発生した場合: comm.h と comm.c はパブリック モジュールです。test1.h と test1.c は共通のモジュールを使用します。test2.h と test2.c は共通のモジュールを使用します。test.h と test.c は test1 モジュールと test2 モジュールを使用します。このようにして、comm.h の 2 つのコピーが最終的なプログラムに表示されます。これにより、ファイルの内容が重複して作成されます。

この問題をどうやって解決すればいいでしょうか?

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif   //__TEST_H_

または:

#pragma once

ヘッダー ファイルの繰り返しの導入を避けることができます。


C言語にはまだまだ前処理命令がたくさんあり、まだまだ勉強していく必要があります。! 

おすすめ

転載: blog.csdn.net/m0_74755811/article/details/132009588
おすすめ