编译及预编译

前言

我在看网上很多开源的stm32工程的时候遇到了一些关于编译,预编译的代码。由于非计算机专业科班出身,对于这一块的原理知识理解不到位,故总结如下。

什么是c语言的编译

在网上搜索资料的时候看到了下面这段代码:

// hello.c
#include <stdio.h>
int main(){
    printf("hello world!\n");
}

在linux系统下编译此c程序,只需要:

$ gcc hello.c # 编译
$ ./a.out # 执行
hello world!

实际上以我微薄的计算机基础也知道编译过程是分好几步的,在《语言程序设计》靠前的位置应该有讲
图片来源:博客园博主CarpenterLee

从上图可知总共是分成四步来执行,分别是预编译,编译,汇编,链接。

预编译

预编译处理是将自己写的.c,.cpp,.h等文本文件转化成.i,.ii等文本文件。这个过程处理的是程序中#define #inculde #ifdef等等带“#”的命令行,其本质是做一些文本替换的工作。在c语言中有很多以#开头的命令:

#include

对于#include命令大家肯定都非常熟悉,该指令指示编译器将xxx.xxx文件的全部内容插入此处,本质上也是一种文本的代替。#include有两种用法,分别是#include “xxx.h”或者是#include <xxx.h>。若使用“”则预编译指令会首先从当前文件路径里寻找目标文件,若使用<>则编译器从类库路径里面寻求头文件。一般引用自己编写的头文件使用“”,引用类库比如string.h则使用<>。
实际上引用类库也可以使用“”,因为预编译时若在程序目录中找不到,则编译器会继续在类库等其他地方定位到头文件。

#define

(1)无参宏定义
如:#define m (a+b)
这是一种最简单的文本替换,可以是常数或者表达式,程序不对此进行任何检查,出错也是在宏展开后才能报错。
(2)带参宏定义
这种定义方式就像定义一个小型的函数,格式如下:
#define 宏定义名(形参列表) (形参函数)
例如:

#define m(a,b) ((a>b)?a:b)
int a = m(1,2)

这个宏定义能实现大小值的比较
那么在进行宏定义引用的时候就必须要用实参代替宏定义中的形参。这样的好处是方便程序修改,提高运行效率,是一种非常实用的函数定义方式。注意在编程的过程中尽量用小括号将形参函数框起来,避免因为优先级导致的程序错误。
宏定义因为其简单的定义所以能够实现一些“骚操作”。有一些任务根本无法用函数实现,但是用宏定义却很好实现。比如参数类型没法作为参数传递给函数,但是可以把参数类型传递给带参的宏:

  #define MALLOC(n, type) \
  ( (type *) malloc((n)* sizeof(type)))

多行定义
宏定义中允许包含两行以上命令的情形,此时必须在最右边加上”\”且该行”\”后不能再有任何字符,连注释部分都不能有。可以理解为,多行定义能够插入一大片的多行代码,并没有特别的机制。
但是当插入大段代码的时候往往会出现很多的问题。比如:

int a =1;
#define setcurrent(motor1,motor2) \
cansend(motor1); cansend(motor1);\

if(a)
setcurrent(id1,id2);
else
getcurrent(id1);

那么宏定义展开后的代码为:

if(a)
cansend(motor1); cansend(motor1);;
else
getcurrent(id1);

这里有两个问题。第一个是由于广大编程人员的习惯,每一句命令后面习惯性加分号,这是个好习惯,但是如果同时在宏定义里加就会出现问题。第二个是if后面只执行了第一个函数而没有执行第二个函数。所以,在宏定义大量的文本内容并且想将其模块化执行时,用do{}while(0);是最佳的方法。上述程序可以改成:

int a =1;
#define setcurrent(motor1,motor2) \
do{/
cansend(motor1);\
cansend(motor1);\
}while(0)\

if(a)
setcurrent(id1,id2);
else
getcurrent(id1);

注意,do{}while(0);语法在while(0)后是一定有分号的,这里为了让程序更美观,定义的时候没有加分号,而在使用宏定义的时候加了分号,在实际 使用过程中可以根据使用需求决定是否在宏定义里加分号。

#undef

#define宏定义的作用范围是从本句开始一直到程序结束,所以#undef的所用就是解除从#undef命令行之后的对应宏定义。
一般宏定义用大写字母来表示,并且程序中以双引号括起来的宏是不被程序识别的。

#ifdef #else #endif

这几句命令比较简单,比较典型的用法是在32标准库的头文件里,为了保证不同容量型号的芯片都能使用同一个头文件,文件中广泛使用了#ifdef来区分同一个系列芯片的不同型号。

#ifndf

在32头文件的书写中经常使用

#ifndef __xxx_H
#define __xxx_H
/*************/

#endif 

它的作用是防止在文件中被重复引用编译。假如在一个.c文件中头文件include一个头文件多次,由于第一次编译时deine 了该宏定义,所以在后面的#ifndef中逻辑不通过所以避免了重复定义在keil中报错。

结语

在stm32中,预编译的使用相当频繁,在32标准库中预编译占相当重的比例,所有的寄存器地址以及各种外设的地址全部是通过预编译来定义的。而在用户层中好的宏定义不仅可以使得代码更加可读和美观,更能降低代码整体的维护成本,将程序员从繁琐的底层中解放出来。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值