【预处理和程序环境】

简介: 【预处理和程序环境】

一、程序的翻译环境和执行环境

我们的代码写完后称为源代码,源代码一般都要经过由编译器和链接器组成的翻译环境中,翻译成二进制的指令(机器指令),再进入执行环境一步步执行代码。

二、详解编译+链接

其中,在编译过程中,符号汇总是指将所有.c文件的函数名汇总在一起;在汇编过程中的形成符号表是指,将所有.c文件中的函数都给一个地址,其中,在主函数中的函数如果只有声明时,会给一个无效的地址,例如空地址,但是它的有效地址可能在另一个.c文件中定义了;然后会在链接器中的符号表的合并和重定位中,取它的有效地址;在链接器中的合并段表中,将所有的.o文件(目标文件)一一对应的匹配合并,目标文件是有格式的,它会按照格式一一对应的合并;

三、#define

1. #define定义标识符

例如:

#define MAX 1000
     int main()
     {
      printf("%d\n", MAX);
      int arr[MAX];
      return 0;
     }

2. #define定义宏

#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏或定义宏。

例如:

#define SQUARE(x)  ((x)*(x))    //注意SQUARE的后面不能接空格,要直接用括号括住参数
    int main()
    {
        printf("%d\n", SQUARE(1+7));
        return 0;
    }

在使用#define定义宏时,要尽量多的去使用括号,避免在替换过程中符号的优先级影响计算的结果;

3. #define的替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。

  1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
  2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
  3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。

下面这个程序,首先替换#define定义的宏DOUBLE(x) ,再进行其他计算;

#define DOUBLE(x) ((x)+(x))
    int main()
    {
        int a = 10*DOUBLE(4);
        printf("%d\n", a);
        return 0;
    }

4. #和##

4.1 #的使用

使用 # ,把一个宏参数变成对应的字符串

//  \是续行符,把下一行的代码续上上一行,实际上还是一行代码
    #define print_format(num, format) \
                printf("the value of "#num" is "format, num)
    int main()
    {
        int a = 10;
        print_format(a, "%d\n");//the value of a is 10
        //printf("the value of a is %d\n", a);
        int b = 20;
        print_format(b, "%d\n");//the value of b is 20
        //printf("the value of b is %d\n", b);
        float f = 3.14f;
        print_format(f, "%f\n");//the value of f is 3.140000
        //printf("the value of f is %f\n", f);
        return 0;
    }

4.2 ##的使用

##可以把位于它两边的符号合成一个符号。它允许宏定义从分离的文本片段创建标识符.

int Class109 = 2022;

//将参数Class和109合成一个
    #define CAT(x,y) x##y
    //Class109
    int main()
    {
        printf("%d\n", CAT(Class, 109));  //2022
        return 0;
    }

四、宏和函数对比

宏通常被应用于执行简单的运算;

比如在两个数中找出较大的一个:#define MAX(a, b) ((a)>(b)?(a):(b))

这里不用函数的原因:

  1. 用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多。所以宏比函数在程序的规模和速度方面更胜一筹。
  2. 更为重要的是函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使用。反之这个宏怎可以适用于整形、长整型、浮点型等可以用于>来比较的类型。宏是类型无关的。

宏的缺点:

当然和函数相比宏也有劣势的地方:

  1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。
  2. 宏是没法调试的。
  3. 宏由于类型无关,也就不够严谨。
  4. 宏可能会带来运算符优先级的问题,导致程容易出现错。

但是宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到;eg:

#define MALLOC(num,type)    (type*)malloc(num * sizeof(type))
    int main()
    {
      int* p = (int*)malloc(10 * sizeof(int));
      assert(p);
      //实现一个宏表示malloc
      int* p2 = MALLOC(10, int);
      assert(p2);
      return 0;
    }

五、条件编译

  1. #if 遇到 #endif 就会结束
  2. #if defined(symbol)
    #ifdef symbol
    只要symbol被定义,这个条件就为真,symbol为0也为真;
    #if !defined(symbol)
    #ifndef symbol
    如果symbol没有被定义,这个条件就为真
#if 常量表达式
   //...
  #endif
  //常量表达式由预处理器求值。
  如:
  #define __DEBUG__ 1
  #if __DEBUG__
   //..
  #endif
  2.多个分支的条件编译
  #if 常量表达式
   //...
  #elif 常量表达式
   //...
  #else
   //...
  #endif
  3.判断是否被定义
  //只要symbol被定义,这个条件就为真,symbol为0也为真;
  #if defined(symbol)
  #ifdef symbol
  //如果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
目录
相关文章
|
10天前
|
存储 自然语言处理 程序员
程序环境和预处理(1)
程序环境和预处理(1)
24 0
|
10天前
|
编译器 Linux C语言
程序环境和预处理(2)
程序环境和预处理(2)
22 0
|
2月前
|
存储 编译器 程序员
程序环境和预处理
程序环境和预处理
|
2月前
|
存储 编译器 程序员
零基础也能学会的『程序环境和预处理』
零基础也能学会的『程序环境和预处理』
|
2月前
|
存储 自然语言处理 编译器
程序环境和预处理(详解)
程序环境和预处理(详解)
|
11月前
|
存储 自然语言处理 程序员
【程序环境与预处理】(一)
【程序环境与预处理】(一)
63 0
|
2月前
|
编译器 Linux C++
【程序环境与预处理玩转指南】(下)
【程序环境与预处理玩转指南】
|
2月前
|
存储 编译器 程序员
【程序环境与预处理玩转指南】(上)
【程序环境与预处理玩转指南】
|
2月前
【程序环境与预处理玩转指南】(中)
【程序环境与预处理玩转指南】
|
2月前
|
存储 自然语言处理 编译器
程序环境+预处理
程序环境+预处理
58 0