JobPlus知识库 IT 软件开发 文章
后台开发基础知识

零、背景

前段时间工作需要,准备了一下后台开发上的基础知识。
其实这些基础知识我也不懂,我大学四年只看了算法相关的书籍,其他的都没看过。


不过自己有几年工作项目经验和分析能力,所以平常和大家聊的时候,自己就根据以前听说的知识片段进行推测:假设自己要实现或设计这样一个东西,会怎样。一般情况下我们想的和实际的样子查不了太多的。


现在这些基础知识有必要简单的梳理一下,算是自己备忘一下吧。

PS:本想一篇文章把所有东西都记录了,结果只写了这点东西,其他的再找时间记录吧。

一. 段

程序中的地址都是虚拟地址(沙盒),虚拟地址经过有组织的固定划分,连续的一片地址就是一个段。
经常听说代码段,堆,栈,数据段其实就是对内存布局的一个划分。

大概像下面的样子:

具体到实际使用中,需要储存各种辅助数据,所以还有很多其他杂七杂八的段,比我像下面样子:

于堆和栈运行时才需要的,所以这里还看不出来其使用的地址起始位置。
另外也可以看到,全局变量和静态变量如果初始化的值为0,也在BBS段。
其实BBS段名字叫做未初始化段不好,应该叫做值全0段。

上面那个.gnu.hash段就被坑过,交接到我手上一个旧项目,编译后发上去直接coredump,发现是编译器与操作系统的问题,需要编译时手动指定这个东西。

二、 堆

对于堆和栈,这里分别单独划分一个小节来聊聊。

我们平常申请动态内存时,有人会说在堆栈上申请的,不是堆吗?
比如c语言的malloc系列函数,C++的new操作符。
这些我们用到的申请内存的接口,实际上并不是直接向系统申请内存的。
这些都是语言提供的接口,对系统接口进行了封装,简单的理解就是对系统函数封装了一下。

这里的封装主要是内存管理。
我们调用malloc来申请内存,当内存大小小于128KB时,会在堆上调用brk进行申请,如果大于128KB了,会使用mmap去自由堆空间申请。
brk申请的内存和自由堆有一个很大的区别:brk实际上还是一个栈(敲黑板),如果栈顶的内存不释放,其他地方即使释放了也没有还给操作系统。
而自动堆空间才是真正的堆申请,释放后就立马还给操作系统了。

实际上我们一般不直接使用语言自带的malloc,因为自带的内存管理会频繁的向系统申请释放资源,成本很大的。
我们使用一些内存管理库,预先申请一大块内存,后续申请的时候再在那个大内存生分配小内存。
由于这个申请小内存不是系统调用,从而可以提高一些性能。比如两年前我的一个服务,就引入了TCMalloc。

这里实际上就涉到怎么管理内存了。
这个管理内存的话题这里就不说了,后面有时间可以专门聊聊。

三、栈

栈其实比较简单。
主要用来储存函数的参数和局部变量。
平常说的爆栈了也是这个栈满了。

这么简单的栈为什么要单独说呢?
因为要聊聊协程。

什么是栈呢?
就是一片连续的地址,我们可以按照先进先出的规则进行储存函数的参数和函数内的局部变量。
那如果我们自己申请一片内存,然后修改寄存器的值,就可以把自己申请的内存当做栈来使用了。

这样有什么好处呢?
如果大家写过网络编程的话,就知道同步程序很容易写,顺着写就行了。
但是同步程序有个问题:当前进程阻塞了,只能处理一个请求。
如果我们想在一个io操作阻塞的时候去做其他事情,就需要使用把程序状态化。

状态化对于逻辑能力比较强的人也很简单,但是并不是所有人都能很快的理解状态机,如果状态复杂了,即使理解了也很容易弄错。
所以我们需要一种既能同步写程序,又能在io阻塞的时候去做其他事情的方法。

有人说使用多进程或者多线程,那是一种方案,但是我们有更好的,只有一个进程一个线程就能实现,那就是协程。
我们知道一个程序的执行实际上就是不断的执行函数,然后在栈上入栈出栈。
之所以不能在io阻塞的时候做其他事情,就是因为栈只有一个,如果去做其他事情,当io操作结束回来时,回不到之前那个栈了(给大家一个问题:状态机为什么没这个问题?)。

我们发现问题的本质了,自然就可以找到解决方案了。
针对每一个io操作我们独立分配一个栈不就解决问题了吗?

栈是什么?一片连续地址。
那我们就动态创造这样一片连续的地址,把sp指向它,也可以当做栈使用的。
然后就可以以同步的方式写异步代码了。
听过线程池,进程池,连接池,现在有了栈池,叫做协程池更好。

这里其实还有一个问题:爆栈。
由于栈是我们动态创建的,为了保证并发数,栈的大小很小,因此很容易遇到使用的栈空间过大,导致不够用。
所以在协程中,需要把握好临时变量内存的大小了。

四、符号

编译成程序后,一切都变成符号了。
变量名,函数名,类都是符号。

聊到符号,不得不说符号的组成。
还会问c++重载怎么实现的?默认参数怎么实现的? c++和C的区别等等?

针对这些问题,下面一张图就解释清楚了。

C语言的符号没有任何前缀后缀。
C++的有个前缀,后缀是参数类型列表。由于符号上有参数类型,所以可以实现重载。
对于默认参数,我们发现没有生产新的符号,那是因为默认参数是在编译的时候实现,即在调用函数前插入了默认值。


五、继承与多态

其实对于继承和多态这个话题,我不会深入探讨,太浪费时间,天也不早了,该睡觉觉了。

谈起C++,必谈的就是多态了。
什么是多态呢?
父类的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
注意:这里引用也是可以的哦。

对于多态这个话题,一般都是问怎么实现多态的和为什么析构函数需要virtual,对于其他话题又不敢问的太深,怕一聊进去就聊不出来了。
比如虚函数表什么时候确定的?
编译时虚函数表示什么样子?
对象实例化后我们能手动修改虚函数表吗?
多重继承上也有一些虚函数的话题,这里就不提了。

对于继承这个话题,棱形继承的内存布局也不提了。

六、总结

这里只是记录了几个最最基础的知识点。
其实不知道这些也不影响我们敲代码,但是天天使用这些东西,了解了可以让我们对自己的服务有更深的了解。
尤其是遇到那些奇葩的问题时,不知道对应的知识点,恰好又没有合适的关键词去google的话,可能好几个小时都找不到问题原因。

好了,一写几个小时又过去了,所以有不对的地方欢迎拍砖。


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持
0人赞 举报
分享到
用户评价(0)

暂无评价,你也可以发布评价哦:)

扫码APP

扫描使用APP

扫码使用

扫描使用小程序