C语言
简述编译运行一段代码的过程
源程序是指未经编译的,按照一定的程序设计语言规范书写的,人类可读的文本文件,源程序就是所写好的代码。
可执行程序,即常说的.exe程序,可以执行程序,完成计算机功能。在C语言中,.c文件就是所谓的源文件。
源程序到可执行程序的过程。在这个过程中,会发生如下的变化:
.c文件生成.obj文件的过程,称为编译,.obj文件生成到.exe文件的过程,称为链接。
.obj文件就是一个是程序编译生成的二进制文件,当.exe文件生成以后.obj文件就会被删除。事实上,.c文件生成.exe文件的过程总共是经历了预处理,编译,汇编,链接,这四个过程。
1、预处理
为了接下来能够解释的更加清楚,使用linux平台下的gcc编译器解释。先书写一个非常简单的程序来介绍:
test.c
#include<stdio.h>
int main()
{
printf("hello");
return 0;
}
直接编译得到:
第一步发生的是预编译,使用-E指令会使程序只进行到预编译指令。经过预编译指令后的会生成一个.i文件。
在预编译的过程中,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令(#ifdef,#ifndef,#else,#elif,#endif),宏的替换,添加行号,保留所有的编译器指令。
当进行预编译以后的文件中将不再存在宏,所有的宏都已经被替代。当想要判断宏是否正确或者头文件包含是否正确时,也可以通过预编译来查看。
2、编译
在预处理结束后,进行的是编译。编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码。
3、汇编
汇编过程将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。
在这里最终会生成一个重定位目标文件 .o文件,类似windows下的.obj文件。这里生成的目标文件里面就是二进制文件。另外,在这里会形成符号表,给这些符号会分配虚拟地址。
4、链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数等等。所有这些问题,都需要经链接程序的处理方能得以解决。链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
链接分为静态链接和动态链接:
静态链接:后缀是.a,主要在编译的时候将库文件里面代码搬迁到可执行的文件中;
动态链接:后缀是.so,主要在执行的时候需要转换到库文件代码执行;
两种链接的优缺点:
- 静态的链接产生的可执行的文件体积比较的大;而动态链接的可执行文件的体积比较小;
- 动态的链接的编译的效率比较的高;
- 静态链接的可执行的文件执行的效率高
- 静态链接的可执行的文件的“布局”比较好一点;
静态链接和动态链接有什么区别
静态链接是指把要调用的函数或者过程直接链接到可执行文件中,成为可执行文件的一部分。换句话说,函数和过程的代码就在程序的exe文件中,该文件包含了运行时所需的全部代码。静态链接的缺点是:当多个程序都调用相同函数时,内存中就会存在这个函数的多个复制,这样就浪费了内存资源
动态链接是相对于静态链接而言的。动态链接调用的函数代码并没有被复制到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在操作系统的管理下,才在应用程序与相应的动态链接库( dynamic link library,d)之间建立链接关系。当要执行调用,dll文件中的函数时,根据链接产生的重定位信息,操作系统才转去执行,dll文件中相应的函数代码。
静态链接的执行程序能够在其他同类操作系统的机器上直接运行。例如,一个exe文件是在 Windows2000系统上静态链接的,那么将该文件直接复制到另一台 Windows2000的机器上,是可以运行的。而动态链接的执行程序则不可以,除非把该exe文件所需的dll文件都一并复制过去,或者对方机器上也有所需的相同版本的dll文件,否则是不能保证正常运行的。
静态链接库和动态链接库有什么区别
静态链接库就是使用的lib文件,库中的代码最后需要链接到可执行文件中去,所以静态链接的可执行文件一般比较大一些。
动态链接库是一个包含可由多个程序同时使用的代码和数据的库,它包含函数和数据的模块的集合。程序文件(如exe文件或d文件)在运行时加载这些模块(即所需的模块映射到调用进程的地址空间)。
静态链接库和动态链接库的相同点是它们都实现了代码的共享,不同点是静态链接库lib文件中的代码被包含在调用的exe文件中,该lib文件中不能再包含其他动态链接库或者静态链接库了。而动态链接库dll文件可以被调用的exe动态地“引用”和“卸载”,该dll文件中可以包含其他动态链接库或者静态链接库。
5、new和malloc
- malloc和free是c++/c语言的库函数,需要头文件支持stdlib.h;new和delete是C++的关键字,不需要头文件,需要编译器支持;
- 使用new操作符申请内存分配时,无需指定内存块的大小,编译器会根据类型信息自行计算。而 malloc则需要显式地支持所需内存的大小。
- new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无需进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void,需要通过强制类型转换将
6 、extern”C” 的作用
我们可以在C++中使用C的已编译好的函数模块,这时候就需要用到extern”C”。也就是extern“C” 都是在c++文件里添加的。
extern在链接阶段起作用(四大阶段:预处理--编译--汇编--链接)。
7、strcat、strncat、strcmp、strcpy哪些函数会导致内存溢出?如何改进?
strcpy函数会导致内存溢出。
strcpy拷贝函数不安全,他不做任何的检查措施,也不判断拷贝大小,不判断目的地址内存是否够用。
1 char *strcpy(char *strDest,const char *strSrc)
strncpy拷贝函数,虽然计算了复制的大小,但是也不安全,没有检查目标的边界。
1 strncpy(dest, src, sizeof(dest));
strncpy_s是安全的
strcmp(str1,str2),是比较函数,若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。(比较字符串)
strncat()主要功能是在字符串的结尾追加n个字符。
1 char * strncat(char *dest, const char *src, size_t n);
strcat()函数主要用来将两个char类型连接。例如:
char d[20]="Golden";
char s[20]="View";
strcat(d,s);
//打印d
printf("%s",d);
输出 d 为 GoldenView (中间无空格)
延伸:
memcpy拷贝函数,它与strcpy的区别就是memcpy可以拷贝任意类型的数据,strcpy只能拷贝字符串类型。
memcpy 函数用于把资源内存(src所指向的内存区域)拷贝到目标内存(dest所指向的内存区域);有一个size变量控制拷贝的字节数;
函数原型:
1 void *memcpy(void *dest, void *src, unsigned int count);
8、static的用法
- 用static修饰局部变量:使其变为静态存储方式(静态数据区),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。
- 用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。
- 用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
9、const的用法
const主要用来修饰变量、函数形参和类成员函数:
用const修饰常量:定义时就初始化,以后不能更改
用const修饰形参:func(const int a){};该形参在函数里不能改变
用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。
10、volatile作用和用法
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量在内存中的值,而不是使用保存在寄存器里的备份(虽然读写寄存器比读写内存快)。
硬件、中断、RTOS等等所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。
以下几种情况都会用到volatile:
1、并行设备的硬件寄存器(如:状态寄存器)
2、一个中断服务子程序中会访问到的非自动变量
3、多线程应用中被几个任务共享的变量
11、const常量和#define的区别
用#define max 100 ; 定义的常量是没有类型的(不进行类型安全检查,可能会产生意想不到的错误),所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常量的名字联系起来,define所定义的宏变量在预处理阶段的时候进行替换,在程序中使用到该常量的地方都要进行拷贝替换;
用const int max = 255 ; 定义的常量有类型(编译时会进行类型检查)名字,存放在内存的静态区域 中,在编译时确定其值。在程序运行过程中const变量只有一个拷贝,而#define所定义的宏变量却有多个拷贝,所以宏定义在程序运行过程中所消耗的内存要比const变量的大得多
12、变量的作用域(全局变量和局部变量)
全局变量:在所有函数体的外部定义的,程序的所在部分(甚至其它文件中的代码)都可以使用。全局变量不受作用域的影响(也就是说,全局变量的生命期一直到程序的结束)。
局部变量:出现在一个作用域内,它们是局限于一个函数的。局部变量经常被称为自动变量,因为它们在进入作用域时自动生成,离开作用域时自动消失。关键字auto可以显式地说明这个问题,但是局部变量默认为auto,所以没有必要声明为auto。
局部变量可以和全局变量重名,在局部变量作用域范围内,全局变量失效,采用的是局部变量的值。
13、内存对齐作用:
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
结构体struct内存对齐的3大规则:
- 对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍;
- 结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍;
- 如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型。
14、中断
硬中断 / 软中断是什么?有什么区别?
硬中断
1. 硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。 每个设备或设备集都有它自己的 IRQ(中断请求)。基于IRQ ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通 常是内核中的一个子程序,而不是一个独立的进程)。
2. 处理中断的驱动是需要运行在CPU上的,因此,当中断产生的时候,CPU会中断当前正在运行的任 务,来处理中断。在有多核心的系统上,一个中断通常只能中断一颗CPU(也有一种特殊的情况, 就是在大型主机上是有硬件通道的,它可以在没有主CPU的支持下,可以同时处理多个中断。)。
3. 硬中断可以直接中断CPU。它会引起内核中相关的代码被触发。对于那些需要花费一些时间去处理 的进程,中断代码本身也可以被其他的硬中断中断。
4. 对于时钟中断,内核调度代码会将当前正在运行的进程挂起,从而让其他的进程来运行。它的存在 是为了让调度代码(或称为调度器)可以调度多任务。
软中断
1. 软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的。
2. 通常,软中断是一些对I/O的请求。这些请求会调用内核中可以调度I/O发生的程序。 对于某些设
备,I/O请求需要被立即处理,而磁盘I/O请求通常可以排队并且可以稍后处理。根据I/O模型的不 同,进程或许会被挂起直到I/O完成,此时内核调度器就会选择另一个进程去运行。 I/O可以在进程 之间产生。并且调度过程通常和磁盘I/O的方式是相同。
3. 软中断仅与内核相联系。而内核主要负责对需要运行的任何其他的进程进行调度。 一些内核允许设 备驱动的一些部分存在于用户空间,并且当需要的时候内核也会调度这个进程去运行。
4. 软中断并不会直接中断CPU。也只有当前正在运行的代码(或进程)才会产生软中断。这种中断是 一种需要内核为正在运行的进程去做一些事情(通常为I/O)的请求。有一个特殊的软中断是Yield 调用,它的作用是请求内核调度器去查看是否有一些其他的进程可以运行。
区别
1. 软中断是执行中断指令产生的,而硬中断是由外设引发的。
2. 硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。 3. 硬中断是可屏蔽的,软中断不可屏蔽。
4. 硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。 5. 软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。
中断为什么要区分上半部和下半部?
Linux中断分为硬件中断和内部中断(异常),调用过程:外部中断产生->发送中断信号到中断控制器-> 通知处理器产生中断的中断号,让其进一步处理。
对于中断上半部和下半部的产生,为了中断处理过程中被新的中断打断,将中断处理一分为二,上半部 登记新的中断,快速处理简单的任务,剩余复杂耗时的处理留给下半部处理,下半部处理过程中可以被 中断,上半部处理时不可被中断。
中断下半部一般如何实现?
软中断、 tasklet、工作队列。