一腔热血勤珍重,洒去犹能化碧涛。
本文出自2020年字节跳动秋招提前批面试真题,原题面经及本文精讲均已收录在:
GitHub https://github.com/hlwxzmj/Killing-Campus-Interview 与「互联网校招面经」
微信搜一下「互联网校招面经」,第一时间订阅获取更多精选校招面经与面试真题详解。
张三同学原本是一名法学专业的研究生,但是由于他自己实在是对法律不是很感兴趣,加上天天遭受到某位姓罗的老师的粗暴对待,就想要转行到互联网行业。目标的岗位就是当前非常火热的java工程师。
经过一顿操作猛如虎的各种高级框架学习之后,张三这回觉得自己终于大成,可以在互联网界重出江湖,来一雪之前在前在法律界被人天天毒打的耻辱。他出山第一个矛头就直指互联网界著名帮派之一——字节派,他觉得以他的实力肯定可以轻松拿下字节派的入门资格,为他日后东山再起做准备。他当天中午投递了简历,没想到两小时后就立刻收到了明天的面试邀请,内心一阵狂喜,头脑中已经想象出明天跟面试官愉快畅谈动人场景。
第二天,他胸有成竹的来到了面试室,坐在对面的面试官是一位非常年轻帅气,年少有为的小哥哥。
他对张三说:“小伙子我看你简历上写了会很多的东西,很不错嘛。但是由于你投递是初级岗位,我就先从一些简单的基础知识跟你聊聊吧。”
张三一听,心中大喜。问基础是吗?这回稳了。
面试官:“那我们先来聊聊最基础的函数调用吧。你说一说函数调用过程的基本原理吧。”
张三:“ 啊这…”
面试官:”那栈帧你有了解过吗?“
张三:”啊这…“
面试结束
想要了解程序在运行时函数详细的调用过程,首先要对程序运行时的内存情况有一个基本的了解。下图是一个正在运行的程序的经典内存布局:
内核区域:是给系统内核使用的内存空间,应用程序无法访问这一区域。
栈:是函数调用相关的核心内存区域,用于维护函数调用的上下文!从上图中可知,栈区域的增长是从上往下的,也就是从高地址向低地址增长。
动态链接库映射区:用于映射装载的动态链接库。
堆:对是用来存放应用程序动态分配的内存区域,比如大家常用的malloc或者new一个对象实例的时候往往就存放在此处的内存区域。而堆区域是从低地址向高地址增长。
未初始化区域:存放未初始化的全局变量和静态变量。
初始化区域:存放已经初始化的全局变量和静态变量。
保留区:保留区是内存中受到保护而禁止访问的区域。
栈是计算机科学中非常重要的的一个概念, 它有两个基本的含义:
1、是数据结构中的一种,在数据结构中栈被定义为一种特殊的容器,可以将数据入栈(push),也可以将栈中的数据出栈(pop)。但是栈必须遵循一个原则:先入栈的数据后出栈。
2、在计算机系统中,栈则是上面提到的程序运行时用于存储函数调用相关逻辑的内存区域。这是我们本文需要重点关注的。
上图就是一个计算机系统中栈的实例,可以看到栈底是位于高地址方向,而栈顶是位于低地址方向。
其中esp
寄存器一直指向栈顶的位置。此时如果往栈中压入数据,会导致esp
减小,所指向位置下移,完成数据入栈操作后依旧指向栈顶位置。而出栈操作则刚好与入栈操作相反。
入栈后
栈之所以与函数调用密切相关,是因为栈中保存了一个与函数调用息息相关的非常关键的信息,栈帧。栈帧中一般包含如下几种信息:
如下图就是一个函数栈帧结构:
其中esp
与ebp
寄存器是函数调用过程中最重要的两个寄存器,它们划定了函数调用的内存活动范围。
其中,上文有介绍过,esp
寄存器始终指向栈内存的顶部,同时也指向了当前函数栈帧记录的顶部。
而ebp寄存器则始终指向函数栈帧中一个固定位置。在ebp
之前首先保存了old epb
的值,它的地址是epb+4
,这个值是上一个函数栈帧的ebp
寄存器中应该存储的值。其次再往前是这个函数的返回地址,也是上一个函数调用当前函数的地址,它的地址是epb+8
。后面以此类推。
看到这里一下子就好像明白了函数调用其中的奥妙!
先把所有的函数参数压入栈中
把要返回的函数地址压入栈中
跳入到调用的函数体中执行
push %ebp
: 先把上一个函数的ebp
寄存器中的值压入栈中(如之前图片中的old ebp
), 此操作是为了在函数返回时恢复上一个函数的ebp
寄存器中的值。
%esp
, %ebp
: 把esp
中的数据赋值给ebp
。此时esp
寄存器刚好指向栈顶,栈顶就是刚刚压入的old ebp
。 此操作完成后,ebp
寄存器中的值被刷新,指向了新的函数的栈帧的固定位置。pop XXX
: 把函数过程内的数据都出栈。%ebp
, %esp
: 将ebp
寄存器中的值赋值给esp
。注意!注意!此时esp只向了跟ebp相同的位置啦!即此时栈顶指针寄存器esp刚好指向了old ebp的位置!
ebp
: 然后继续进行出栈操作,将old ebp
出栈然后赋值给ebp
。看到这里是不是恍然大悟啦,ebp
寄存器的值在此被刷新,还原成了上一个函数调用时应该保存的值。esp
寄存器指向栈顶,而此时的栈顶刚好是返回地址,直接跳转回去!一次函数的调用流程就此结束!先看一下样例源代码,这是一个比较基础的c++求和小程序代码:
我们在用gdb disassemble命令看看调用sun函数时,底层汇编层代码到底都在做什么!看不懂没关系,可以往下看!
哇,看这个sum函数汇编代码的开头结尾代码流程,是不是感觉非常眼熟?跟上文中所讲的几乎完全一致???
来,首先看第一部分刚进入sum函数的时候:
push %rbp
: 先把上一个函数的ebp
寄存器中的值压入栈中
mov %esp
,%ebp
: 然后把esp
中的数据赋值给ebp
。
然后呐!中间部分,执行函数内计算过程,具体汇编代码看不懂的同学可以不必深究。
等到函数返回的时候:
mov %ebp
,%esp
:将ebp
寄存器中的值赋值给esp
pop %ebp
: 然后继续进行出栈操作,将old ebp出栈然后赋值给ebp
。
最后跳转回返回地址!!!
函数大家肯定在熟悉不过了,但是面试官问函数调用原理往往是想进一步考察候选人对计算机基础知识的扎实程度。这个考察这个知识点的类似问题有很多,比如比较常见的函数一直递归调用下去会出现什么问题?(栈溢出),程序运行时堆栈你有了解吗?(上面进程内存结构图)等等。
回答的时候只要对上文所讲到的大致程序运行时内存结构,函数调用基本流程跟面试官说清楚就可以了。如果还能具体的讲出ebp,esp相关寄存器的作用那就是了解的非常深入了,会给面试官留下非常好的印象。
如果你能看到这里,那猪真的是超级开心了,最起码几天的辛苦画图码字是值得的。
我以后每周都会分享高频精彩的互联网大厂技术岗位面试真题,力求用最简洁的语言,最有趣的风格,最干货的内容去用心创作优秀的技术文章。猪真的一直觉得:技术本原本枯燥,但是技术文章本不应该同样枯燥。如果觉得猪的这篇用4天时间写的文章还不错的话,求你花2秒钟的时间给猪呗!第一秒在文章左下角点个赞!第二秒点个关注!如果你还能在评论区夸猪一下,那猪更是乐开了花呢!如果大家还有什么想要深入了解的大厂高频面试知识,也期待评论区留下你的声音。
创作辛苦,好心人不要白嫖这个猪,你们的三连就是我继续更新的最大动力,希望下一篇文章猪还能遇见你!
如果本篇文章内容有任何错误,非常欢迎大家评论区直接狂揍我,我把右手剁了请评论的大家吃猪蹄。
技术文章每周持续更新,本文出自2020年字节跳动秋招提前批面试真题,原题面经及本文精讲均已收录在:
GitHub https://github.com/hlwxzmj/Killing-Campus-Interview 与「互联网校招面经」。
微信搜一搜「互联网校招面经」,第一时间订阅获取更多精选校招面经与大厂面试真题题解。欢迎同学们关注,star。
同时我还花了很久的时间,从全网络搜集,整理,精选,精装了字节跳动2021年校园招聘提前批面试真题,直接分享给大家:
链接:https://pan.baidu.com/s/1MPcVH6LvKaicpc7Xtklskg 提取码:oq6i
我是风口浪尖上的猪,浙江大学计算机硕士,毕业前工作过三家大厂四个部门。
一只致力于用最简洁的语言,最有趣的风格,最干货的内容去用心创作优秀技术文章,用心与读者做朋友的猪。
感谢大家的点赞,收藏与评论,我们下期见!