嵌入式-C语言(二)
- 1.宏定义是在编译的哪个阶段被处理的?
- 2.什么是预编译,何时需要预编译
- 3.预处理部分命令
- 4.静态链接于动态连接的优缺点
- 5.静态链接和动态连接有什么区别
- 6.静态链接库和动态连接库有什么区别
- 7.静态链接库和动态连接库有什么区别
- 8.const与#define的异同?
- 9.const与#define 相比,有何优点?
- 10.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
- 11.已知数组table,用宏求数组元素个数。
- 12.下面代码能不能编译通过?
- 13.预处理器标识#error的作用是什么?
- 14.用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
- 15.typedef和define
- 16. NUL与NULL
- 17.两种#include调用区别
1.宏定义是在编译的哪个阶段被处理的?
宏定义是在编译预处理阶段被处理的。
解读:编译预处理:头文件包含、宏替换、条件编译、去除注释、添加行号。
(1)预处理:引入头文件、进行宏替换、处理条件编译指令、去除注释、添加行号。
(2)编译:进行语法分析、词法分析、语义分析符号汇总等,并生成汇编代码.s。
(3)汇编:将汇编代码转成二进制代码,二进制文件就可以让机器来读,每一句汇编代码都会产生一句机器语言。最终生成重定位目标文件.o文件(目标文件)。
(4)链接:将有关的目标文件彼此连接为可执行代码。分为静态链接(将库文件代码搬迁到可执行文件中,后缀是.a)和动态链接(在执行的时候转到库文件代码执行,后缀是.so)。
2.什么是预编译,何时需要预编译
预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。C提供的预处理功能主要有以下三种:
- 1)宏定义
- 2)文件包含
- 3)条件编译
3.预处理部分命令
#define //定义一个预处理宏
#undef //取消宏的定义
#if //编译预处理中的条件命令,相当于C语法中的if语句
#ifdef //判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef //与#ifdef相反,判断某个宏是否未被定义
#elif //若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if(扩展条件)
#else //与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else(扩展条件)
#endif //#if, #ifdef, #ifndef这些条件命令的结束标志.
#defined //与#if, #elif配合使用,判断某个宏是否被定义
4.静态链接于动态连接的优缺点
- 静态的链接产生的可执行文件体积比较大,而动态连接的可执行文件体积较小。
- 动态的链接编译效率比较高
- 静态链接的可执行文件的执行效率比较高
- 静态链接的可执行的文件的“布局”比较好一点
5.静态链接和动态连接有什么区别
静态链接将库文件代码搬迁到可执行文件中,成为可执行文件的一部分,即该文件包含了运行时的全部代码。其优点是:在程序发布的时候就不需要的依赖库,也就是不再需要带着库一块发布,程序可以独立执行。缺点是:当多个程序调用同一个函数时,内存中就会存在这个函数的多个复制,浪费了内存资源。
动态连接调用的的函数代码并没有复制到内存中,仅仅在其中加入了所调用函数的描述信息,在程序运行或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运行到指定的代码时,去共享执行内存中已经加载的动态库可执行代码,最终达到运行时连接的目的。其优点是: 多个程序可以共享同一段代码,而不需要在磁盘上存储多个拷贝。缺点是:由于是运行时加载,可能会影响程序的前期执行性能。
6.静态链接库和动态连接库有什么区别
- 由于静态库是在编译期间直接将代码合到可执行程序中,而动态库是在执行期时调用DLL中的函数体,所以执行速度比动态库要快一点;
- 静态库链接生成的可执行文件体积较大,且包含相同的公共代码,造成内存浪费;
- 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;
- DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性,适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。
7.静态链接库和动态连接库有什么区别
- 带参宏只是在编译预处理阶段进行简单的字符替换;而函数则是在运行时进行调用和返回。
- 宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
- 带参宏在处理时不分配内存;而函数调用会分配临时内存。
- 宏不存在类型问题,宏名无类型,它的参数也是无类型的;而函数中的实参和形参都要定义类型,二者的类型要求一致。
- 而使用宏定义次数多时,宏替换后源程序会变长;而函数调用不使源程序变长。
8.const与#define的异同?
- 异:const有数据类型,编译器可以做静态类型检查;而宏定义没有类型,可能会导致类型出错。
- 同:两者都可用来定义常数。
9.const与#define 相比,有何优点?
- Const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
- const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
- 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试
10.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。
#define MIN(A, B) ((A) <= (B)? (A) : (B))
11.已知数组table,用宏求数组元素个数。
#define COUNT(table) (sizeof(table) / sizeof(table[0]))
12.下面代码能不能编译通过?
#define c 3
c++;
不能。因为自增运算符++用于变量,3是常量。
13.预处理器标识#error的作用是什么?
编译程序时,只要遇到 #error 就会跳出一个编译错误。
当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,当你不太确定当前是否定义了 XXX 时,可写如下预处理代码:
#ifdef XXX
#error "XXX has been defined"
#else
…
#endif
如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了
14.用预处理指令#define声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
- 注意预处理器将为你计算常数表达式的值,并且整个宏体要用括号括起来。
- 注意这个表达式将使一个16位机的整型数溢出,因此要用到无符号长整型符号UL,告诉编译器这个常数是的无符号长整型数。
15.typedef和define
typedef和define都是替一个对象取一个别名,来增强程序的可读性,但他们也存在以下4个不同:
原理不同
- define是C语言规定的语法,他是预处理命令,在预处理阶段进行简单而又机械的字符串替换,不做正确性检查;只有在编译阶段才会发现可能的错误并报错。
- typedef是关键字,在编译时处理,所以具有类型检查的功能,它在自己的作用域内对已存在的类型一个别名,但切记不能在一个函数定义里使用标识符typedef。
功能不同
- typedef用来定义类型的别名,这些类型不仅包括内部类型(int、char等),还包括自定义类型(如struct),使得类型更加容易记忆。此外,还有一个重要的用途,就是定义机器无关的类型。
- define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
作用域不同
define没有作用域的限制,只要是预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域.
16. NUL与NULL
牢记下面的话,它有助于回忆指针和ASCII码零的正确术语:
一个‘L’的NUL用于结束一个ASCII字符串
两个‘L’的NULL用于表示什么也不指向(空指针)
ASCII字符中零的位模式被称为‘NUL’。表示哪里也不指向的特殊的指针则是‘NULL’,这两个术语不可互换。
17.两种#include调用区别
- 对于#include<filename.h>,编译器先从标准库开始搜索filename.h。使得系统文件调用较快,只要向用户提供头文件和二进制的库即可;
- 而对于#include”filename.h”,编译器先从用户的工作路径开始搜索filename.h,然后去寻找系统路径,使得自定义文件调用较快。