C程序环境及预处理

简介: C程序环境及预处理

文章目录


一、程序的翻译环境和执行环境


1.程序编译过程


2.编译内部原理


3.执行环境


二、程序运行前的预处理


1.预定义符号归纳


2.define定义标识符


3.define定义宏


4.define替换规则


5.宏和函数的对比


三、头文件被包含的方式


四、练习:写一个宏,可以将一个整数二进制位的奇偶位交换


 大家好,我是纪宁。


 在ANSI C的任何一种实现中,都存在两个不同的环境。第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。第2种是执行环境,它用于实际执行代码。翻译环境中也有很多的内部原理,一起来学习吧。


一、程序的翻译环境和执行环境

1.程序编译过程

 


 其中,源文件是文件名后缀为 .c 的文件,目标文件是文件后缀名为 .obj 的文件。


 组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人的程序库,将其需要的函数也链接到程序中


2.编译内部原理



 在预处理阶段,系统会自动处理掉所有的预编译指令。 包括删去注释,将头文件包含,#define 定义符号和宏的替换等等


 而在编译阶段,系统会将C语言代码翻译成汇编指令,还有语法分析、词义分析、语义分析、符号汇总等


 在汇编阶段。系统将汇编指令翻译成二进制指令,并生成.o目标文件,形成符号表。


 最后在链接阶段合并段表,重定位和合成符号表(在链接阶段可发现函数是否正常定义)


3.执行环境

 首先,程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。


 接下来就是程序的执行开始,调用main函数开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。


 最后就是终止程序。正常终止main函数,也有可能是意外终止。




二、程序运行前的预处理

1.预定义符号归纳

 下面的这些符号都是C语言内置符号


__FILE__  进行编译的源文件

__LINE__  文件当前的行号

__DATE__  文件被编译的日期

__TIME__ 文件被编译的时间

程序中使用对应的符号在预处理阶段自动转化为对应的含义,测试代码如下


#include<stdio.h>

int main() {

   printf("%d\n", __LINE__);

   printf("%s\n", __TIME__);

   printf("%s\n", __DATE__);

   printf("%s\n", __FILE__);

   return 0;

}

运行结果




 由于_LINE_在第四行,所以转化结果为4;时间和日期就是我测试代码的时间、日期;_FILE_则对应我源文件的路径。


2.define定义标识符

语法如下:


#define name stuff

 在预处理阶段,编译器会自动将代码中的 name 全部替换为 stuff 。但需注意在写#define定义标识符的时候,后面不能加 ;,预处理阶段编译器会将 ; 也当成要替换的内容。


3.define定义宏

 #define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义

宏(define macro)。下面是宏的申明方式:


#define name( parament-list ) stuff

 其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。它的使用方法与函数类似,只不过将函数传参改成了直接替换。因为改成了直接替换,所以宏的参数尽量加括号修饰。其次,参数列表的左括号必须与name紧邻,因为如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。


 宏的参数一般会被多次替换,所以不能使用那些带 ‘副作用’ 的参数,副作用即表达式求值中出现的永久性效果,如自加自减这些运算。


 例如,求一下如下代码的运算结果:


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

...

x = 5;

y = 8;

z = MAX(x++, y++);

printf("x=%d y=%d z=%d\n", x, y, z);

 大部分人可能会理所当然的以为传过去的是x++和y++表达式的值,那么结构就应该是z的结果剧应该是8,x的结果应该是6,y的结果应该是9 ?可实际上并非如此:




 这就是参数带有副作用的影响


4.define替换规则

在程序中扩展#define定义符号和宏时,需要涉及几个步骤。


1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。


2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。


3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。


还有几点需要注意


1. 宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。


2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索


5.宏和函数的对比

 在某些方面来讲,宏是非常有优势的。比如求两个数的最大值这样一个简单的工作,如果用函数来完成的话,可能会非常耗费时间,因为每次调用函数都要进行函数栈帧创建和销毁,而宏却不需要;函数传参必须是特定的类型,而宏的参数却可以在算数运算范围内传多种类型的参数。总结就是:在小型工程中,宏比函数在程序的规模和速度方面更胜一筹;宏是类型无关的,且可以将类型当参数传过过去,这点是函数做不到的。


 但宏也有以下缺点:


1. 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,否则可能大幅度增加程序的长度。


2. 宏是没法调试的。


3. 宏由于类型无关,也就不够严谨。


4. 宏可能会带来运算符优先级的问题,导致程容易出现错。


三、头文件被包含的方式

 在C语言中,包含头文件有两种方法:" "包含和< >包含。


 "  "包含是编译器包含本地文件的方法,查找策略是先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,如果找不到就提示编译错误。


 而< >包含的查找头文件是直接去标准路径下去查找,如果找不到就提示编译错误。


 看了这种情况,有人可能会问:那以后能不能包含所有文件都用 " " 来包含,答案是可以的,但是如果考虑到程序的效率问题,就另当别论了,因为 " "去查标准路径的头文件是需要查找两次的,而<>查找标准路径的头文件只需要查找一次,效率明显高很多。


 为了防止头文件被重复引用,头文件中都会有条件编译的内容来防止文件内容的重复


在每个头文件的开头写这几条语句


#ifndef __TEST_H__

#define __TEST_H__

//头文件的内容

#endif  //__TEST_H_

或者:


#pragma once

就可以避免头文件的重复引用。


这是一些常用的条件编译指令


#if

#elif

#endif

四、练习:写一个宏,可以将一个整数二进制位的奇偶位交换

 思路:将这个数的二进制位的奇数位取出,向左移动一位;将这个数的二进制位的偶数位取出,向右移动一位,最后相加即可。




 以8位二进制为例:&0x55可以得到它的奇数位,&0xaa可以得到它的偶数位,如果上升到32位二进制,那么&0x55555555可以得到它的奇数位,然后左移,&0xaaaaaaaa可以得到它的偶数位,然后右移,最后再相加即可将这个数的二进制位的奇偶位交换。


 如果看懂图解的话,写代码用宏实现就很简单了。


#include<stdio.h>

#define SWAP(n)  (n=((n&0xaaaaaaaa)>>1)+((n&0x55555555)<<1))

int main()

{

int n = -120;

SWAP(n);//交换奇偶位

printf("%d\n", n);

SWAP(n);//再交换回来

printf("%d\n", n);

return 0;

}

用 -120 验证奇偶位交换是否成功


运行结果:




 时间过得很快,眨眼间C语言的博客已经接近完结了,后续有时间还会发布C语言的一些经典面试题解,大家尽请期待。


 下一篇博客就开始学习并更新数据结构与算法的内容,感谢各位对纪宁的支持。

相关文章
|
4月前
|
存储 编译器 程序员
程序环境和预处理
程序环境和预处理
52 0
|
7天前
|
存储 自然语言处理 编译器
程序环境和预处理(详解)
程序环境和预处理(详解)
|
4月前
|
编译器 Linux C++
【程序环境与预处理玩转指南】(下)
【程序环境与预处理玩转指南】
|
4月前
【程序环境与预处理玩转指南】(中)
【程序环境与预处理玩转指南】
|
9月前
|
存储 编译器 程序员
【C】程序环境和预处理
在ANSI C的任何一种实现中,存在两个不同的环境。
|
9月前
|
编译器 Linux C++
【程序环境与预处理】(二)
【程序环境与预处理】(二)
55 0
|
5月前
|
存储 自然语言处理 编译器
程序环境+预处理
程序环境+预处理
50 0
|
5月前
|
存储 自然语言处理 编译器
|
5月前
|
Serverless
程序环境和预处理(二)
程序环境和预处理(二)
|
5月前
|
编译器 Linux C语言
程序环境和预处理(三)
程序环境和预处理(三)