那些后台开发中的基础知识(一)

作者: | 更新日期:

前段时间面试了一个人,面试前自己准备了一下,这里备忘一下。

本文首发于公众号:天空的代码世界,微信号:tiankonguse

大家好,这里是tiankonguse的公众号(tiankonguse-code)。
tiankonguse曾是一名ACMer,现在是鹅厂视频部门的后台开发。
这里主要记录算法,数学,计算机技术等好玩的东西。

这篇文章从公众号tiankonguse-code自动同步过来。
如果转载请加上署名:公众号tiankonguse-code,并附上公众号二维码,谢谢。

零、背景

前段时间工作需要,准备了一下后台开发上的基础知识。
其实这些基础知识我也不懂,我大学四年只看了算法相关的书籍,其他的都没看过。
不过自己有几年工作项目经验,所以平常和大家聊的时候,自己就根据以前听说的知识片段进行推测:假设自己要实现或设计这样一个东西,会怎样。一般情况下自己想的就是实际的样子。
现在这些基础知识有必要简单的梳理一下,算是自己备忘一下吧。

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

一. 段

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

大概像下面的样子:

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

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

二、 堆

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

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

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

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

这里实际上就涉到怎么管理内存了?
这个管理内存的话题这里就不说了。

三、栈

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

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

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

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

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

有人说使用多进程或者多线程,那是一种方案,但是我们有更好的,只有一个进程一个线程,那就是协程。
我们知道一个程序的执行实际上就是不断的执行函数,然后再栈上入栈出栈。
只所以不能在io阻塞的时候做其他事情,就是因为栈只有一个,我们调用io操作时,前面的栈里保存了io操作前的一些数据。
我们做其他事情了,栈就会改变,而当前io操作结束了,不能回到对应栈的位置。

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

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

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

四、符号

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

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

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

五、继承与多态

其实对于继承和多态这个话题,我不会深入探讨,太浪费时间,且意义不大。

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

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

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

六、总结

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

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

对了现在开通了博客、公众号、算法小密圈、IT技术交流微信群。
要加微信群的可以加我微信,我拉大家进群。
比较好玩的算法放在小密圈发布。
欢迎大家加入看各种算法的思路。

长按图片关注公众号,阅读不一样的技术文章。

本文首发于公众号:天空的代码世界,微信号:tiankonguse
如果你想留言,可以在微信里面关注公众号进行留言。

关注公众号,接收最新消息

tiankonguse +
穿越