目录
内存管理
内存分配
堆和栈的区别?(面试重点)
申请内存的函数
malloc
realloc
free
gcc工具链
编译的过程(面试重点)
第一步,预处理:
第二步,编译:
第三步,汇编:
第四步,链接:
常用参数
静态库和动态库的制作(开发重点)
静态库的制作过程
动态库的制作过程
上一篇复习了联合体和枚举,这一篇开始讲解一下内存管理和gcc工具链。
说明:我们学过单片机的一般都是有C语言基础的了,网上关于C语言的资料有很多,大家如果对C语言不熟悉的话可以先去详细学一下,再以这篇博文作为复习资料学习。
这篇博文的目的是复习C语言,我们会陆续以30多个编程题作为复习要点,这30多个编程题基本涵盖了C语言所有的内容了,只要你掌握了这30多个编程题,那么你的C语言基本就没什么问题了。
注意:由于本专栏是嵌入式全栈开发专栏,为了我们能熟悉以后实际工作中的开发环境,我们写C语言全部在Linux中的vim编辑器中写,这么做事为了我们能够熟练掌握Linux系统的常用命令以及Linux上的vim编辑器的常用工作命令,以达到对口训练的目的!
vim编辑器的一些工作命令在上一篇博文中已经详细介绍过了,如果不了解可以先去看看。
我们正式开始:
内存管理
C语言不能直接操作物理内存,程序中使用的内存都是虚拟内存(一个进程再启动的时候,系统都会给它分配4个G的虚拟内存,一般情况下是按1:3来分配的,1个G给系统内核来使用,3个G给应用层来使用)。
内存分配
3个G的内存又分为:
注:
以上表格有的时候会分为5段,就是把数据段分为数据段和BSS段,即已初始化数据段
和未初始化数据段。
有的时候将以上表格分为3段,就是将堆和栈分到一起,称为堆栈。
静态数据区也在数据段,比如static修饰的变量;
代码段存放的是编译好的二进制文件;
例如:
//全局变量
int num; //未初始化数据段,BSS段
char ch=’x’; //已初始化数据段
int main()
{
char*s=”helloworld”;// s存放在栈空间,”helloworld”属于只读数据段,不能被修改
s[0]=’x’;//这样写是不行的,”helloworld”属于只读数据段,不能被修改
static int a=0;//静态数据区
char *p=(char*)malloc(sizeof(char)*128);//p栈空间,申请的128个字节属于堆空间
char p1[ ]=”helloworld”; //p1是局部变量在栈空间,”helloworld”放在p1里面的,所以”helloworld”就是在栈空间
char *p2=”helloworld”; //p2属于栈空间,占8个字节,它指向了只读数据区的”helloworld”
return 0;
}
堆和栈的区别?(面试重点)
- 堆空间是用户管理的,用户申请,用户释放;而栈空间是系统管理的,当用户定义一个变量的时候,系统会自动为它开辟一个空间,当一段程序运行完时候,系统会自动释放掉这个空间。
- 堆空间更大,栈空间更小,如果我们要申请连续的大内存,比如说10万个整数,我们可以去堆空间申请。
- 堆空间使用效率低(因为空间大,内存记在一个链表里面,申请内存时,它需要去查一下链表,查一下哪边可以使用),栈空间使用效率高(一直往前申请就可以了)。
- 堆空间函数结束不会释放(需要用户手动释放),栈空间函数结束自动释放。
申请内存的函数
malloc
函数原型:
void *malloc(size_t size)
realloc
函数原型:
void *realloc(void *ptr, size_t size)
如果你用malloc申请128个字节不够用了,可以通过realloc从128个字节后面即第129个字节开始再申请一些空间,但是前提是后面够用。
如果第128个字节后面不够用了,它会在其他的地址给你申请一个空间,将前面128个字节的内容拷贝进去,然后再在后面给你申请一块空间,返回一个新地址给你使用。
所以realloc相当于是拓展内存的。
free
动态申请的内存需要手动释放,如果不释放,会造成内存泄漏。释放后的指针应该置为空指针,否则会变成野指针。
gcc工具链
这一节我们来详细讲一节关于编译的事情。
一般我们在Linux终端想要编译我们写好的.c文件的话,直接就是输入“gcc 文件名.c -o 文件名”就行,但其实这一个过程是分为4个步骤进行的:
编译的过程(面试重点)
第一步,预处理:
处理所有以#开头的代码,包括头文件、宏定义、条件编译
比如:gcc -E hello.c -o hello.i
注:.i就是预处理后的文件。
头文件预处理过程就是将头文件直接展开。意思就是它会到操作系统里面把stdio.h这文件给找出来,然后将这个文件的内容复制一份粘贴出来,这就是将头文件展开的操作。
头文件的”<>”尖括号就是表示到系统指定的目录下面去找。
这是头文件的预处理过程。
宏定义比如#define PRICE 10 int sum=PRICE*100;预处理的话直接就是将PRICE替换成int sum=10*100
条件编译的预处理过程就是直接把#if 0....#endif框选的代码丢掉。
如果你写了一段代码后不需要它了,就可以这样:
#if 0
Void f1()
{
Printf(“helloworld\n”);
}
#endif
这种操作类似于/* */注释掉代码。
第二步,编译:
语法检查以及将C语言变成汇编语言
比如:gcc -S hello.i -o hello.s
注:编译后的文件是.s文件,也就是汇编文件。
第三步,汇编:
将汇编语言变成二进制文件
比如:gcc -c hello.s -o hello.o
注:.o文件就是二进制文件。
以ELF开头,就是二进制文件:
我们可以用file来看一下
第四步,链接:
链接代码需要用到的其他文件(库文件等)
比如:gcc hello.o -o hello
上一步得到的.o文件还是不能够直接执行,是因为它还差一步。比如我们刚刚写的这个代码用到了printf,当程序从main()函数开始执行到printf这里时,它不知道printf是什么东西,我们还差一步链接的操作,即把“printf这个代码在哪”这个信息链接到原文件里面去。
链接有两种,一个是静态链接,一个是动态链接(我们直接写的“gcc 文件名.c -o 文件名”就是一种动态的链接)。
动态链接就是将“printf这个代码在哪”这个信息链接到原文件里面去,等到运行的时候它会根据我们提示的位置信息就找到printf()这个函数,这样才能执行。如果libc文件丢了则不能执行了。
比如gcc hello.o -o hello-动态
静态链接生成的二进制文件比动态链接的文件大。因为静态链接时是直接将libc中的printf()函数的实现放到生成的二进制文件中。但是,这种方式在libc文件丢失的情况下还是可以执行。
比如gcc hello.o -o hello-静态 -static
常用参数
-c:只是编译不链接,生成目标文件“.o”
-S:只是编译不汇编,生成汇编代码
-E:只进行预编译,不做其他处理
-g:在可执行程序中包含标准调试信息,用gdb调试程序的时候用到。
-o file:把输出文件输出到file里
-V:打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir:大写的i,在头文件的搜索路径列表中添加dir目录,如果我们写的头文件和.c文件不是在同一个文件中,编译的时候可以将头文件放在-I后面。
-L dir:在库文件的搜索路径列表中添加dir目录,链接静态库和动态库的时候用到。
-static: 链接静态库
-l library :连接名为library的库文件
静态库和动态库的制作(开发重点)
静态编译用到静态库,动态编译用到动态库;
静态库的制作过程
什么是静态库?怎样制作静态库?
1、写一个hello.c文件,里面调用了f1()和f2()两个函数,然后f1()和f2()的实现分别写在单独的文件f1.c和f2.c中。
hello.c
f1.c
f2.c
2、接下来首先第一步是将f1.c和f2.c两个原文件转换成二进制文件。输入”gcc -c f1.c f2.c”这样就会自动生成两个.o文件。
3、然后我们用“ar -crv lib(静态库的名字随便取).a f1.o f2.o”,注意一定是以lib开头,加上静态库的名字,.a是静态库的后缀,比如“ar -crv libx.a f1.o f2.o”x就是静态库的名字。最终生成的libx.a文件就是静态库。
4、当我们拿到这个静态库的时候,要执行hello.c文件就要结合这个静态库才行,比如“gcc hello.c -o hello -static -L . -l x”。
注:“.”表示在当前目录下面。-static是静态链接,-L 后面接库文件的路径,-l后面是库文件的名字。
这样它就生成了hello的二进制文件
5、接下来我们改一下hello.c文件,在main()函数的上方声明一下f1和f2这两个函数。
声明之后我们就可以正常编译hello.c文件了。
动态库的制作过程
什么是动态库?怎样制作动态库?
1、同上,写好f1.c和f2.c和hello.c文件之后,输入“gcc -fPIC -shared -o lib库的名字.so f1.c f2.c”,比如gcc -fPIC -shared -o libxx.so f1.c f2.c,最终生成的libxx.so就是动态库。
2、我们编译hello.c文件之前先链接动态库,“gcc hello.c -o hello -L . -l xx”。
3、链接好之后用”ldd 加上”gcc hello.c -o hello -L . -l xx”这一步操作后生成的二进制文件名”来看看它会默认去取哪里找,比如我们看到它会去/lib这个目录下面去查找,我们将libxx.so动态库拷贝到/lib目录下。
注:不同的操作系统可能会去不同的目录下去找,”ldd 加上”gcc hello.c -o hello -L . -l xx”这一步操作后生成的二进制文件名”可以查找二进制文件所用到的库在哪。
拷贝进去/lib中
这个时候我们再执行hello就成功了
以上就是这篇内容,如想了解更多,欢迎订阅本专栏!
如有问题可评论区或者私信留言,如果想要进交流群请私信!