以下内容源于朱有鹏嵌入式课程的学习,如有侵权,请告知删除。

一、C语言预处理理论

1、由源码到可执行程序的过程

  • 源码.c->(编译)->elf可执行程序
  • 源码.c->(编译)->目标文件.o->(链接)->elf可执行程序
  • 源码.c->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序
  • 源码.c->(预处理)->预处理过的.i源文件->(编译)->汇编文件.S->(汇编)->目标文件.o->(链接)->elf可执行程序
  • 预处理用预处理器,编译用编译器,汇编用汇编器,链接用链接器,这几个工具再加上其他一些额外的会用到的可用工具,合起来叫编译工具链。
  • gcc就是一个编译工具链。

2、预处理的意义

(1)编译器本身的主要目的是编译源代码,将C的源代码转化成.S的汇编代码。编译器聚焦核心功能后,就剥离出了一些非核心的功能到预处理器去。

(2)预处理器帮编译器做一些编译前的杂事。

3、编程中常见的预处理

(1)头文件包含:#include(#include <>和#include ""的区别);

(2)注释;

(3)条件编译:#if #elif #endif, #ifdef

(4)宏定义

4、gcc中只预处理不编译的方法

  • gcc编译时可以给一些参数来做一些设置。
  • 譬如gcc xx.c -o xx可以指定可执行程序的名称;
  • 譬如gcc xx.c -c -o xx.o可以指定只编译不连接,也可以生成.o的目标文件。
  • 譬如gcc -E xx.c -o xx.i可以实现只预处理不编译。一般情况下没必要只预处理不编译,但有时候这种技巧可以用来帮助我们研究预处理过程,帮助debug程序。

5、总结

  • 宏定义被预处理时的现象有:第一,宏定义语句本身不见了(可见编译器根本就不认识#define,编译器根本不知道还有个宏定义);第二,typedef重命名语言还在,说明它和宏定义是有本质区别的(说明typedef是由编译器来处理而不是预处理器处理的);

二、C语言预处理代码实战

1、头文件包含

(1)#include <> 和 #include""的区别

  • <>专门用来包含系统提供的头文件(就是系统自带的,不是程序员自己写的),""用来包含自己写的头文件;
  • 更深层次来说:<>的话C语言编译器只会到系统指定目录(编译器中配置的或者操作系统配置的寻找目录,譬如在ubuntu中是/usr/include目录,编译器还允许用-I来附加指定其他的包含路径)去寻找这个头文件(隐含意思就是不会找当前目录下),如果找不到就会提示这个头文件不存在。
  • ""包含的头文件,编译器默认会先在当前目录下寻找相应的头文件,如果没找到然后再到系统指定目录去寻找,如果还没找到则提示文件不存在。
  • 规则虽然允许用双引号来包含系统指定目录,但是一般的使用原则是:如果是系统指定的自带的用<>,如果是自己写的在当前目录下放着用"",如果是自己写的但是集中放在了一起专门存放头文件的目录下将来在编译器中用-I参数来寻找,这种情况下用<>。

(2)头文件包含的真实含义就是:在#include<xx.h>的那一行,将xx.h这个头文件的内容原地展开替换这一行#include语句。过程在预处理中进行。

2、注释

  • 注释是给人看的,不是给编译器看的。
  • 在预处理阶段,预处理器会拿掉程序中所有的注释语句,到了编译器编译阶段程序中其实已经没有注释了。

3、条件编译

  • 条件编译中用的两种条件判定方法分别是#ifdef 和 #if

三、宏定义

1、宏定义的规则和使用解析

(1)宏定义的解析规则:在预处理阶段由预处理器进行替换,这个替换是原封不动的替换。

(2)宏可以带参数,称为带参宏。

  • 在定义带参宏时,每一个参数在宏体中引用时都必须加括号,最后整体再加括号,括号缺一不可。

2、宏定义示例1:MAX宏

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

(1)要想到使用三目运算符来完成。

(2)注意括号的使用

3、宏定义示例2:SEC_PER_YEAR

  • #define SEC_PER_YEAR (365*24*60*60UL)

(1)当一个数字直接出现在程序中时,它的是类型默认是int;

(2)一年有多少秒,这个数字刚好超过了int类型存储的范围.

4、带参宏和带参函数的区别(宏定义的缺陷)

(1)宏定义是在预处理期间处理的,而函数是在编译期间处理的。

  • 这个区别带来的实质差异是:宏定义最终是在调用宏的地方把宏体原地展开,而函数是在调用函数处跳转到函数中去执行,执行完后再跳转回来。
  • 宏定义是原地展开,因此没有调用开销;而函数是跳转执行再返回,因此函数有比较大的调用开销。所以宏定义和函数相比,优势就是没有调用开销,没有传参开销,所以当函数体很短(尤其是只有一句话时)可以用宏定义来替代,这样效率高。

(2)带参宏和带参函数的一个重要差别

  • 宏定义不会检查参数的类型,返回值也不会附带类型。用宏的时候程序员必须很注意实际传参和宏所希望的参数类型一致,否则可能编译不报错但是运行有误。
  • 函数有明确的参数类型和返回值类型。当我们调用函数时编译器会帮我们做参数的静态类型检查,如果编译器发现我们实际传参和参数声明不同时会报警告或错误。用函数的时候程序员不太用操心类型不匹配因为编译器会检查,如果不匹配编译器会叫。
  • 宏和函数各有千秋,各有优劣。总的来说,如果代码比较多用函数适合而且不影响效率;但是对于那些只有一两句话的函数开销就太大了,适合用带参宏。但是用带参宏又有缺点:不检查参数类型。

5、内联函数和inline关键字

(1)内联函数通过在函数定义前加inline关键字实现。

(2)“内联函数就是带了参数静态类型检查的宏。”

  • 内联函数本质上是函数,所以有函数的优点(内联函数是编译器负责处理的,编译器可以帮我们做参数的静态类型检查);
  • 但是同时也有带参宏的优点(不用调用开销,而是原地展开)。
  • 当我们的函数内函数体很短(譬如只有一两句话)的时候,希望利用编译器的参数类型检查来排错,又希望没有调用开销时,最适合使用内联函数。

6、宏定义来实现条件编译(#define #undef #ifdef)

  • 程序有DEBUG版本和RELEASE版本,区别就是编译时有无定义DEBUG宏。

三、函数库

1、什么是函数库?

  • 函数库就是一些事先写好的函数的集合,给别人复用。

2、函数库的提供形式:动态链接库与静态链接库

(1)早期的函数共享都是以源代码的形式进行的。

  • 这种方式共享是最彻底的(后来这种源码共享的方向就形成了我们现在的开源社区)。
  • 缺点是无法以商业化形式来发布函数库。

(2)以库(主要有2种:静态库和动态库)的形式来提供。

  • 比较早出现的是静态链接库。静态库其实就是商业公司将自己的函数库源代码经过只编译不连接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件)。商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用;客户拿到.a和.h文件后,通过.h头文件得知库中的库函数的原型,然后在自己的.c文件中直接调用这些库文件,在连接的时候链接器会去.a文件中拿出被调用的那个函数的编译后的.o二进制代码段链接进去形成最终的可执行程序。
  • 动态链接库比静态链接库出现的晚一些,效率更高一些,是改进型的。现在我们一般都是使用动态库。静态库在用户链接自己的可执行程序时就已经把调用的库中的函数的代码段链接进最终可执行程序中了,这样好处是可以执行,坏处是太占地方了。尤其是有多个应用程序都使用了这个库函数时,实际上在多个应用程序最后生成的可执行程序中都各自有一份这个库函数的代码段。当这些应用程序同时在内存中运行时,实际上在内存中有多个这个库函数的代码段,这完全重复了。而动态链接库本身不将库函数的代码段链接入可执行程序,只是做个标记。然后当应用程序在内存中执行时,运行时环境发现它调用了一个动态库中的库函数时,会去加载这个动态库到内存中,然后以后不管有多少个应用程序去调用这个库中的函数都会跳转到第一次加载的地方去执行(不会重复加载)。

3、函数库中库函数的使用

(1)gcc中编译链接程序默认是使用动态库的,要想静态链接需要显式用-static来强制静态链接。

(2)库函数的使用需要注意4点。

  • 第一,包含相应的头文件;
  • 第二,调用库函数时注意函数原型;
  • 第三,有些库函数链接时需要额外用-lxxx来指定链接;
  • 第四,如果是动态库,要注意-L指定动态库的地址。

4、字符串函数

(1)C库中字符串处理函数包含在string.h中,这个文件在ubuntu系统中在/usr/include中。

(2)常见字符串处理函数及作用:

memcpy 确定src和dst不会overlap,则使用memcpy效率高
memmove 确定会overlap或者不确定但是有可能overlap,则使用memove比较保险
memset
memcmp
memchr
strcpy
strncpy
strcat
strncat
strcmp
strncmp
strdup
strndup
strchr
strstr
strtok

5、数学库函数(math.h)

  • 真正的数学运算的函数定义在:/usr/include/i386-linux-gnu/bits/mathcalls.h;
  • 使用数学库函数的时候,只需要包含math.h即可。

6、C链接器的工作特点

  • 因为库函数有很多,链接器去库函数目录搜索的时间比较久。为了提升速度想了一个折中的方案:链接器只是默认的寻找几个最常用的库,如果是一些不常用的库中的函数被调用,需要程序员在链接时明确给出要扩展查找的库的名字。链接时可以用-lxxx来指示链接器去到libxxx.so中去查找这个函数。
  • 链接时加-lm,就是告诉链接器到libm中去查找用到的函数。

五、自己制作静态链接库并使用

(1)第一步:自己制作静态链接库
首先使用gcc -c只编译不连接,生成.o文件;然后使用ar工具进行打包成.a归档文件
库名不能随便乱起,一般是lib+库名称,后缀名是.a表示是一个归档文件

注意:制作出来了静态库之后,发布时需要发布.a文件和.h文件。

(2)第二步:使用静态链接库

把.a和.h都放在我引用的文件夹下,然后在.c文件中包含库的.h,然后直接使用库函数。
第一次,编译方法:gcc test.c -o test
报错信息:test.c:(.text+0xa): undefined reference to `func1'
test.c:(.text+0x1e): undefined reference to `func2'
第二次,编译方法:gcc test.c -o test -laston
报错信息:/usr/bin/ld: cannot find -laston
collect2: error: ld returned 1 exit status
第三次,编译方法:gcc test.c -o test -laston -L.

无报错,生成test,执行正确。

除了ar名另外,还有个nm命令也很有用,它可以用来查看一个.a文件中都有哪些符号

六、自己制作动态链接库并使用

(1)动态链接库的后缀名是.so(对应windows系统中的dll),静态库的扩展名是.a

(2)第一步:创建一个动态链接库。

gcc aston.c -o aston.o -c -fPIC
gcc -o libaston.so aston.o -shared 
-fPIC是位置无关码,-shared是按照共享库的方式来链接。

注意:做库的人给用库的人发布库时,发布libxxx.so和xxx.h即可。

(3)第二步:使用自己创建的共享库。

第一步,编译方法:gcc test.c -o test
报错信息:test.c:(.text+0xa): undefined reference to `func1'
test.c:(.text+0x1e): undefined reference to `func2'
collect2: error: ld returned 1 exit status

第二步,编译方法:gcc test.c -o test -laston
报错信息:/usr/bin/ld: cannot find -laston
collect2: error: ld returned 1 exit status

第三步,编译方法:gcc test.c -o test -laston -L.
编译成功

但是运行出错,报错信息:

error while loading shared libraries: libaston.so: cannot open shared object file: No such file or directory

错误原因:动态链接库运行时需要被加载(运行时环境在执行test程序的时候发现他动态链接了libaston.so,于是乎会去固定目录尝试加载libaston.so,如果加载失败则会打印以上错误信息。)

解决方法一:
将libaston.so放到固定目录下就可以了,这个固定目录一般是/usr/lib目录。
cp libaston.so /usr/lib即可

解决方法二:

使用环境变量LD_LIBRARY_PATH。操作系统在加载固定目录/usr/lib之前,会先去LD_LIBRARY_PATH这个环境变量所指定的目录下去寻找,如果找到就不用去/usr/lib下面找了,如果没找到再去/usr/lib下面找。所以解决方案就是将libaston.so所在的目录导出到环境变量LD_LIBRARY_PATH中即可。

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/hgfs/Winshare/s5pv210/AdvancedC/4.6.PreprocessFunction/4.6.12.sharedobject.c/sotest

解决方案三:

ubuntu中用ldconfig

(4)ldd命令

  • 作用是可以在一个使用了共享库的程序执行之前解析出这个程序使用了哪些共享库,并且查看这些共享库是否能被找到,能被解析(决定这个程序是否能正确执行)。

宏定义与预处理、函数和函数库相关推荐

  1. 6、宏定义与预处理、函数与函数库

    C语言预处理理论 由源码到可执行程序的过程 源码.c->(编译)->elf可执行程序 源码.c->(编译)->目标文件.o->(链接)->elf可执行程序 源码.c ...

  2. Linux之IFS间隔符、C编程、Makefile工程文件、gdb调试、宏定义、预处理、assert和调试用特殊的宏

    1 间隔符 在shell下的for循环语句中,容器中,各个元素之间 是通过 间隔符 来分开的 默认情况下:读取 列表中的值----以 空格.TAB.换行  为间隔符 列表可以为:     常量    ...

  3. 【C语言】----宏定义,预处理宏

    什么是宏? 宏是学习任何语言所不可缺少的,优秀的宏定义可以使得代码变得很简洁且高效,有效地提高编程效率. 宏是一种预处理指令,它提供了一种机制,可以用来替换源代码中的字符串,解释器或编译器在遇到宏时会 ...

  4. 【GCC编译优化系列】宏定义名称与函数同名是一种什么骚操作?

    作者简介 *架构师李肯(全网同名)**,一个专注于嵌入式IoT领域的架构师.有着近10年的嵌入式一线开发经验,深耕IoT领域多年,熟知IoT领域的业务发展,深度掌握IoT领域的相关技术栈,包括但不限于 ...

  5. linux驱动 打印变量,linux驱动 内核函数 变量 宏定义

    insmod modprobe(自动检测 模块加载时需要的别的模块) rmmod 用户空间工具, 加载模块到运行中的内核以及去除它们. #include module_init(init_functi ...

  6. C++中的内联函数和C中的宏定义的区别

    在C++中内联函数: 内联函数即是在函数的声明和和定义前面加上"inline"关键字,内联函数和常规函数一样,都是按照值来传递参数的,如果参数为表达式,如4.5+7.5,则函数将传 ...

  7. 在主函数内使用宏定义

    之前习惯于在主函数外面使用宏定义,但实际上主函数内也可使用,例: #include<stdio.h> #include<stdlib.h> #include<unistd ...

  8. C语言中宏定义和函数的区别

    前言 在C语言中,对于一些常用或通用的代码段的封装可以有两种方式:函数和宏定义. 这篇博客就来带大家梳理一下对于这两种方式我们在使用时应该如何抉择,以及它们的区别和优缺点. 宏定义和函数的区别 从程序 ...

  9. VC预处理指令与宏定义的妙用

    VC中预处理指令与宏定义的妙用 刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具.使用它们可以进行简单的源代码控制,版本控制,预 ...

最新文章

  1. C语言程序设计有哪几种结构,第章c语言程序设计的三种基本结构.ppt
  2. pybind11 入门
  3. 挖掘Windows 10看图的习惯用法
  4. 勒索病毒“WannaCry”复现
  5. 二叉树的前序、中序、后序遍历与创建
  6. 【转】ABP源码分析十八:UI Inputs
  7. 如何让你变得魅力十足
  8. arcgis重心迁移分析_锂电池行业分析:从工艺、成本及供需角度看三元材料高镍化趋势...
  9. 读书笔记-泛型有限通配符
  10. js 正则表达式判断非法字符以及常用正则表达式。
  11. thoughtworks作业trains
  12. Cortex-M核心寄存器
  13. Java语言十五讲(第十四讲 容器框架二)
  14. 手机摄像头变成PC电脑摄像头
  15. 创业者妻子发声力挺老公:合伙创业七年未分股份被踢出局
  16. java计算机毕业设计基于微信小程序的校园外卖订餐系统APP
  17. 百度之星2017资格赛1003 度度熊与邪恶大魔王
  18. C语言实现总体方差,总体标准差,样本方差,样本标准差
  19. 如何安装和搭建wordpress个人网站(超详细+零基础)
  20. Linux虚拟机如何修改mysql的数据目录位置

热门文章

  1. 设置Eclipse中的字符集为UTF-8
  2. iOS - Core Animation 核心动画
  3. Linq 合并数据并相加
  4. 读小米的《参与感》书的摘录(一),与大家分享!
  5. POJ 2485 Highways (prim最小生成树)
  6. Sliverlight MD5
  7. 【 CDN 最佳实践】CDN 命中率优化思路
  8. Bash : 索引数组
  9. Android数据的存储方式简介
  10. 【原创】什么是 wire protocol