编译和链接(下)

简介: 编译和链接(下)

#define 替换规则

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

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

注意:

  1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
  2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。

#和##

我们先来看这样一段代码:

#define PRINT(FORMAT, VALUE) printf("the value is "FORMAT"\n", VALUE);
int main()
{
  PRINT("%d", 10);
  return 0;
}

这里只有当字符串作为宏参数的时候才可以把字符串放在字符串中。

另外一个技巧是:

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

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

了解了# 之后我们来了解一下## 的作用:

##可以把位于它两边的符号合成一个符号。

它允许宏定义从分离的文本片段创建标识符。

#define ADD_TO_SUM(num, value) sum##num += value;
int main() {
    int sum5 = 0;
    ADD_TO_SUM(5, 10);
    printf("sum5: %d\n", sum5);
    return 0;
}

注意

这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。

宏和函数对比

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

比如:

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

为什么不用函数来完成这个任务?

原因有二:

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

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

3. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。

4. 宏是没法调试的。

5. 宏由于类型无关,也就不够严谨。

6. 宏可能会带来运算符优先级的问题,导致程容易出现错。

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

#define MALLOC(num, type) (type *)malloc(num * sizeof(type))
int main()
{
  MALLOC(10, int);//类型作为参数
  //预处理器替换之后:
  (int*)malloc(10 * sizeof(int));
  return 0;
}

宏和函数的一个对比:

命令行定义

许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。

例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)

#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;
}

条件编译

在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。

比如说:

调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译。

常见的条件编译指令:

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();
 #endi

文件包含

我们已经知道, #include 指令可以使另外一个文件被编译。就像它实际出现于 #include 指令的地方一样。

这种替换的方式很简单:

预处理器先删除这条指令,并用包含文件的内容替换。

这样一个源文件被包含10次,那就实际被编译10次。

嵌套文件包含

如果出现这样的场景:

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

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

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

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

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

我们想要解决这个问题,也很简单:

1.每个头文件的开头写:

#ifndef TEST_H

#define TEST_H //头文件的内容

#endif //TEST_H

2.#pragma once

今天的分享就到此位置了,各位看客老爷万福金安!

目录
相关文章
|
6月前
|
存储 自然语言处理 编译器
|
6月前
|
存储 自然语言处理 编译器
编译和链接(翻译环境:预编译+编译+汇编+链接​、运行环境)
编译和链接(翻译环境:预编译+编译+汇编+链接​、运行环境)
|
1月前
|
存储 自然语言处理 编译器
|
6月前
|
存储 编译器 Linux
什么是编译与链接呢!
什么是编译与链接呢!
39 0
|
5月前
|
存储 自然语言处理 前端开发
编译与链接(想了解编译与链接,那么看这一篇就足够了!)
编译与链接(想了解编译与链接,那么看这一篇就足够了!)
|
5月前
|
存储 自然语言处理 C语言
编译和链接
编译和链接
22 0
|
11月前
|
存储 自然语言处理 算法
程序的编译和链接
程序的编译和链接
47 0
|
6月前
|
自然语言处理 编译器 C语言
C语言程序编译和链接
在ANSI C的任何⼀种实现中,存在两个不同的环境。 第1种是翻译环境,在这个环境中源代码被转换为可执⾏的机器指令(⼆进制指令)。 第2种是执⾏环境,它⽤于实际执⾏代码。
30 0
|
存储 自然语言处理 程序员
编译和链接(上)
编译和链接(上)
43 0
|
自然语言处理 编译器 C语言
编译+链接和预处理
编译+链接和预处理