预处理指令、Makefile

预处理指令

程序员所编写的C代码不能直接被编译器编译,需要一段程序把C代码翻译成
标准C代码。
翻译的过程叫预处理,执行翻译的程序叫做预处理器,被翻译的代码叫做预处
理执行指令,以#开头的都是预处理指令。

查看预处理结果:
    gcc —E code.c  把预处理结果直接显示到终端上
    gcc -E code.c -o code.i  把预处理的结果存储到.i结尾的文件中
一、预处理指令的分类
1、文件包含
#include 
     #include <>    从系统指定的目录下查找头文件并导入
     #include ""   先从当前目录下查找头文件,如果找不到,再从系统
     指定的目录下查找头文件并导入

     编译参数 -I /路径 来指定头文件的加载路径
     系统指定目录是通过设置环境变量来指定 ~/.bashrc
2、宏常量
 #define 宏名 字面值数据 (末尾不加分号)    例:#define  MAX 100
 如果在代码中使用了宏,在预处理时会把所有的宏替换成宏名后面的
 字面值数据
 
 优点:提高可扩展性(批量性修改)、因为是常量,提高了安全性、
 提高可读性、可以与case配合使用
 
 注意:  一般宏名全部大写,末尾不加分号,不能直接换行(可以
 使用续行符\)
 
 预定义的宏:
     __func__    获取函数名  %s
     __FILE__    获取文件名  %s
     __DATE__    获取当前日期    %s
     __TIME__    获取当前时间    %s
     __LINE__    获取当前行号    %d
3、宏函数
#define FUNC(arg)  arg*10
带参数的宏,不是真正的函数,使用宏函数的位置会被替代成宏函数
后面的代码,提供的参数会被替换到相应的参数的位置

注意:宏函数也可以使用大括号来保护代码
不检查参数的类型,没有传参,没有返回值,只有计算结果
    #define SUM (a,b) a+b
    1、把代码中使用的宏函数替换为宏函数后面的代码 a+b
    2、把宏函数代码中使用的参数替换为调用者提供的参数
注意:宏函数不能直接换行,如果需要换行要在末尾加续行符\

宏的二义性:
    由于宏代码所处的位置、参数不同导致宏有不同的功能,这叫做宏的二义性
    如何避免宏的二义性:
        宏函数整体加小括号、每个参数都要加小括号
        注意:使用宏函数时不要提供带自变运算符的变量作为参数

    运算符:
        # 把宏函数的参数变成字符串
        ##合并两个参数变成标识符

实现一个交换两个变量的宏函数

#define swap(a,b)   {typeof(a) temp = (a);(a) = (b);(b) = temp;}  
4、条件编译:(顶格写)

根据条件让代码是否参与最终的编译

    头文件卫士:防止头文件被重复包含 
    #ifndef FILENAME_H
    #define FILENAME_H
    #endif//FILENAME_H

    版本控制
    #if VERSON >=3
    #elif VERSON >=2
    #elif VERSON >=1
    #else
    #endif

    编译器控制
    #if __cplusplus
        printf("C++编译器\n");
    #else
        printf("C编译器\n");
    #endif

    #if __x86_64__
        printf("64位操作系统\n");
    #elif __i386__
        printf("32位操作系统\n");
    #endif

    判断、调试、上线    gcc code.c -DDBUG  //-D编译时定义宏
    #ifdef
    #else
    #endif

    定义打印调试信息的宏函数
    #include <stdio.h>
    #ifdef DEBUG
        #define debug(...)  printf(__VA_ARGS__)
    #else
        #define debug(...)
    #endif

    定义打印错误信息的宏函数
    以上两个宏函数会使用即可
    #define error(...) printf("%s %s %s:%d:%m %s %s\n",\
     __FILE__,__func__,__VA_ARGS__,__LINE__,__DATE__,__TIME__)

    int main(int argc,const char* argv[])
    {
        int num  = 10; 
        int ret  = scanf("%d",&num);
        debug("ret=%d\n",ret);

    }
5、常见的笔试面试题:
1、定义一个100年有多少秒的宏
    #define YEAR_SEC (3600*24*365*100u)

2、在类型重定义时#define 和 typedef 区别?
    如果是普通类型,他们的功能没有任何区别,
    #define INT int
    typedef int INT

    如果是指针类型
    #define INTP int*
    typedef int* INTP
    例:
    INTP p1,p2,p3;
        如果是#define     只有p1是指针变量,p2 p3都是int类型变量
        如果是typedef     p1p2p3都是指针变量  

    如果:
    typedef int* pint;
    #define PINT int*

    那么:
    const pint p ;//p不可更改,但p指向的内容可更改
    const PINT p ;//p可更改,但是p指向的内容不可更改。

    pint是一种指针类型 const pint p 就是把指针给锁住了 p不可更改
    而const PINT p 是const int* p 锁的是指针p所指的对象。


3、定义常量时,#define和const的区别

    (1) 编译器处理方式不同
      define宏是在预处理阶段展开。
      const常量是编译运行阶段使用。

    (2) 类型和安全检查不同
      define宏没有类型,不做任何类型检查,仅仅是展开。
      const常量有具体的类型,在编译阶段会执行类型检查。

    (3) 存储方式不同
      define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。
      (宏定义不分配内存,变量定义分配内存。
      const常量会在内存中分配(可以是堆中也可以是栈中)。

    (4)const  可以节省空间,避免不必要的内存分配。


4、宏函数与函数的区别?
    他们是什么?
        宏函数:不是真正的函数,只是代码替换,只是用法像函数
        函数:一段具有某项功能代码的集合,会被编译成二进制指令,存储到
        代码段中,函数名就是该段内存的首地址、有独立的命名空间、栈空间

    有什么不一样?
        函数:  有返回值、   类型检查、安全、入栈、出栈、速度慢、跳转
        宏函数:只有运算结果、  通用、 危险、替换、    、速度快、冗余
二、头文件
    重点:头文件中只能编写声明语句,不能有定义语句
    全局变量的声明
    函数声明
    宏常量
    宏函数
    typedef 类型重定义
    结构、联合、枚举的类型声明
头文件的编写规则:
1、为每个.c文件写一份.h文件,.h文件是对.c文件的说明
2、如果需要用到某个.c文件的变量、函数、宏,只需要把它的头文件导入即可
3、.c文件也需要导入自己的.h文件,目的是为了让声明与定义一致
头文件的相互包含:
假如a.h中包含了b.h的内容,而b.h又需要包含a.h的内容,这种会导致编译出错

错误 :未知的类型名‘xxx’,一般都是头文件相互包含所导致的,也可能是复制
粘贴时头文件宏名忘记改

解决方法:把a.h中需要的内容和b.h中需要的内容提取出来,写成一个c.h
三、Makefile
Makefile是由一系列编译指令组成的可执行文件,也叫做编译脚本
在终端执行make命令会自动执行Makefile脚本中的编译指令,它可以根据文件的最后修改时间,
来判断哪些文件需要被编译,哪些文件不需要被编译,从而大大的提高编译效率

编译规则:
    1、如果该项目没有被编译过,则编译全部的.c文件,并链接成可执行文件
    2、如果某些.c文件被修改,则只编译修改过的.c文件,并重新链接新的可执行文件
    3、如果某些.h文件被修改,所有依赖它的.c都要重新编译并链接

一个最简单的Makefile脚本:
执行目标:依赖
编译指令
被依赖的目标1:依赖的文件
编译指令
被依赖的目标2:依赖的文件
编译指令

负责清理的执行目标:
rm …

通用的Makefile脚本
 CC=gcc
STD=-std=gnu99
LIB=-lm
INC=-I./include
BIN=SAMS
FLAG=-Wall -Werror
OBJ=admin.o dao.o list.o main.o sams.o student.o teacher.o tools.o

all:$(OBJ)
	$(CC) $(OBJ) $(LIB) -o $(BIN)

%.o:%.c
	$(CC) $(STD) $(FLAG) $(INC) -c $< -o $@

clean:
	rm -rf $(OBJ) $(BIN)
		

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: `add_definitions`是在Makefile中用于向编译器添加预定义宏的命令。它的语法如下: ``` add_definitions(-DDEFINITION) ``` 其中,`-DDEFINITION`表示要添加的预定义宏,可以有多个。例如,如果我们想要在编译时定义一个名为`DEBUG`的宏,可以在Makefile中添加以下行: ``` CFLAGS += -g -Wall add_definitions(-DDEBUG) ``` 这将向编译器添加`-DDEBUG`选项,并将`DEBUG`宏定义为`1`。这样,在代码中就可以使用`#ifdef DEBUG`等条件编译语句来控制调试信息的输出。 ### 回答2: makefile中的add_definitions指令用于添加预处理指令定义到编译选项中。这个指令可以在编译过程中向C/C++编译器传递一些宏定义,例如定义一些宏来控制程序中的条件编译。具体而言,通过在makefile中使用add_definitions进行宏定义,可以在程序编译时将这些宏定义加入到编译选项中。 使用add_definitions指令时,我们可以通过以下格式来添加宏定义: ``` add_definitions(-D宏名=宏值) ``` 其中,宏名是要定义的宏的名称,宏值是宏的取值。在使用时,需要注意宏值中有些特殊字符需要进行转义处理,例如引号或者反斜杠。 将add_definitions指令添加到makefile中,能够帮助我们在编译过程中对代码进行更灵活的控制。它可以用于定义一些全局宏,或者通过不同的编译选项来控制代码的编译行为,例如调试模式与发布模式之间的切换、针对不同平台的适配等。 总之,通过在makefile中使用add_definitions指令,我们可以方便地向编译选项中添加宏定义,从而实现对代码编译过程的灵活控制。 ### 回答3: 在Makefile中,add_definitions是一个用于向编译器添加预定义的宏定义的命令。它可以将一些预定义的宏定义添加到编译器的命令行参数中。 通过使用add_definitions命令,我们可以将一些标识符定义为宏,并在编译时将其传递给编译器。这样做的好处是,我们可以在不修改源代码的情况下,为编译器提供一些额外的宏定义,从而可以在编译时对代码进行一些自定义的配置。 在Makefile中,使用add_definitions的语法如下: ``` CFLAGS += -D<macro> ``` 其中,CFLAGS是一个存储编译器参数的变量,通过+=可以将参数追加到该变量中。-D<macro>表示将<macro>定义为宏。 例如,我们可以在Makefile中使用add_definitions来定义一个名为DEBUG的宏,示例如下: ``` CFLAGS += -DDEBUG ``` 通过上述定义,我们可以在编译时传递宏定义给编译器,从而可以在代码中使用#ifdef和#ifndef等条件编译指令,根据宏的定义与否来选择性地编译不同的代码块。 总而言之,add_definitions是Makefile中用于向编译器添加预定义宏定义的命令,可以通过该命令将一些标识符定义为宏,并将其传递给编译器。这样可以在编译时对代码进行一些自定义的配置,而不需要修改源代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值