[C Language] Preprocessing Detailed Explanation

        This article directory

1 Predefined symbols

2 #define

2.1 #define definition identifier

2.2 #define define macro

2.3 #define substitution rules

2.4 # and ##

2.5 Macro arguments with side effects

2.6 Comparison of macros and functions

2.7 Naming Conventions

3 #undef

4 Command line definition

5 Conditional compilation

6 files contain

6.1 How header files are included

6.2 Nested file includes


1 Predefined symbols

__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义

These predefined symbols are built-in to the language.

Take a chestnut:

printf("file:%s line:%d\n", __FILE__, __LINE__);

2 #define

2.1 #define definition identifier

//语法:
#define name stuff

Take a chestnut:

#define MAX 1000
#define reg register //为 register这个关键字,创建一个简短的名字
#define do_forever for(;;) //用更形象的符号来替换一种实现
#define CASE break;case //在写case语句的时候自动把 break写上。
// 如果定义的 stuff过长,可以分成几行写,除了最后一行外,每行的后面都加一个反斜杠(续行符)。
#define DEBUG_PRINT printf("file:%s\tline:%d\t    \
                            date:%s\ttime:%s\n",  \
                            __FILE__,__LINE__,    \
                            __DATE__,__TIME__)

Ask:

When defining an identifier, do you want to add it at the end ; ?

for example:

#define MAX 1000;
#define MAX 1000

It is recommended not to add ; , which will easily lead to problems.

For example, the following scenario:

if (condition)
	max = MAX;
else
	max = 0;

There will be a syntax error here.

2.2 #define define macro

The #define mechanism includes a provision that allows parameters to be substituted into the text, and this implementation is often called a macro or define macro.

Here is how the macro is declared:

#define name( parament-list ) stuff

where parament-list is a comma-separated list of symbols that may appear in stuff.

Notice:

The opening parenthesis of the parameter list must be immediately adjacent to name.

If any white space exists between the two, the argument list is interpreted as part of stuff.

like:

#define SQUARE( x ) x * x

This macro takes one parameter x ,

If after the above statement, you put

SQUARE( 5 );

Placed in a program, the preprocessor will replace the above expression with the following expression:

5 * 5

warn:

There is a problem with this macro:

Observe the code snippet below:

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

At first glance, you might think that this code will print the value 36.

In fact, it will print 11

Why?

When replacing text, the parameter x is replaced with a + 1, so this statement effectively becomes:

printf("%d\n", a + 1 * a + 1);

This makes it clear that the expressions produced by the substitution are not evaluated in the expected order.

Adding two parentheses to the macro definition solves this problem easily:

#define SQUARE(x) (x) * (x)

After preprocessing in this way, the expected effect is produced:

printf("%d\n", (a + 1) * (a + 1));

Here is another macro definition:

#define DOUBLE(x) (x) + (x)

We used parentheses in the definition to avoid the previous problem, but this macro may have new errors.

int a = 5;
printf("%d\n", 10 * DOUBLE(a));

What value will this print?

warning:

It looks like it prints 100, but in fact it prints 55,

We find that after replacing:

printf("%d\n", 10 * (5) + (5));

The multiplication operation precedes the addition defined by the macro, so the

 55 

The solution to this problem is to add a pair of parentheses around the macro definition expression.

#define DOUBLE( x ) ( ( x ) + ( x ) )

hint:

All macro definitions used to evaluate numeric expressions should be parenthesized in this way to avoid unforeseen interactions between operators in arguments or adjacent operators when using the macro.

2.3 #define substitution rules

There are several steps involved when expanding #defines to define symbols and macros in a program.

  1. When calling a macro, the arguments are first checked to see if they contain any symbols defined by #define. If yes, they are replaced first.
  2. The replacement text is then inserted into the program in place of the original text. For macros, parameter names are replaced by their values.
  3. Finally, the resulting file is scanned again to see if it contains any symbols defined by #define. If yes, repeat the above processing.

Notice:

  1. Other #define-defined symbols can appear in macro parameters and #define definitions. But with macros, recursion cannot occur.
  2. When the preprocessor searches for symbols defined by #define, the contents of string constants are not searched.

2.4 # and ##

How to insert parameters into the string?

First let's look at this code:

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

Is the output here

 hello bit

The answer is definite: yes.

We found that strings are characterized by automatic connection.

        1. Can we write such code?

#define PRINT(FORMAT, VALUE)\
    printf("the value is "FORMAT"\n", VALUE);
...
PRINT("%d", 10);

Here only when the string is used as a macro parameter can the string be placed in the string.

        2. Another trick is:

            Use # to turn a macro parameter into a corresponding string .

            for example:

int i = 10;
#define PRINT(FORMAT, VALUE)\
    printf("the value of "#VALUE" is "FORMAT"\n", VALUE);
...
PRINT("%d", i+3);//产生了什么效果?

The #VALUE in the code will be processed by the preprocessor as:

"VALUE"

The final output should be:

the value of i+3 is 13

## role

## You can combine the symbols on both sides of it into one symbol.

It allows macro definitions to create identifiers from separated text fragments.

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

Note:

Such a connection must result in a valid identifier. Otherwise the result is undefined.

2.5 Macro arguments with side effects

When a macro parameter appears more than once in the definition of the macro, if the parameter has side effects, then you may be dangerous when using this macro, resulting in unpredictable consequences. Side effects are permanent effects that occur when an expression is evaluated.

For example:

x+1;//不带副作用
x++;//带有副作用

The MAX macro can demonstrate problems caused by parameters with side effects.

#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);//输出的结果是什么?

Here we need to know what the result of the preprocessor is:

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

So the output is:

x=6 y=10 z=9

2.6 Comparison of macros and functions

Macros are usually used to perform simple operations.

For example, find the larger of two numbers.

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

So why not use a function for this task?

There are two reasons:

  1. The code to call and return from a function may take more time than it takes to actually perform this small computational work.
    So macros are better than functions in terms of program size and speed.
  2. More importantly, the parameters of the function must be declared as specific types.
    So functions can only be used on expressions of the appropriate type. On the contrary, how can this macro be applicable to types such as integers, long integers, and floating-point types that can be used for > to compare.
    Macros are type independent.

Disadvantages of macros: Of course, compared with functions, macros also have disadvantages:

  1. Every time a macro is used, a copy of the code defined by the macro is inserted into the program. Unless the macro is relatively short, it can increase the length of the program considerably.
  2. Macros cannot be debugged.
  3. Macros are not rigorous enough because they are type-independent.
  4. Macros may introduce operator precedence issues, making programs prone to errors.

Macros can sometimes do things that functions cannot. For example: macro parameters can have types , but functions cannot.

#define MALLOC(num, type)\
    (type*)malloc(num * sizeof(type))
...
//使用
MALLOC(10, int);//类型作为参数

//预处理器替换之后:
(int*)malloc(10 * sizeof(int));

A comparison of macros and functions

belongs to

sex

#defineDefine macros function

generation

code

long

Every time

Macro code is inserted into the program each time it is used. Except for very small macros, the length of the program can grow substantially The function code appears in only one place; every time the function is used, the same code in that place is called

hold

OK

speed

Every time

faster There is additional overhead for function calls and returns, so it is relatively slow

Hold

do

symbol

excellent

First

class

The evaluation of macro parameters is in the context of all surrounding expressions. Unless parentheses are added, the precedence of adjacent operators may have unpredictable consequences, so it is recommended to use more parentheses when writing macros Function parameters are evaluated only once when the function is called, and its resulting value is passed to the function. The result of evaluating an expression is more predictable

bring

have

vice

do

parameters used

Parameters may be substituted in multiple places in the macro body, so parameter evaluation with side effects may produce unpredictable results Function parameters are only evaluated once when passing parameters, and the result is easier to control
Parameter Type The parameters of the macro have nothing to do with the type, as long as the operation on the parameters is legal, it can be used for any parameter type The parameters of a function are related to the type. If the types of the parameters are different, different functions are required, even if they perform the same task.
debugging Macros are inconvenient to debug Functions can be debugged statement by statement
recursion Macros cannot be recursive functions can be recursive

2.7 Naming Conventions

In general, the usage syntax of function macros is very similar. So language itself cannot help us distinguish between the two.

Then one of our usual habits is:

capitalize macro names

Do not use all uppercase function names

3 #undef

This command is used to remove a macro definition.

#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除。

4 Command line definition

Many C compilers provide the ability to define symbols on the command line. Used to start the compilation process.

For example: this feature is useful when we want to compile different versions of a program based on the same source file. (Suppose an array of a certain length is declared in a program. If the machine memory is limited, we need a small array, but if another machine has a larger memory, we need an array that can be larger.)

#include <stdio.h>
int main()
{
	int array[ARRAY_SIZE];
	int i = 0;
	for (i = 0; i < ARRAY_SIZE; i++)
	{
		array[i] = i;
	}
	for (i = 0; i < ARRAY_SIZE; i++)
	{
		printf("%d ", array[i]);
	}
	printf("\n");
	return 0;
}

Compile instructions:

//linux 环境演示
gcc -D ARRAY_SIZE=10 programe.c

5 Conditional compilation

When compiling a program, it is very convenient if we want to compile or discard a statement (a group of statements). Because we have conditional compilation directives.

For example:

For debugging code, it is a pity to delete it, but to keep it in the way, so we can selectively compile it.

#include <stdio.h>
#define __DEBUG__

int main()
{
	int i = 0;
	int arr[10] = { 0 };
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef __DEBUG__
		printf("%d\n", arr[i]);//为了观察数组是否赋值成功。
#endif //__DEBUG__
	}
	return 0;
}

Common conditional compilation directives:

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

6 files contain

We already know that the #include directive can cause another file to be compiled. Just like where it actually appears in the #include directive.

This replacement is simple:

The preprocessor first removes this directive and replaces it with the contents of the include file.

Such a source file is included 10 times, it is actually compiled 10 times.

6.1 How header files are included

  • The local file contains
#include "filename"

查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件。

如果找不到就提示编译错误。

Linux环境的标准头文件的路径:

/usr/include

VS环境的标准头文件的路径:

C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//这是VS2013的默认路径

注意按照自己的安装路径去找。

  • 库文件包含
#include <filename.h>

查找头文件直接去标准路径下去查找,如果找不到就提示编译错误。

这样是不是可以说,对于库文件也可以使用 “” 的形式包含?

答案是肯定的,可以

但是这样做查找的效率就低些,当然这样也不容易区分是库文件还是本地文件了。

6.2 嵌套文件包含

如果出现这样的场景:

comm.h 和 comm.c 是公共模块。

test1.h 和 test1.c 使用了公共模块。

test2.h 和 test2.c 使用了公共模块。

test.h 和 test.c 使用了 test1 模块和 test2 模块。

这样最终程序中就会出现两份 comm.h 的内容。这样就造成了文件内容的重复。

如何解决这个问题?

答案:条件编译。

每个头文件的开头写:

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

或者:

#pragma once

就可以避免头文件的重复引入。


本文完

Guess you like

Origin blog.csdn.net/m0_73156359/article/details/132126217