预处理器篇(Preprocessor)
1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
答: #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
这里会涉及以下几点知识:
1) #define 语法的基本知识,例如:不能以分号结束;没有括号导致运算顺序错误等等。
2) 预处理器需要你写的是一个这个常数值的计算表达式,这样显得更清晰,我们实际写代码也是这样做的,如果你直接写这个表达式的结果,你的能力肯定会被质疑。
3) 要有意识到这个计算结果是是非常大的一个数据,这个表达式的结果将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉编译器这个常数是的长整型数;UL就是告诉机器这是一个无符号长整型;
2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
答: #define MIN(A,B) ((A) <= (B) ? (A) : (B))
这个问题经常被考到:
1) 标识#define在宏中应用的基本知识。这是很重要的。因为在 嵌入(inline)操作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2) 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优的代码,了解这个用法是很重要的。
3) 懂得在宏中小心地把参数用括号括起来
4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?
least = MIN(*p++, b); ->((*p++) <b ?(*p++) : b) 此时前后两个的*p++ 的值已经不是同一个了,多处使用这个宏时,每次的*p++ 都不是同一个值
3. 预处理器标识#error的目的是什么?
答: 编译程序时,只要遇到#error就会生成一个编译错误提示消息,并停止编译。其语法格式为:#error error-message
下面举个例子:
程序中往往有很多的预处理指令
#ifdef XXX
...
#else
...
#endif
当程序比较大时,往往有些宏定义是在外部指定的(如makefile),
或是在系统头文件中指定的,
当你不太确定当前是否定义了 XXX 时,就可以改成如下这样进行编译:
#ifdef XXX
...
#error "XXX has been defined"
...
#else
#endif
这样,如果编译时出现错误,输出了XXX has been defined,表明宏XXX已经被定义了。
并且终止代码运行。提示客户去查找问题;
4.预处理器在如何头文件中发挥互斥作用?
答 : 使用 #ifndef #define #endif,比如
// 当项目中有多个c文件使用到同一个头文件是,
//在编译的时候会出现大量的变量,函数声明冲突,解决就是使用宏来进行互斥
#ifndef _HEAR_H_
#define _HEAR_H_
... (头文件内容)
#endif
5.#define和const可以用于定义常量,那两者有何区别?
答:
Define和const 都可以用于定义常量,但在生效时间,内存占用情况,类型检查有以下区别:
- define只是单纯的文本替换,define常量的生命生命周期止于编译器,不存在分配内存,存在与程序的代码段
- const生效于编译的阶段;define生效于预处理阶段
- Const修饰的常量处于程序的数据段,在堆栈中分配空间
- Const有数据类型检查,define没有
- #define不可调试,const能调试
- const定义的变量在C中不是真正的常量
- Const 定义的常量不能作为数组的大小
6.typedef和#define都可以抽象化地简化代码,那两者有何区别?
答:
原理不同:
- 首先#define是预处理命令,在预处理阶段只是机械的替换带入字符串,并不会左类型检查,
- typedef是关键字,作用是给自己的作用域内给一个已经存在的类型起个别名
- #define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域
- 对指针的操作不同
7.#include包含中,使用尖括号和双引号有何区别?
答:
对于.#include<头文件>,表示是系统文件,编译会先从标准库路径下搜索,编译器设置的头文件路径-->系统变量
对于#include”头文件”,从当前头文件目录-->编译器设置的头文件路径-->系统变量
8.评价如下的代码?
#define m(a,b) a*b
// #define m(a,b) (a)*(b)//避免出问题最好加上()
int main()
{
printf("%d\n",m(5,6));
printf("%d\n",m(5+1,6));//实际是这样的:5+1*6
return 0;
}
#define sqort(a) ((a)*(a))
// 实例1:
int a = 5;
int b = sqort(a++);//( (a++) * (a++) ) 先给括号赋值 5之后a再+1=6
printf("%d\n",b);//30
printf("%d\n",a);//7
// 实例2:
int a = 5;
int b = sqort(++a);//((++a)* (++a))
printf("%d\n",b);//49
printf("%d\n",a);//7
答:这里主要是考察++a和后加加的问题:(++a)返回的a的引用(即先自加再引用),(a++)返回的是a加之前的数值(即先应用再自加);
其次,有如下缺点
- 无法进行类型检查
- 运算优先级问题
- 无法调试
- 代码膨胀
- 无法操作类的私有数据成员
9.简述C代码编译过程?
此外又一个类似的问法:简述GCC的编译步骤和作用?
答:此处简单描述一个test.c如何编译成一个二进制文件test的过程来进行:
1.预处理:宏内容替换,#include内容替换等
gcc -E test.c -o test.i
2编译:编译为汇编代码,输出汇编代码文件
gcc -S test.i -o test.s
3.汇编:将汇编代码文件编译为目标文件
gcc -C test.s -o test.o
4.链接:将目标文件与所需的附加目标文件(静态链接库和动态链接库
标准输入输出库)连接起来,最终生成可执行文件test
gcc test.o -o test
整个过程我们我们可以用ESC(电脑左上角的按键速记) 和 iso(苹果系统简称速记)。
- 预处理(Preprocessing):
- 预处理阶段是在实际编译之前的一个可选步骤,用于处理源代码中的预处理指令,比如
#include
和#define
。- 预处理器将处理这些指令,并且可能会包含其他文件、进行宏替换等。
- 预处理的输出是一个经过处理的源文件,通常以
.i
或.ii
为扩展名。
- 编译(Compiling):
- 编译阶段将预处理后的源代码转换为汇编代码(Assembly Code)。
- 编译器(如GNU Compiler Collection中的
gcc
)将C源代码翻译成汇编语言。- 输出是一个以
.s
为扩展名的汇编代码文件。
- 汇编(Assembling):
- 汇编阶段将汇编代码转换为机器语言指令。
- 汇编器(如GNU Assembler中的
as
)将汇编代码翻译成机器码。- 输出是一个以
.o
或.obj
为扩展名的目标文件,包含了二进制指令。
- 链接(Linking):
- 链接阶段将多个目标文件(例如,多个C文件分别编译得到的目标文件)合并为一个可执行文件。
- 链接器(如GNU的
ld
或gcc
中的链接器部分)将各个目标文件中的函数和变量引用解析,并创建一个可执行文件。- 链接的输出是一个可执行的二进制文件,可以在操作系统上运行。
10.在头文件中是否可以定义静态变量
答:不可以,因为静态变量是有记忆的,不会随函数结束而结束,所以,如果定义在头文件中,那么就会被多个文件开辟空间,浪费资源或者重新出错.
11.简述 #和##的作用?
答:在C语言中,#
和 ##
都是预处理器(preprocessor)操作符,它们与宏定义(#define
)一起使用,以在编译前对代码进行文本替换和拼接操作。这些操作符在处理宏定义时特别有用,允许我们创建更灵活和强大的宏。其中
1 、#
操作符:#
操作符用于将其后的宏参数转换为字符串字面量。这在你需要打印变量的名字或者需要用到变量的字符串表示形式时非常有用。
#define STRINGIFY(x) #x
const char* name = STRINGIFY(variable_name); // name 将指向 "variable_name" 的字符串
2.##
操作符:##
操作符是令牌粘贴操作符,用于连接两个令牌(可以是宏参数、标识符等)形成一个单一的令牌。这在需要创建具有动态名称的变量或函数时特别有用。
#define CONCAT(x, y) x ## y
int CONCAT(var, 1) = 10; // 这将定义一个名为 var1 的整数变量,并初始化为 10
综上,简单的说:
#利用宏,将参数实现字符串化 ;##是运算符粘合剂,组合成一个变量,强制分隔。
用例:
// #利用宏参数字符串化
#define ARGV(x) printf(""#x" is %s\n",#x) //表示把参数x解释为字符串
int a = 5;
ARGV(a); //a is a
// ##运算符粘合剂 组合成一个变量,强制分隔
#define targ( n ) X##n //表示X1......或Xn
int main()
{
int a = 5;
// ARGV(a);
int targ(1) = 10;//表示 X1 =10
printf("%d\",X1);//10
return 0
}