我是靠谱客的博主 想人陪洋葱,最近开发中收集的这篇文章主要介绍263-面经1(商汤C++机器学习),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1、程序的内存布局

程序运行时叫进程
就是进程的虚拟地址空间

在这里插入图片描述
这张图是32位x86体系下的进程的虚拟地址空间,下面是低地址,上面是高地址。

我们从最下面(低地址开始看)往上看:

  • 从0-0x08048000是0地址,然后从0x08048000往上,放的是代码段(.text段:指令,只读不能写);
  • 数据存储的.Data和.BSS段(没有初始化/初始化为0)
  • 然后是堆Heap(malloc,new)
  • 然后是Memory Mapping Segment共享库(printf,scanf这类要包含头文件的函数,比如说,我们写printf或者scanf,要包含<stdio.h>,这个头文件只有这两个函数的声明,没有定义实现,这些函数的实现是在libc.so里面,当我们程序运行的时候,如果需要用到printf或者scanf的时候,我们就会去加载libc.so共享库,这些共享库就在Memory Mapping Segment);
  • 然后再往上看,就是我们函数运行的栈Stack了(系统调用,函数详细的调用堆栈过程);
  • 下面的3GB是用户空间,上面的那1GB是内核空间(Kernel space)。

一个系统上可以运行几百个进程,每个进程它的用户空间都是独立的,但是内核空间都是共享的,因为操作系统只有1份!

关于.text段和.rodata段:

在进行内存布局的时候,操作系统去管理内存,都是以页面来进行分配的,但是.text段不一定就占1个页面,也有可能它不够用,会占用2个页面。
在这里插入图片描述
从段的角度来说,.text段和.rodata段是没有杂到一块存储的。
从内存的角度来说,它们都是相同内存属性的段,是放到同一块的。

再比如说,.data段和.bss段在内存上,属于同一个内存属性:可读可写,也是放到同一个段。
从段的角度来说,是肯定不会杂到一块放的,从内存的角度来说,是在一块放的。
在这里插入图片描述

2、堆和栈的区别

在这里插入图片描述
在内存层面,堆内存和栈内存的区别:

  • 堆内存是: 在代码上通过malloc或者new,通过free或者delete还释放堆内存。堆内存是我们用户手动开辟的,手动释放的。
  • 栈内存是: 调用一个函数,就用到栈的内存,出函数的右括号},栈内存进行回收。栈内存是系统自动开辟,自动释放的。

在数据结构上:

  • 是属于线性表,满足先进后出,后进先出的线性表
  • 不是线性的,常用的是二叉堆,就是二叉树,二叉堆经常用的,大根堆(该二叉树中所有节点的值最大的那个节点在堆顶,就是根节点)或者小根堆(该二叉树中所有节点的值最小的那个节点在堆顶,就是根节点)
  • 应用场景: 优先级队列(priority queue),C++底层默认实现的是大根堆)

3、函数调用参数是怎么传递的?

函数调用,参数压栈:

从右向左,先压右边的实参入栈,压完实参后,就该压下一行指令的地址,然后把调用方(main函数的栈底地址ebp)压过来,然后访问形参,然后通过ebp指针的偏移访问相应的实参,ebp+4,访问到第一个实参,ebp+8访问到第二个实参。
在这里插入图片描述

4、为什么函数调用的参数要从右向左压栈

在这里插入图片描述
在这里插入图片描述
我们写可变参,最前面左边得有一个参数,后面才能加…表示可变参。
在这里插入图片描述
可变参怎么用?
在这里插入图片描述
这两次func调用,第一个参数10都是传给a,后面传几个参数无所谓,是可变的,类型不一样也可以。

我们的printf就是一个可变参:
在这里插入图片描述
第一个参数(格式化字符串)里面有多少个%d,%c等等,后面就写对应的参数个数。

我们知道,代码是从上到下依次进行编译的,在编译到func函数的时候,高级源代码都要编译成汇编指令,这个汇编指令一进入这个函数的左括号{,执行push ebp,把调用方main函数的栈底地址ebp压到当前函数栈的栈底,也就是func函数的栈底,然后执行mov ebp esp,把esp的值赋给ebp,让ebp刚才指向main函数的栈底,现在指向func函数的栈底,

然后sub esp,4ch 就是给func函数开辟栈帧,调用一个函数就开辟栈帧;

在VS下,还有一个指令操作:rep stos 0xCCCCCCC,gcc下是没有这个操作的。

然后,我们这个func函数里面,有这个参数,它要访问这个参数a,

假如说这个参数是从左向右压栈的,10就是在最底,然后20,30,40,50。

但是指令是在编译时生成好的,它运行的时候是不会自己选择的,怎么样生成的指令,怎么样运行就可以了,关键是func函数在编译的时候,取参数,取a是怎么取的?

栈上取这个局部变量都是通过ebp的偏移来取的,func函数在编译的时候,根本就没有办法访问到这个a了,因为现在是从左向右压的,第一个参数肯定是在栈底,后面的参数在栈顶,紧接着压下一行指令地址和func函数的ebp,func函数在编译阶段,不知道它会有多少个参数,不知道用户传几个参数,根本不知道,它在访问a,不知道把ebp偏移多少,所以是没有办法生成指令去访问它的参数的,因为编译阶段不知道用户会传多少个参数。
在这里插入图片描述

所以它必须把左边的这个已知的参数必须放在离ebp最近的地方,反着压!
已知的这个参数就在栈顶了,所以它就永远知道ebp+4就是这个已知的参数的内存!!!!

在以后运行的时候,不管用户在这里传的是多少个参数,就是可以通过ebp+4来访问第一个参数,然后接着往下访问,就是可变参的参数了,在编译阶段,就可以给可变参数生成指令了!!!它生成指令就可以把下面的所有参数都可以访问完了。

所以说,从右向左压参数,意味着第一个参数是最后压的,最后压的肯定在栈顶,最后在func函数生成指令的时候,ebp+4就可以访问到这个第一个参数a了。

如果编译的时候是从左向右压的,意味着左边的先压的在栈底,右边先压的在栈顶,在编译阶段,不知道参数有多少个,怎么通过ebp偏移访问第一个参数a呢?没有办法访问啊!

也就是说,从右向左压,才能合理的确定参数的个数。
在这里插入图片描述

我们看printf,通过可以访问第一个参数:格式化字符串,能访问这第1个格式化字符串,就根据里面的%d%c之类的就可以往下继续取其他的参数了。
如果看到有一个%d,就向下访问,如果又看到有一个%c,也继续向下访问,直到访问到这个格式化字符串的了,停止向下访问。

6、有一个函数

在这里插入图片描述
在这里插入图片描述
参数是从左向右压的,从实参到形参,我们看到这是既没有用指针传递,也没有用引用传递,所以都是要生成新的string对象,实参s2,s1到形参s2,s1,调用的是拷贝构造函数,这里调用2次拷贝构造函数,先构造s2,然后构造s1;(引用接收就不会有拷贝构造了)

string tmp=s1+s2;这里s1+s2会产生一个新的string对象,然后拿这个新的string对象拷贝构造tmp对象。

然后tmp对象返回,通过C++11本身的优化,调用右值的拷贝构造函数(移动构造函数),拿tmp直接拷贝构造main函数的s对象,然后出fun的右},析构tmp对象,然后析构s1, 然后析构s2,最后析构s。
在这里插入图片描述
在这里插入图片描述

也就是,因为C++编译器的优化,下面这2种写法等价:(不会先调用构造,再调用拷贝构造)
在这里插入图片描述
我们再看,如果写成这样:

在这里插入图片描述
现在是s1+s2的结果直接拷贝构造主函数的s对象了。省去了刚才tmp的拷贝构造和析构函数的调用。

注意:
在这里插入图片描述

7、一个结构体里面定义了一个char和double,它的内存布局?

在这里插入图片描述
答案是16

  • C语言的结构体和C++的类都是要内存对齐,方式是一样的
  • 第一行:char占1个字节,剩下的7个字节补位的;第二行的8个字节就是double。

在这里插入图片描述
就是1!

在这里插入图片描述
都是char,最长的分量是1个字节,按1个字节对齐,总共3!

空结构体多大?
在这里插入图片描述
空结构体大小为1!

我们的总结如下:
在这里插入图片描述
为什么在gcc下,C语言的空结构体大小是0,C++的空结构体大小是1?

  • 1、空结构体,不需要访问什么东西,啥也改干了,所以C语言中,定义空结构体的大小是0;
  • 2、而在C++中,struct定义的东西不叫变量,叫做对象。
  • 对象和变量的本质区别是: 定义一个变量只需要内存就可以了,但是C++生成对象时得先有内存,然后还得要有构造,构造函数编译的时候会生成this指针,所以,要生成对象,必须要有一块内存,给this传地址,才能调用构造,创建对象。
  • 所以创建一个空的结构体(空类),大小是1,因为内存的最小单位是字节,就是1个字节大小。构造函数只有知道内存地址,才会去这个内存地址上做构造对象。

在这里插入图片描述
sizeof(Test) 和 sizeof(t) 是一个意思!

比如下面的sizeof(Data)和sizeof(data)大小都是1。
在这里插入图片描述

只有空结构体大小是1,或者结构体中只有一个char类型的变量,大小是1,其他的都是按照变量大小内存对齐

在这里插入图片描述
基类和派生类都是空类,但是只是继承关系,Data3就是空类,大小就是1。
在这里插入图片描述
在32位系统下,如果是空类虚继承空类,多了一个vbptr指针,所以大小是4字节。

在这里插入图片描述
如果基类中有虚方法,就会产生虚函数表,则派生类虚继承这个基类,又多了一个vfptr,所以此时,大小是8字节(x32系统)。

不管多少个虚函数,都是只有一个vfptr哦,影响的只是虚函数表vftable的大小而已,不影响对象的大小,都是只有一个vfptr

8、智能指针问题

在这里插入图片描述

9、继承和多态问题

最后

以上就是想人陪洋葱为你收集整理的263-面经1(商汤C++机器学习)的全部内容,希望文章能够帮你解决263-面经1(商汤C++机器学习)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部