-
2021-11-04 22:54:19
读书笔记:游戏引擎架构第二版
目的:了解存储空间如何分配,多种C/C++变量类型如何运作,需要认识的C/C++的内存布局。
一 可执行映像
当生成C/C++ 程序时,链接器创建可执行文件,这种流行的可执行文件格式 称为可执行与可链接格式。一些平台的可执行文件格式.elf 作为扩展名,windows上的可执行文件格式使用.exe作为扩展名。
无论是哪种文件格式,可执行文件总是包含程序的部分映像,程序执行时这部分映像会放在内存中。之所以被称为部分映像是因为:由于程序除了把可执行映像置于内存中,一般也会分配额外的内存。
可执行映像被分为几个相连的块,这些块被称为段或者节(section)。每个操作系统的可执行文件布局方式都有些差异,同一个操作系统的不同可执行文件也会有些微差别。一般来说,映像文件最少有以下四个段组成:
·代码段:包含程序中定义的全部函数的可执行机器码;
·数据段:包含全部 获初始化的全局以及静态变量。链接器会为这些变量分配所需的内存,其内存布局将会和程序执行时完全一样,并且链接器会填入适当的初始值。
·BSS段:包含程序中定义的所有未初始化的全局变量和静态变量。(C/C++中明确定义,任何为初始化的全局变量和静态变量皆为零。)不过,与其在BSS段存储可能很大块的零值,链接器只需要简单的存储所需零值的字节个数,足以安置此段内未初始化的全局以及静态变量。当操作系统载入程序时,就会保留BSS段所需的字节个数,并为该部分内存填入0,之后调用程序进入点(比如main())。
·只读数据段:rodata段。包含程序中定义的只读(常量)全局变量。比如所有浮点常量以及所有的const 关键字声明的全局对象实例就属于只读数据段。注意:通常编译器把整数常量,看作是明示常量,并且直接把明示常量插进机器码中,明示常量直接占用代码段的存储空间,不存储于只读数据段。
全局变量,是指由所有函数以及类声明外的文件作用域定义的变量。按照是否被初始化决定存储于数据段还是BSS段。static关键字可以把全局变量或者函数指明为内部链接,使其不显露在其他的翻译单元。但是除此之外,可以用static关键字来声明置于函数内的全局变量。函数静态变量的词法作用域只在他定义的函数之内(即变量的名字只能在函数内见到)。变量会在第一次调用其函数时被初始化(而不像文件域静态变量,在main()调用前已经被初始化了)。但是,以可执行映像内存布局来说,函数静态变量和文件域静态变量并没有什么区别,都是根据是**否被初始化而分别存储于数据段或者BSS段的**。
二 程序堆栈
当可执行程序被载入内存并运行的时候,操作系统会保留一块称为程序堆栈的内存。
当调用函数的时候,一块连续的内存会被压入栈,这个内存块被称为堆栈帧。比如函数a()调用了b(),函数b()的新堆栈帧就会被压入a()堆栈帧之上,当b()返回的时候,他的堆栈帧就会被弹出,并在b()之后的位置继续执行a()。
堆栈帧存储三类数据:
·堆栈帧存储调用函数的返回地址。当函数返回的时候,就可以凭这个数据继续执行调用方的函数;
·堆栈帧保存相关CPU寄存器的内容。凭借这个过程被调用方可以使用任何觉得合适的寄存器,不需要担心调用方所需的数据被覆盖。当函数返回时,各个寄存器就会还原到调用方可以继续执行的状态。如果函数有返回值,该值就会存储在指定的寄存器中,使调用方能够使用,但是其他寄存器会恢复原来的值。
·堆栈帧还包括函数里所有的局部变量(自动变量)。凭借这个过程,每个函数调用都各自保持一组私有的局部变量集合。甚至函数对自己递归回掉也是一样的道理。(实际上,一些局部变量会分配使用cpu寄存器,不是存在堆栈帧。但是这些变量在大部分情况下,运作方式如何使用堆栈帧)
堆栈帧的压入和弹出操作,一般会通过调整一个cpu寄存器的值来实现,这个寄存器被称为堆栈指针。
当含有自动变量的函数返回时,他的堆栈帧会被舍弃,那么该函数内所有自动变量被视为不在存在。从技术上来说,这些变量所占有的内存仍然在已经被舍弃的堆栈帧中,当调用下一个函数这些变量所占有的内存就很可能被覆盖。
常见错误就是返回局部变量的地址。只有程序立即使用返回的地址,并且在使用期间不调用其他的函数,才有可能不出问题。大多数情况,这类代码会导致程序崩溃,特别难调试。
三 动态分配的堆
之前说讲的都是程序中的数据如何被存储为全局 静态或者局部变量。全局静态变量分配于可执行映像里,而局部变量则分配于程序堆栈之中。这两种存储方式都是静态的定义的,这意味着,其所需的内存大小与布局在编译链接程序时就能知道的。可能有时候不能再编译期完全知悉程序的内存需求。程序经常需要动态的分配额外的内存。程序经常需要动态的分配的额外的内存。
为了提供动态分配内存功能,操作系统会为每个运行进程维护一块内存,可以调用malloc()(或者操作系统的专用函数,比heapAlloc())中分配,稍后调用free()(或者操作系统的专用函数,比如HeapFree())把内存交还。这个内存块称为堆内存块或者自由存储。当动态分配内存时,有时候称为分配得到的内存时置于堆中的。
注:从技术上来说,堆是C语言和操作系统的术语,而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念。基本上,所有C++编译器预设会使用堆去实现自由存储。但是程序员可通过重载操作符,改用其他内存实现自由存储,例如全局变量做的对象池。
四 成员变量
C中struct 和C++中Class都可以用来把变量组成逻辑单元。二者的声明并不占用内存。这些声明仅用数据布局的描述,如果一个磨具用来制作struct 或者class的实例。
当声明了一个struct或class时,就能以和基本数据类型相同的任何方式进行分配(定义)。
比如:
·作为自动变量,置于程序堆栈上:
void someFunction()
{
Foo localFoo();
}
·作为全局变量,文件静态变量或者函数静态变量;
Foo gFoo; static Foo sFoo;
void someFuntion()
{
static Foo sLocalFoo;
}
·动态地从自由存储中分配。在此情况下,存储数据地址的指针或者引用
五 类的静态成员
根据的上下文,static关键字有许多不同的含义。
·当用于文件作用域时,static 意味着“限制变量或者函数的可见性,只有本.cpp文件才能使用该变量或者函数”。
·当用于函数作用域时,static 意味着“变量为全局,非自动,只在本函数内函数可见”。
·当用于struct或者class声明时,static意味着“该变量非一般成员变量,而是类似全局变量”。
注意:当static用于class声明时,并不控制该变量的可见性(文件作用域才会)。反而,其用途时区分正常的每个实例变量,以及行为像全局变量的每个类(per-class)变量。
类静态变量的可见性 时通过声明的public: private: protected: 关键字决定的。类静态变量自动包含于其被定义的class或struct命名空间里。若在class或struct以外使用这些变量,必须加入class或者struct的名字以消除歧义(例如Foo::sVarName);
如何extern的一般全局变量,类声明内的类静态变量并不占用内存,必须在一个.cpp文件内定义类静态变量以分配内存。
比如:
//foo.h
class Foo
{
public:
static F32 sClassStatict;//不分配内存;
}
F32 Foo::sClassStatic =-1.0f; // 定义内存和初始化;
六 对象内存的布局
对齐和包裹
特别留意struct 和class 在内存中的布局时,就能想清楚,如果大小不一样的数据成就交错放置,布局应该有什么变化。
比如
struct InefficientPacking
{
U32 mU1; //32位 4字节对齐
char* mP6;//32 4
F32 mF2;//32 4
I32 mI4;//32 4
U8 mB3;//8 1
bool mB5;//8 1
}
编译器简单地吧数据成员尽可能地紧凑的包裹在一起,然而这毕竟不是常态。相反,编译器通常会在布局中留下空隙。(一些编译器会通过#pragma pack或者命令行设置不留空隙,但预设行为会在数据成员之间留空隙)
为什么编译器会留下空隙呢?原因在于,事实上每种数据类型都有天然的对齐方式,提供CPU高效的从内存中进行进行读写。数据对象的对齐是指,其内存地址是否时为对齐字节大小的倍数(通常是2的幂)。
·1字节对齐的对象,可放在任何地址;
·2字节对齐的对象,只可以放在偶数地址,即地址最低有效半字节。0x0,0x2,0x4,0x8,0xA,0xC或者0xE;
·4字节对齐的对象,只可以4的倍数的地址(0x0,0x4,0x8,0xC)
·16字节对齐的对象,只可以放在16倍数的地址(即地址最低有效半字节为0x0)1Byte=8Bit 对齐是很重要的。因为现在很多处理器实际上只能正常的读写已对齐的数据块。例如,程序从0x6A341174地址读取32位(4字节)的整数,内存控制器就可以愉快的载入数据,因为地址是4位字节对齐的(这个例子的最低有效半字节为0x4)。可是若要从0x6A341173载入32位整数,内存控制器就需要读入两个4字节块:一块位于0x6A341170,一块位于0x6A341174。之后,还需要通过掩码和移位操作取得32位整数的两部分,在用逻辑or操作把两部分合并,并把结果写入CPU的目标寄存器。 一些微处理器甚至不做这些处理。如果读写非对齐数据,读出来或者写进入的可能只是随机数。对于另一些微处理器,程序甚至会崩溃(PS2就是这类“零容忍”的例子)。 各数据类型有不同的对齐需求。作为一个良好的经验法则,数据类型应该需要其字节大小的对齐。比如,32位值通常需要4字节对齐,16位通常需要2字节对齐,8位可以在任何位对齐)。在支持SIMD矢量数学的CPU中,每个SIMD寄存器喊32个4字节浮点数,共128位(16字节)。包含4个浮点数的SIMD矢量通常需要16字节对齐。 在class或者struct中,当把较小的数据类型(如8位的bool)放置于较大类型的(如32位的float)之间时,编译器便会启用填充(pad),以保证所有的成员都是正常对齐的。 注意:上面的InefficientPacking 结构体预期的是18字节,但是实际上是20字节。这是由于在末端加进了两个字节的填充。编译器加上这种填充,使结构作为数组类型的时候仍能够维持正确的对齐。换而言之,**如果定义此结构的数组,并且其首个元素是对齐的,那么结构末端的填充保证了所有之后的数组元素都是正确对齐的**。 有些人的写法可能会在结构体上主动添加明确的填充:比如增加 U8 _pad[2];这个变量作为明确的填充;
六 C++的内存布局
在内存布局上,C++的类有别于C的结构体之处有两点——继承与函数。
当B类继承自A类,内存里B类的数据成员会紧接着A类数据成员之后。每个新的派生类都会简单的把其数据成员附加到末端,即使类之间可能因为对齐而加入填充。(多重继承都比较混乱,例如会在派生类的内存布局中包含同一基类的多个版本。对于游戏程序员来说通常会避免使用多重继承)
注:在C++中,struct和Class的区别只在于预设的成员可见性,struct的预设成员可见性为public,而class则为private。C++中的struct一样可以有继承和虚函数。
多态的核心——虚函数表。
当类含有或者继承了一个或者多个虚函数时,那么就会在类的布局里添加4字节(若目标硬件采用64位则是8字节),通常会加在类的布局的最前端。这4字节或者8个字节称为虚表指针,因为此4字节代表一个指针,指向名为虚函数表的数据结构。
在每个类的虚函数表里,包含该类的声明或者继承而来的所有虚函数指针。每个(含有虚函数的)具体类都具有一个虚函数表,并且这些类的实例都会有虚表指针指向该虚函数的表。
多态的核心——虚函数表。因为它使程序员在编写代码时无须考虑代码是和哪个具体类进行沟通的。
比例:Shape为基类,Circle,Rectangle以及Triangle为派生类。假设Shape定义了名为Draw()的虚函数,而所有的派生类都重载了此函数,提供了个别的实现。包括Circle::Draw(),Rectangle::Draw()以及Triangle::Draw(),任何继承自Shape的类,其虚函数表都有Draw()函数的条目,但条目会指向具体类的函数实现。Circle类的虚函数表包含指向Rectangle::Draw()的指针,而Triangle类的虚函数表则包含指向Triangle::Draw()的指针。假如有一个指向Shape的指针(Shape *pShape),要调用其虚函数Draw(),代码可先对其虚表指针解引用,取得虚函数表,再从表中找到Draw()的条目,就可以调用。若pShape只想一个Circle的类的实例,结果就是调用Circle::Draw()。以此类推。更多相关内容 -
C++ 内存对象布局
2018-11-08 17:43:47涉及各种情况下C++对象的sizeof大小,包括单一类对象,继承,重复继承 多继承 单一虚继承 等各种情况下的对象大小。对C++对象内存布局有清楚了解。 -
C++内存布局
2022-04-20 19:22:34C++内存布局中,堆和栈在内存中的关系是怎样的,堆相对栈总是处于低地址区吗?windows linux上布局一样吗?C++内存布局中,堆和栈在内存中的关系是怎样的,堆相对栈总是处于低地址区吗?windows linux上布局一样吗?
环境说明
win10 64位, vc6, vs2019, centos7.9 gcc9.3 linux, mint20.2 gcc9.3
遇到问题先搜索一番
基本上都是一样的图示,随便找了两个放在下面。
看到这里似乎脑袋里灵光一闪,内存布局一定是这样的:
- 堆相对于栈总是处于低地址区,
- 堆上高地址增长
- 栈向低地址增长。
实际表现是这样吗
由于平时使用windows,先在windows上验证一下吧。
- 经典的VC6
啥情况?怎么栈的地址好像比堆的要小呢?一定是VC6这老古董太老了,有bug。
- VS2019 x86 在新版本上总应该没问题了吧
奇怪了,依旧与网上大神们的图对不上,栈的地址就是比堆要小
- VS2019 x64 哦,我知道了,现在是64位的天下,谁还看x86的呀,再换x64试试
这下总算与网上大神们的图对上了
- centos7.9,忽然想到大神们是不是 都不用windows,再看看linux吧
完美匹配,看来大神们一定是用的linux开发学习的
- linuxmint 20.2(基于ubuntu) linux debian系 redhat系都挺多的,再试试debian系(手头只有虚拟机版本的,凑和用吧)
啥?又与大神们的图对不上了?已经开始晕了
思考一下
win上x86 与x64 不同,linux 同为x64 centos 与 linuxmint(ubuntu) 不同。为什么呢?
其实
这是由于内存是由操作系统管理的,具体到内存布局上不同系统会有不同的分配方式,堆和栈及其它代码段数据段等的相对关系不是全部统一的
,需要我们区别对待总结
相信通过这个学习过程,读者已经对内存布局有了更深的理解,再来回顾一下
这个图展示了一种典型
的内存布局,读者千万不可认为所有系统上均是如此
那么对我们之前内存布局的理解应修改为
- 堆相对于栈
不总是
处于低地址区, - 堆上高地址增长
- 栈向低地址增长。
-
浅析C++内存布局
2022-05-07 16:44:45简单总结下C++变量在内存中的布局和可执行文件相关的知识。暂未涉及虚函数,虚函数表,类的继承和多态等C++对象的内存模型。对象的内存模型可能够写一本书了。推荐经典书籍《深度探索c++对象模型》C++程序在内存中的布局是怎样的?总结下C++内存布局的相关知识。
概述
简单总结下C++变量在内存中的布局和可执行文件相关的知识。暂未涉及虚函数,虚函数表,类的继承和多态等C++对象的内存模型。对象的内存模型推荐经典书籍《 深度探索C++对象模型》,豆瓣评分9.1。
开篇先回顾下Linux运行时存储器映像:
以下示例可以调试观测内存地址:
#include <iostream> int gdata1 = 1; int gdata2 = 0; int gdata3; static int gdata4 = 4; static int gdata5 = 0; static int gdata6; int main() { int a = 11; int b = 0; int c; static int d = 12; static int e = 0; static int f; const char *p = "hello world"; return 0; }
使用size命令和objdump来查看目标文件的结构和内容:
使用 objdump -s -d 查看更详细的内容:
使用
nm
(names)查看符号表 :若是在linux下,上述指令都有效。还可使用readelf 可显示一个或者多个elf格式的目标文件的信息 。
readelf是Linux下的分析ELF文件的命令,这个命令在分析ELF文件格式时非常有用。常见的文件如在Linux上的可执行文件,动态库(*.so)或者静态库(*.a) 等包含ELF格式的文件。
什么是ELF文件?
ELF(Executable and Linkable Format)是Unix及类Unix系统下可执行文件、共享库等二进制文件标准格式。
系统里的目标文件是按照特定的目标文件格式来组织的,各个系统的目标文件格式都不相同。
从贝尔实验室诞生的第一个Unix系统使用的是a.out格式(直到今天,直到文件仍然称为a.out文件)。Windows使用可移植植入(PortableExecutable,PE)格式。MacOS- X使用Mach-O格式。现代x86-64Linux和Unix系统使用可调可链接格式(ELF)。
ELF格式的文件在Linux系统下有.axf,.bin,.elf,.o,.prx,.puff,.ko,.mod和.so等等。
.text(代码段)
.text段存放程序代码,运行前就已经确定(编译时确定),通常为只读。
在window平台上,可执行程序为xxx.exe。它产生两种东西:指令和数据。.exe程序存放在磁盘中,执行时被加载到内存中,不是物理内存,而是虚拟内存空间,.text中存放指令。
.rodata(只读数据段)
rodata段存储常量数据,比如程序中定义为const的全局变量,#define定义的常量,以及诸如“Hello World”的字符串常量。(注意有些立即数与指令编译在一起,是放在text段中的)。
const修饰的全局变量在常量区。const修饰的局部变量只是为了防止修改,没有放入常量区。
编译器会去掉重复的字符串常量,程序的每个字符串常量只有一份。
有些系统中rodata段是多个进程共享的,目的是为了提高空间利用率。
如在main中的 const char *p = "hello world"; 即存放在.rodata中。在vs2017中,并不能将常量字符串定义为char *p类型,否则会编译失败;
.data
data存储已经初始化的全局变量,属于静态内存分配。(注意:初始化为0的全局变量还是被保存在BSS段),static声明的变量也存储在数据段。
.bss
bss段存储没有初值的全局变量或默认为0的全局变量,属于静态内存分配。 bss段内容会被全部设为0。
stack
stack段存储参数变量和局部变量,由系统进行申请和释放,属于静态内存分配。
stack的特点是先进先出,可用于保存/恢复调用现场。
heap
heap段是程序运行过程中被动态分配的内存段,由用户申请和释放(例如malloc和free)。
申请时至少分配虚存,当真正存储数据时才分配物理内存;释放时也不是立即释放物理内存,而是可能被重复利用。
总结
1、执行文件中包含了text、rodata、data段的内容,不包含bss段内容(一堆0放入执行文件没有意义)
2、堆和栈的内存增长方向是相反的:栈是从高地址向低地址生长,堆是从低地址向高地址生长。
3、局部变量存储在stack中,编写函数时要注意如果该函数被递归调用很多次,可能会引起stack overflow的问题。
C++程序的内存格局通常分为四大区:全局数据区(静态区,常量区),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区)。全局数据区存放全局变量,静态数据和常量。所有类和函数代码存放在代码区。为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区。余下的空间被称为堆区(在栈与堆之间有部分动态分配的姑且称之为共享区,叫法可能不一样)。
推荐书籍《程序员的自我修养》
该书主要介绍系统软件的运行机制和原理,涉及在Windows和Linux两个系统平台上,一个应用程序在编译、链接和运行时刻所发生的各种事项。
包括:代码指令是如何保存的,库文件如何与应用程序代码静态链接,应用程序如何被装载到内存中并开始运行,动态链接如何实现,C/C++运行库的工作原理,以及操作系统提供的系统服务是如何被调用的。
类对象所占的内存空间
一个类的实例化对象所占空间的大小? 注意不要说类的大小,是类的对象的大小。 首先,类的大小是什么?确切的说,类只是一个类型的定义,它是没有大小可言的,用sizeof运算符对一个类型名操作,得到的是具有该类型实体的大小:
#include <iostream> class Test { }; int main() { Test test; std::cout << sizeof(test) << std::endl; //1 return 0; }
- 打开VS2019开发者命令行工具(每个版本都有)
- 切换到源文件所在路径
- cl /dl reportSingleClassLayout类名 "源文件名.cpp"
可以看到一个空类对象的大小1。
一个空类对象的大小是1,为什么不是0?
类A明明是空类,它的大小应该为0,为什么编译器输出的结果为1呢?这就是实例化的原因(空类同样被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存中得到了独一无二的地址,所以obj的大小是1。
添加虚函数之后,类的变化
当类中添加了虚函数时,类的大小变为4,同时编译器给类中添加了虚函数表指针
vfptr
这个指针指向了vftable,
vftable
这张表里面存放的本类所有的虚函数入口地址。结论
1.当类中添加了虚函数时,类的大小变为4,同时编译器给类中添加了虚函数表指针vfptr 这个指针指向了vftable,vftable 这张表里面存放的本类所有的虚函数入口地址。
2.当发生继承时,子类会将父类的虚函数表指针继承下来,指向父类的的虚函数表,在子类调用构造函数后,编译器会将该指针指向自己的虚函数表。
3.当子类重写了父类的虚函数时,在虚函数表中会用子类的函数地址去覆盖父类对应的虚函数地址。
4.程序运行时,通过父类指针或者引用 调用函数时,编译器会先找到该对象中的虚函数指针,根据指针找到虚函数表,在虚函数表中找到对应的函数入口 地址进行调用。二进制可执行文件的执行流程
可执行文件生成过程
- 预处理:进行头文件和宏定义的替换
- 编译:由编译器把高级语言代码编译为汇编代码
- 汇编:由汇编器把汇编代码翻译成二进制代码,也即是.o文件
- 连接:由连接器把多个.o文件连接成可执行文件;可分为编译时链接,加载时链接(程序被加载到内存中执行时),运行时链接(由应用程序来执行时)。
加载可执行目标文件
ELF头标书文件的整体格式还包含程序的入口点(程序需要运行时执行的第一条指令的地址)。可执行文件的连续片(chunk)被映射到连续的内存段。
当在shell中输入./programName时,shell解析到/判断不是内置命令(如果是内置命令时会搜索/usr /usr/lib ...)而是一个可执行文件,调用常驻内存的加载器(通过execve调用加载器)的操作系统代码来调用他。将可执行程序的代码和数据从磁盘复制到内存,在程序头部表的引导下加载器将可执行文件的片(chunk)复制到代码段和数据段,跳转到程序的第一条指令或入口点来运行。
linux的每个程序都运行在一个进程的上下文中,有自己的虚拟地址空间。当一个shell运行时,父进程shell生成一个子进程,他是父进程的一个复制。子进程通过execve系统调用调用加载器,加载器删除现有的虚拟内存段,创建新的代码段数据段堆栈,新堆栈被初始化为0,通过将虚拟地址空间的页映射到可执行文件的页面大小chunk,新的代码段和数据段被初始化为可执行文件的内容,最后跳转到_start,最终调用程序的main函数,除了头部的一些信息,加载过程没有任何数据从磁盘复制到内存,知道CPU引用的第一个虚拟页时才被复制。利用页面调度算法将他从磁盘复制到内存。
linux系统从开机到启动,执行流程从代码层面看大致经历:
设备上电后执行一段bootloader的汇编阶段。汇编第一阶段的代码主要可以分为以下部分:
设置异常向量表
设置特权管理模式
初始化PLL、DDR、MUX…
关MMU,关CACHE
判断代码在RAM还是FLASH,将FLASH代码复制至RAM中
设置堆栈、清空bss段
跳转至C语言处进入第二阶段,第二段也属于bootloader的功能,完成一些硬件资源初始化。最后才是操作系统内核的引导启动。
处理器流水线概述
1.经典的五级流水线
一条指令的流程:
- 取指
- 译码
- 执行
- 访存
- 写回
前一条指令完成了“取指”进入“译码”阶段后,下一条指令马上就可以进入“取指”阶段。
RISC-V架构
国外的指令集架构本质上是一种授权付费的,很难实现国产自主。如果国家自己定义一套指令集架构没有太大意义,因为处理器架构必须是全球范围的一个通用架构,必须获得生态支持。现在有了RISC-V可以很好的解决这一问题,“RISC-V可能真正能成为国产的自主的指令集架构。”
最近中美贸易战激战正酣,中兴被美国禁运的消息传来,一时间国内舆论大哗,说了多年的芯片“自主研发”,这么多的国产芯片上市公司,似乎到了关键时刻就被人卡脖子了。
胡振波表示,在产业界和芯片界,主流的大公司都在用RISC-V架构来做产品。西部数据和AMD都明确的说在用RISC-V做芯片,CEVA在用RISC-V做控制芯片核,谷歌也在用RISC-V做一些新的芯片项目。还有美光。除了这些大公司,还有很多业界大公司已经非常普遍的在用RISC-V来做内核。
最后,胡振波介绍了自己研发的首颗采用RISC-V架构的MCU蜂鸟E200。据介绍,这款全球最小的32位RISC-V核心可以实现超低功耗,对标的是ARM的M0 的MCU内核,安全可控,无需向国外公司支付版税。胡振波表示蜂鸟E200还有205、205FD等一系列产品线。
“这就是开放的指令集的魅力,接下来会冒出更多无法想象的指令集版本。”胡振波最后表示。
引用
【C++学习笔记】03-图说C++对象模型:对象内存布局详解_你行你上天的博客-CSDN博客
C++类对象的内存布局_一叶知秋dong的博客-CSDN博客_c++对象内存结构
https://www.jb51.net/article/225140.htm
C++类对象在内存中的布局_子木呀的博客-CSDN博客_c++对象内存布局
[RISC-V学习]《手把手教你设计CPU——RISC-V处理器》笔记(一)2021/11/10_qq_41876038的博客-CSDN博客_手把手教你设计cpu
从零开始写RISC-V处理器 | liangkangnan的博客
「OS」浅析从代码到可执行文件的过程 – better averyboy
Linux启动ELF可执行文件的过程_第二月的技术博客_51CTO博客
RISC-V架构能否让国产IC真正“自主研发”? - 芯智讯
【UBoot】uboot启动过程分析_Evan_ZGYF丶的博客-CSDN博客_uboot启动流程
【开发工具】【readelf】查看ELF格式文件工具(readelf)的使用_Evan_ZGYF丶的博客-CSDN博客_readelf 查询字符串
-
C++ 内存布局
2021-05-18 18:32:25 -
C++ 内存布局:深入理解C++内存布局
2016-06-24 10:36:28可以看到m_Point2D的内存布局和m_Point的内存布局很类似。一个虚函数表指针,然后三个成员变量。虚函数表中的内容和m_Point中的一摸一样。这是因为CPoint2D 是从CPoint继承过来的。 2.3 基类含有虚函数,使用... -
C++程序内存布局
2022-01-24 11:25:54介绍C++程序内存布局的相关知识 -
C++内存布局分为几个区域,各自具备什么特点?
2019-05-14 21:56:46一、程序的内存布局 二、程序示例 三、栈(stack)与堆(heap)的比较 一、程序的内存布局 在C++中,程序在内存中的存储被分为五个区: 1、栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量... -
c++类内存布局
2022-04-01 10:42:11可知内存布局如下: 父类成员变量 子类成员变量 二、父类含有虚函数 可见内存布局如下: 虚函数表指针 父类成员变量 子类成员变量 三、虚基类 虚继承的情况下,对象a1的大小为12字节,是因为前... -
如何查看C++内存布局(VS2012)
2017-07-13 10:53:26学习C++对象模型时,学习虚函数时,需要弄懂C++内存布局,通过编程查看C++内存布局有助于我们理解这些概念。使用Visual studio的两个命令参数即可查看:/d1 reportAllClassLayout 或者 reportSingleClassLayoutXXX,... -
C++内存布局:深入立即C++内存布局下
2016-07-13 20:26:04我们继续完成内存布局的讲解。 这次需要讲解的内容如下: 基类不含虚函数,使用虚继承,派生类中含有虚函数 基类含有虚函数,使用虚继承,派生类中不含虚函数 基类含有虚函数,使用虚继承,派生类中含有虚函数... -
C++内存布局和内存分配
2021-12-19 18:54:58看了很多,收集若干篇: 内存布局1 内存布局2 内存分配 -
C++ 内存布局查看
2019-09-24 09:42:24cl /d1 reportSingleClassLayout PVertex main.cpp cl[source.cpp]/d1reportSingleClassLayout[classname] 内存布局查看 -
C++程序的内存布局
2020-06-28 23:05:151.c++内存布局 对于一个C/C++程序员来说,搞清楚一个C/C++程序在计算机内存中的布局尤为重要。了解了程序在计算机内存中的布局,对程序员解决段错误,内存泄漏等问题也有一定的帮助,也能更加深刻的理解一个程序。 ... -
C++内存布局详解
2018-01-28 13:04:07C++中类的继承类型,以及对应的类实例内存布局如下图: 1. 单继承 继承情况如下: 对应的对象内存布局: 2. 一般多继承(非菱形) 继承情况如下: 对应的对象内存布局: 3. 一般多继承(菱形) 类... -
谈一谈c/c++程序的内存布局
2021-12-08 21:51:02浅谈c程序的内存布局,怎么查看程序的内存布局 -
c++内存布局最完整
2017-03-31 22:29:21大家都应该知道C++的精髓是虚函数吧? 虚函数带来的好处就是: 可以定义一个基类的指针, 其指向一个继承类, 当通过基类的指针去调用函数时, 可以在运行时决定该调用基类的函数还是继承类的函数. 虚函数是实现多态(动态... -
【转】C++内存布局(透彻易懂)
2018-08-04 14:01:41C++内存布局(上) https://blog.csdn.net/u012658346/article/details/50775742 C++内存布局(下) https://blog.csdn.net/u012658346/article/details/50775742 -
C++对象内存布局
2013-05-01 14:47:23介绍C++对象在内存中是怎样分布的,有助于深层学习C++。 -
C和C++的内存布局
2019-04-11 17:21:491.1 内存分布图 Stack段:局部变量存放区域。 heap段:用户动态分配内存区域。 bss段:存放未初始化的全局或静态变量内存区域。(Block Started by Symbol) 数据段:通常指存放已初始化的全局变量的内存区域... -
C++类对象在内存中的布局
2021-09-26 13:28:08二、C++ 类对象的内存布局 2.1 只有数据成员的对象 2.2 没有虚函数的对象 2.3 拥有仅一个虚函数的类对象 2.4 拥有多个虚函数的类对象 三、继承关系中的C++类对象内存分布 3.1 存在继承关系且本身不存在虚函数... -
C++类对象的内存布局
2022-01-06 17:38:04C++的对象都会进行内存对齐,所谓内存对齐,指的是对象的地址和大小都会对齐到n的倍数上。比如按照4对齐,那么对象的地址会是4的倍数,对象的大小也是4的倍数。究其原因是,机器在内存对齐的地址上访问数据更快,... -
C++ 内存布局:内存布局基础
2016-06-24 00:13:43C++中的内存分布,基础篇 -
C/C++ 内存布局
2014-03-13 13:59:42为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内存布局对调试程序非常有帮助,可以知道程序执行时,到底做了什么,有助于写出干净的代码。 本文的主要内容如下: 源文件转换为可执行文件... -
C++对象的内存布局
2021-03-03 03:43:31不想,这篇文章成为了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更深层次的讨论。当然,我之前的文章还有很多方面没有涉及,从我个人感觉下来,在谈论虚函数表里,至少有以下这些内容没有涉及:1)... -
C++ 内存布局占用情况
2015-08-20 10:35:27C++ 中内存分布比较复杂,主要分为堆、栈、.data区、代码段,在平时会涉及到一个类的内存大小,总结如下: 1. 普通的成员函数不会占内存,但是普通的成员变量占内存。 2. Static的变量和函数都不会占内存。... -
【转载】C/C++内存布局策略详解
2019-08-28 10:41:12详解C/C++的内存布局,以及C++对象内存布局,以及C和C++内存布局之间有什么区别。 C程序内存布局 Linux C程序内存布局 主要分为以下几部分组成: 代码段 初始化数据段(数据段) 未初始化数据段(BSS段...