1. 程序的内存布局
回答:下面我主要说一下x86体系一个进程下的布局,x86体系下一个程序运行以后就叫做进程,进程的地址空间分为两部分,低3G是用户空间,高1G是内核空间,我们平常所写的应用程序运行以后代码分配的内存所占用的空间主要集中在用户空间。用户空间从内存布局上主要分为代码段.text、.data、.bss、堆、栈。代码段是专门存放指令的只读不能写;.data和.bss存放数据,可读可写,初始化且初始值不为0的数据存放在.data段,未初始化或初始值为0的数据存放在.bss段;C语言里通过malloc和C++里通过new的内存会在堆上;栈是由系统分配的,运行一个函数,调用一个函数就会给这个函数开辟栈内存,调用函数这里还涉及了开辟栈帧的具体过程、函数调用堆栈,和面试官可以交流是否说说。
2. 堆和栈的区别
堆和栈即涉及内存又涉及数据结构层面。
(1) 在内存层面,分为堆内存和栈内存。
- 什么时候会用到堆内存,我们在代码上通过malloc或者new开辟堆内存,通过delete或free释放堆内存,是我们手动开辟释放的,否则会出现内存泄漏;栈内存什么时候会用到,就是调用一个函数会用到栈内存,出函数的}栈内存会进行回收,栈内存是系统自动开辟释放的。
- 堆内存的大小远远大于栈内存,所以在分配比较大的内存时尽量分配到堆上而不是栈上。
- 堆分配是由低地址到高地址;栈分配是由高地址到低地址。
(2)在数据结构中堆(二叉堆、大根堆和小根堆)和栈的区别。
- 栈是属于一个线性表,堆不是线性的,相当于一个二叉树。
- 栈是一个可以满足先进后出,后进先出的线性表,堆常用的就是一个二叉堆,二叉堆在数据结构里经常用的就是大根堆和小根堆,大根堆就是二叉树里所有节点的值最大的值在堆顶(根节点),小根堆就是这个二叉树里所有节点值最小的在堆顶,比如优先级队列(priority_queue)
3. 函数调用参数是怎么传递的?
实参把值传递给对应的形参 先将参数从右向左压栈,然后将下一行指令的地址压栈,把调用方的ebp压栈,再将ebp指向esp,到时候访问对应的参数是通过ebp指针的偏移。
4. 为什么函数调用的参数要从右往左压栈?
C和C++要支持可变参函数,所以要从右向左压栈。为什么可变参函数要从右向左压栈? eg:
//可变参函数
void func(int a,...)
{
}
int main()
{
func(10,20,30);
func(10,20,30,40,50);
}
像我们的printf也是可变参函数。 我们的代码是从上往下依次进行编译的,在编译上面func函数的时候,高级语言代码都要编译成汇编指令,汇编指令进入{:
- push ebp
- move ebp,esp
- sub esp,4Ch
- rep stos 0xCCCCCCCC(在vs下才有这个操作,gcc下没有)
在func函数里有参数,要访问里面的参数a,假设压栈顺序是从左向右的,那么先后压入10,20,30,关键是指令的生成是在编译的时候生成的,编译的时候func在取参数的时候该如何取a,栈上的变量都是通过ebp的偏移来取的,func函数在编译的时候根本没有办法访问a,因为你是从左向右压栈的,那么就意味着第一个参数在栈底,后面的参数才在栈顶,将参数压栈后紧接着是下一行指令的地址,接着是func函数的ebp,在编译阶段它是不知道将来用户会传入多少个参数,所以是不知道把ebp偏移多少才能到第一个参数a的,这里是根本没有办法来访问func中所有的参数,因为编译阶段根本不知道将来用户会传入多少个参数。
所以必须要把左边已知的参数必须放在离ebp最近的地方,也就是反着压栈,依次是30,20,10,这样编译器在编译的时候就会知道ebp+4就是第一个参数的内存,接着向下访问就是后面所有可变参的所有参数,以这样的方式就可以给可变参生成指令了,就可以把下面所有参数访问完。
5. 有一个函数
string fun(string s1,string s2)
{
string tmp = s1 + s2;
return tmp;
}
主函数里面通过:string s = fun(s1,s2);调用,依照代码执行顺序分析对于string这个对象都有什么构造、拷贝构造、析构和赋值顺序
如果fun函数体内写成return s1+s2有什么区别?
就会省略tmp的构造函数和析构函数。
可以再说下对象优化的规则:
- 函数传递过程中,对象优先按引用传递,不要按值传递
- 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
- 接收返回值是函数的调用的时候,优先按初始化的方式接收,不要按赋值的方式接收
6. 一个结构体里面定义了一个char和double,它的空间内存布局是怎么样的?
占16B,内存对齐。
补充: