本文主要介绍:
1.预定义符号
2.宏和函数的区别
3.理解编译链接的整个过程和详细的每个过程
一、预定义符号
这些符号的值可以给我们提供一些编译C程序文件时的信息,给我们调试或创建日志文件提供便利。
以下是对一些预定义符号的具体描述:
_FILE_ :提供当前进行编译的源文件名
_LINE_ :提供文件当前行的行号
_DATE_ :提供文件编译时的日期
_TIME_ :提供文件编译时的时间
_STDC_ :如果编译器遵循ANSI C,其值则为1,否则为未定义。
<ANSI C:美国国家标准协会(American National Standards Institute)制定的一个C语言的标准>
以下是代码实现:
二、宏和函数的区别
(其中内容借鉴了《C和指针》一书中的描述)
#define就是预处理宏定义命令,它可以定义一个符号代替一个常量或是一个文本,以后在C文件的书写中,就可以使用这个符号来代替这些常量或 操作,减少程序员的工作量。
函数对于我来说,就像是一个盒子,它里面封装这完成一些功能的语句,我们可以通过在主函数中调用函数的名称来使用函数里面封装的内容。
这里根据《C和指针》中的描述,函数和宏主要 存在5方面的区别:
1.代码长度:
对于宏来说,因为在C文件的预处理过程中,编译器会扫描宏所定义的符号,并用其所代表的内容来替换原来的符号,所以除了用一些特别短小宏定义外,其他的宏替换会导致程序的长度大大增加。
对于函数而言,却完全不用担心这样的事发生,函数的代码只存在于一个地方,而其他地方要使用时,都是使用函数名来调用这个函数。所以,不会增加代码的长度。
2.执行速度
对于宏来说,它是在预处理阶段就直接被所定义的内容所替换,在后面的编译阶段就直接执行,基本没有开销了。所以宏的执行速度快。
对于函数来说,函数的执行是通过在栈上开辟临时空间,进行函数体的操作,然后执行完函数后销毁。这些操作都会造成开销。所以函数的执行速度要慢于宏。
3.操作符优先级
对于宏来说,宏参数的求值是在所有周围的表达式的上下文环境里,它的参数如果不带括号就默认和周围的操作融为一体。这样如果宏里定义的运算符的优先级低于其周围环境里的,则会出现不可预知的后果。
例如:
解释:
上面函数的打印输出并没有按我们预想的结果输出了25,而是输出了13,这就是由于运算符优先级的问题。在文件的预处理阶段,程序中printf语句会被替换成printf(“%\n”,5*2+3);//不是5*(2+3)
4.参数求值
对于宏来说,参数每次用于宏定义时,都会被重新求值,所以这些参数都具有副作用,而它们的出现将使程序出现不可预知的结果。
对于函数来说,参数只是在函数调用前求值一次,在函数中多次使用参数并不会使参数被求值多次,所以不会导致函数的返回值不可预知。
5.参数类型
对于宏来说,它的参数与类型无关,可以表示变量名,也可以表示类型名。
对于函数来说,它的参数必须指定具体的类型。对于实现相同的功能,函数的参数类型不同,则需定义不同的函数。
三、理解编译链接的整个过程和详细的每个过程
主要流程:
C语言源程序—预处理—>汇编程序—汇编—>二进制目标程序—链接—>可执行程序
预处理阶段:
1.预处理主要由预处理器完成。这一阶段一共完成4件事。
1)头文件的展开:将程序中所用的头文件用其内容来替换头文件名。
2)宏替换:扫描程序中的符号,将其 替换成宏所定义的内容。
3)去掉注释:去掉程序中的注释。
4)条件编译:在程序中难免会有文件的重复引用,如果每次引用都要重新调用文件中的内容,这样就会增加许多不必要的开销 。所以为了防止这种情况的发生,我们在文件中使用条件编译符号来防止这种情况的发生。
条件编译语法;
#if constant-expression
statements
#elif
other statements
#else
other statements
#endlf
测试一个符号是否被定义:
#ifdef
#ifndef
2.汇编,就是将高级语言转换成低级的机器语言。
3.链接,就是将程序中出现的函数等,与其源文件进行“匹配连接”,例如实现标准输入输出就需要链接到库文件stdio.h中。因为在程序中我们只是调用这些函数,真正的实现还是在库文件中。
以下,使用Linux中gcc来实现以上过程:
先介绍一些常用命令:
假定我们已经事先创建好了text.c文件
gcc -E text.c -o text.i //将.c文件经预处理后的.i文件
gcc -S text.i -o text.s //将.i文件转换成汇编语言,为.s文件
gcc -c text.s - o text.o //将.s文件转换成目标文件(二进制文件),为.o文件
源文件:
预处理:
编译:
链接:
执行程序: