
C++知识
文章平均质量分 78
九五一
这个作者很懒,什么都没留下…
展开
专栏收录文章
- 默认排序
- 最新发布
- 最早发布
- 最多阅读
- 最少阅读
-
c++为什么malloc时需要指定size,对应的free时不需要指定size
如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,原创 2024-04-13 00:38:07 · 519 阅读 · 0 评论 -
C++ 让类只在堆或栈上分配
当对象使用完后,编译器会调用析构函数来释放栈对象所占的空间。为了统一,可以将构造函数设为protected,然后提供一个public的static函数来完成构造,这样不使用new,而是使用一个函数来构造,使用一个函数来析构。只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。在构造函数私有之后,无法在类外部调用构造函数来构造类对象,只能使用new运算符来建立对象。类的使用很不方便,使用new建立对象,却使用destory函数释放对象,而不是使用delete。原创 2024-03-30 00:38:23 · 979 阅读 · 0 评论 -
C++ 如何让 new 操作符只构造,不申请内存
我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。在很多情况下,placement new的使用方法和其他普通的new有所不同。采用placement new和new的方式创建和删除对象一万次,统计时间,单位是us。Placement new的返回值是这个被构造对象的地址(比如括号中的传递参数)。结论:在频繁构造和析构对象的场景中,placement new对性能有7倍的提升。原创 2024-03-29 23:46:21 · 986 阅读 · 0 评论 -
C++ 如何让 new 操作符只构造,不申请内存
我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。在很多情况下,placement new的使用方法和其他普通的new有所不同。采用placement new和new的方式创建和删除对象一万次,统计时间,单位是us。Placement new的返回值是这个被构造对象的地址(比如括号中的传递参数)。结论:在频繁构造和析构对象的场景中,placement new对性能有7倍的提升。原创 2024-03-28 11:33:24 · 513 阅读 · 0 评论 -
C++ 内存泄漏-原因、避免、定位
大家好!作为C/C++开发人员,内存泄漏是最容易遇到的问题之一,这是由C/C++语言的特性引起的。C/C++语言与其他语言不同,需要开发者去申请和释放内存,即需要开发者去管理内存,如果内存使用不当,就容易造成或者。今天,借助此文,分析下项目中经常遇到的导致内存泄漏的原因,以及如何避免和定位内存泄漏。原创 2024-03-27 20:30:07 · 2695 阅读 · 0 评论 -
6. C++ 内存分布
例:class Testpublic:Test(): _data(0)~Test()private:int _data;// 申请单个Test类型的空间free(p1);// 申请10个Test类型的空间free(p2);// 申请单个Test类型的对象delete p1;// 申请10个Test类型的对象注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会new的原理。原创 2024-03-26 15:24:53 · 1307 阅读 · 0 评论 -
5. C++ 局部静态变量在什么时候分配内存和初始化?
C++引入了对象,这给全局变量的管理带领新的麻烦。C++的对象必须有构造函数生成,并最终执行析构操作。由于构造和析构并非分配内存那么简单,可以说相当复杂,因此何时执行全局或静态对象(C++)的构造和析构呢?由于内置变量无须资源释放操作,仅需要回收内存空间,因此程序结束后全局内存空间被一起回收,不存在变量依赖问题,没有任何代码会再被执行!,在程序结束之后(如调用exit,main),按FILO顺序调用相应的析构操作!对于C语言的全局和静态变量,不管是否被初始化,其内存空间都是全局的;原创 2024-03-25 13:56:25 · 1103 阅读 · 0 评论 -
4. C++ 堆栈工作机制
我们经常会讨论这样的问题:什么时候数据存储在堆栈(Stack)中,什么时候数据存储在堆(Heap)中。我们知道,局部变量是存储在堆栈中的;debug时,查看堆栈可以知道函数的调用顺序;函数调用时传递参数,事实上是把参数压入堆栈,听起来,堆栈象一个大杂烩。那么,堆栈(Stack)到底是如何工作的呢?本文将详解C/C++堆栈的工作机制。阅读时请注意以下几点:1)本文讨论的编译环境是 Visual C/C++,由于高级语言的堆栈工作机制大致相同,因此对其他编译环境或高级语言如C#也有意义。原创 2024-03-23 12:00:00 · 1111 阅读 · 0 评论 -
3. C++ 常见的段错误及对策
也就是说,在程序中malloc 的使用次数一定要和free 相等,否则必有错误。释放完块内存之后,没有把指针置NULL,这个指针就成为了“野指针”,也有书叫“悬垂指针”。这是很危险的,而且也是经常出错的地方。内存分配成功,且已经初始化,但是操作越过了内存的边界。第一种:就是上面所说的,free(p)之后,继续通过p 指针来访问内存。所以,for 循环的循环变量一定要使用半开半闭的区间,而且如果不是特殊情况,循环变量尽量从0 开始。定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。原创 2024-03-22 12:00:00 · 1031 阅读 · 0 评论 -
2. C++ 内存对齐
详见实例4》)原创 2024-03-21 17:00:00 · 1000 阅读 · 0 评论 -
1. C++ 类内存布局
GCC对齐系数可通过#pragma pack(n)来指定(32系统默认为4,64位系统默认为8)给定对齐系数和结构体中最长数据类型中长度较小的值,称为有效对齐值。结构体的一个成员的偏移量为0,之后的每个成员相对于结构体首部的偏移量都是成员大小与有效对齐值中较大的那个的整数倍,如有需要编译器会在成员之间加上填充字节。原创 2024-03-21 11:15:00 · 448 阅读 · 0 评论 -
深度探索C++对象模型之美
从概念来上来讲,每一个没有定义构造函数的类都会由编译器来合成一个默认构造函数,以使得可以定义一个该类的对象,但是默认构造函数是否真的会被合成,将视是否有需要而定。C++ standard 将合成的默认构造函数分为 trivial(不重要) 和 notrivial(重要) 两种,前文所述的四种情况对应于notrivial默认构造函数,其它情况都属于trivial。对于一个trivial默认构造函数,编译器的态度是,既然它全无用处,干脆就不合成它。原创 2024-03-20 11:00:00 · 3108 阅读 · 0 评论 -
C++面向对象
在类内部添加一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包含了所有的虚拟函数的入口地址,每个类的虚拟函数表都不一样,在运行阶段可以循环脉络找到自己的函数入口。在继承结构中,基类指针(引用)指向派生类对象,通过指针(引用)调用同名覆盖方法(虚函数),基类指针指向哪个派生类对象,就会调用哪个派生类对象的同名覆盖方法,称为多态。基类的指针(引用)指向堆上new出来的派生来对象的时候,delete pb(基类指针),它调用析构函数的时候,必须发生动态绑定,否则会导致派生类的析构函数无法调用。原创 2024-03-19 10:45:00 · 993 阅读 · 0 评论 -
15. C++虚函数表原理浅析
虚函数表不一定是存在最开头,但是目前各个编译器大多是这样设置的因为在编译一个类时,编译器需要确定该类的虚函数表的大小。而如果一个成员函数是模板函数,并且该模板函数为虚函数,那么编译器就无法确定虚函数表的大小,因为该模板函数可以被实例化出很多不同的版本,每个版本都可以作为虚函数表的一个条目,而编译器需要知道这些条目的数量,才能确定虚函数表的大小。由于模板函数可以在不同的源文件中进行实例化,因此编译器必须查找所有的源文件,才能确定虚函数表的大小,这对于多文件的项目来说是不可行的。原创 2024-03-18 18:00:00 · 944 阅读 · 0 评论 -
14. C++类中this指针的理解
而在很多个对象中间,我们为了证明某个成员是自己的成员,而不是其他对象的成员,我们同样需要给这些成员取上名字。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。原创 2024-03-18 10:45:00 · 931 阅读 · 0 评论 -
13. C++类的简单理解
可以理解成不能。子类会继承父类所有的函数,包括构造函数**,但是子类的构造函数会把父类的构造函数覆盖了,所以看起来就是没有继承。原创 2024-03-17 14:10:36 · 1030 阅读 · 0 评论 -
11. C++空基类优化
我们都知道,在C++中,不存在大小是零的类。即便是空类,也要占据一个字节,否则无法比较两个空类对象是否是同一个对象(在C/C++中,默认使用地址来判断两个变量是否是同一个)。即便这两个对象有相同的基类,但毕竟是不同的对象,如果指向了同一个地址,那么就无法通过地址来区分对象。其中,1和3是硬性限制,无法突破,但2问题是可以解决的。卖个关子,欲知详情,请见下期。如果仍然像之前说的那种内存等效模型,那么编译器会每个空基类对象都分配内存,因此即便。的大小是2,符合本期开篇提出的继承体系中的内存模型,可等效为。原创 2024-03-17 12:07:35 · 1022 阅读 · 0 评论 -
10. C++关键字virtual用法
派生类会拥有基类定义的函数,但是对于某些函数,有时候希望每个派生类各自定义适合于自己版本的函数,于是基类就将此函数定义为虚函数,让派生类各自实现自己功能版本的函数,也可以不实现;这就是virtual关键字的主要作用。「构造函数不能是虚函数」。在构造函数中调用虚函数,实际运行的是父类的相应函数。因为自己还没有构造好,多态还无法正常使用。「将一个函数定义为纯虚函数。实际上是将这个类定义为抽象类,不能实例化对象」。「纯虚函数通常未定义体,但也可以能够拥有」, 甚至能够显示的调用。原创 2024-03-16 18:30:00 · 1817 阅读 · 0 评论 -
9. C++对象模型图解
上面的类声明中,三个数据成员直接内含在每一个Point3d对象中,而成员函数虽然在类中声明,却不出现在类对象(object)之中,这些函数(non-inline)属于类而不属于类对象,只会为类产生唯一的函数实例。多态,简单来说,是指在继承层次中,父类的指针可以具有多种形态—当它指向某个子类对象时,通过它能够调用到子类的函数,而非父类的函数。总结一下:不考虑虚函数与虚继承,当数据都在同一个访问标识符下,C++的类与C语言的结构体在对象大小和内存布局上是一致的,C++的封装并没有带来空间时间上的影响。原创 2024-03-16 11:30:00 · 820 阅读 · 0 评论 -
8. C++对象模型
非虚继承时,显然D会继承两次A,内部就会存储两份A的数据浪费空间,而且还有二义性,D调用A的方法时,由于有两个A,究竟时调用哪个A的方法呢,编译器也不知道,就会报错,所以有了虚继承,解决了空间浪费以及二义性问题。前面的代码输出中我们可以看到虚函数表中有两个析构函数,一个标志为deleting,一个标志为complete,因为对象有两种构造方式,栈构造和堆构造,所以对应的实现上,对象也有两种析构方式,其中堆上对象的析构和栈上对象的析构不同之处在于,栈内存的析构不需要执行 delete 函数,会自动被回收。原创 2024-03-15 18:00:00 · 1546 阅读 · 0 评论 -
7. C++编辑器合成默认构造函数的问题
这时编译器会在成员类的 default constructor被调用的时候合成的构造函数,比如声明子类对象时,如果 成员类的 default constructor没有被 调用时是不会合成的构造函数,相信学过面向对象的都知道吧,声明类只是声明了一种自定义类型,其实并没有被开辟内存,只有使用时才被开辟内存空间的。虚继承也会在子类对象中被合成一个指向虚基类的指针,因此也要被初始化,所以必须要构造函数,虚基类或者虚继承保证子类对象中只有一份虚基类的对象,特别是对于多重继承更是很重要。原创 2024-03-15 12:00:00 · 766 阅读 · 0 评论 -
6. C++ 钻石继承与虚继承
仿佛一个钻石,因此这种继承关系在C++中通常被称为。原创 2024-03-14 14:18:08 · 1187 阅读 · 1 评论 -
5. C++ 运行时类型识别RTTI
作为C++开发人员,基本都知道dynamic_cast是C++中几个常用的类型转换符之一,其通过类型信息(typeinfo)进行相对安全的类型转换,在转换时,会检查转换的src对象是否真的可以转换成dst类型。这块逻辑比较绕,其实可以将关系理解为图上的一条条连接线,节点理解为类型信息,dynamic_cast的过程,就是判断有没有从src到dst有没有路径的过程。否则,在编译期就能获取其具体类型,甚至在某些情况下,可以对typeid()的结果直接进行替换。然后根据返回值的内容来判断结果,并进行相应的操作。原创 2024-03-14 10:59:50 · 538 阅读 · 0 评论 -
4. C++ 类的大小
此种情况下,Derived1类一开始仍然是虚函数表指针,只是在Derived1类中被重写的虚函数g()在对应的虚函数表项的Base::g()已经被替换为Derived1::g(),新添加的虚函数virtual h()位于虚函数表项的后面,紧跟着基类中最后声明的虚函数表项后,接下来仍然是基类的成员变量,紧接着是继承类的成员变量。空类即什么都没有的类,按上面的说法,照理说大小应该是0,但是,空类的大小为1,因为空类可以实例化,实例化必然在内存中占有一个位置,因此,编译器为其优化为一个字节大小。原创 2024-03-12 13:01:30 · 1046 阅读 · 0 评论 -
3. C++ 继承与派生详解
C++中的多重继承可能更灵活, 并且支持三种派生方式。原创 2024-03-08 22:31:05 · 1126 阅读 · 0 评论 -
2. C++ 对象内存布局
这是因为与单继承不同,在多继承中,class Base1和class Base2相互独立,它们的虚函数没有顺序关系,即f1和f2有着相同对虚表起始位置的偏移量,所以不可以按照偏移量的顺序排布;在前面的内容中,我们多次提到了top offset,在上节Derived的虚函数表中,有两个top offset,其值分别为0和-16,那么这个offset起什么作用呢?在多继承中,当最左边的类中没有虚函数时候,编译器会将第一个有虚函数的基类移到对象的开头,这样对象的开头总是有。原创 2024-03-08 12:51:26 · 815 阅读 · 0 评论 -
1. C++ 编译期多态与运行期多态
对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。今日的C++不再是个单纯的“带类的C”语言,它已经发展成为一个多种次语言所组成的语言集合,其中泛型编程与基于它的STL是C++发展中最为出彩的那部分。对于anim来说,必须支持哪一种接口,要由模板参数执行于anim身上的操作来决定,在上面这个例子中,T必须支持shout()操作,那么shout就是T的一个隐式接口。,这就是编译器多态。原创 2024-03-07 20:27:23 · 957 阅读 · 0 评论 -
5. 链接和加载(linker and loader)
loader即加载器,它原本的功能很单一只是将可执行文件的段拷贝到编译确定的内存地址即可,但是有了动态链接库以后,部分的外部库引用符号在加载的时候才会得到解析,所以加载也要处理链接器的相同操作重定位。而绑定的根本目的就是方便对符号的引用,在符号值发生改变的时候,不需要去手工改动源代码中对符号引用的地方,而这种改动是由链接程序在重新生成执行文件时自动完成的。符号表就是一张字符符号和地址的对应表,符号表的作用就是一个助记符,用一个字符串来标示某些抽象的地址,它能标示的地址有代码地址和数据地址,代码地址包括。原创 2024-03-06 17:34:16 · 1378 阅读 · 0 评论 -
4. 深入窥探动态链接
在动态链接方式实现以前,普遍采用静态链接的方式来生成可执行文件。如果一个程序使用了外部的库函数,那么整个库都会被直接编译到可执行文件中。ELF 支持动态链接,这在处理共享库的时候就会非常高效。当一个程序被加载进内存时,动态链接器会把需要的共享库加载并绑定到该进程的地址空间中。随后在调用某个函数时,对该函数地址进行解析,以达到对该函数调用的目的。(1)简介: 过程连接表,在程序中以 .plt 节表示,该表处于代码段,每一个表项表示了一个与要重定位的函数相关的若干条指令,每个表项长度为 16 个字节,存储的是原创 2024-03-06 14:52:28 · 619 阅读 · 0 评论 -
3. 可执行文件的生成
编译器编译源代码后生成的文件叫做目标文件,目标文件从结构上讲,是已经编译后的可执行文件格式,只是还没经过链接的过程。原创 2024-03-05 18:41:51 · 1477 阅读 · 0 评论 -
2. 静态链接库和动态链接库
计算机中,有些文件专门用于存储可以重复使用的代码块,例如功能实用的函数或者类,我们通常将它们称为库文件,简称“库”(Library)。显然,实际开发中引入他人编写好的库文件可以省略某些功能的开发环节,提高项目的开发效率。但遗憾的是,类似 myMath.c 这种“开源”的库文件很难找到,多数程序员并不会直接分享源代码,他们更愿意分享库文件的二进制版本——链接库。原创 2024-03-05 17:50:37 · 827 阅读 · 0 评论 -
1. 动态链接的执行过程
并且不保存动态链接库的全路径,原创 2024-03-04 13:16:57 · 1110 阅读 · 0 评论 -
STL-萃取技术traits技术
设计适当的型别,是迭代器的责任。设计适当的迭代器,是容器的责任。原创 2024-03-04 19:45:00 · 826 阅读 · 0 评论 -
STL-内存的配置与释放
若malloc失败,会先搜寻其他链表的可用的内存块,添加到内存池,然后递归调用chunk_alloc函数来分配内存,若其他链表也无内存块可用,则只能调用第一级空间配置器,因为第一级空间配置器有malloc失败的出错处理函数,最终的希望只能寄托在那里了。数组大小为16,即维护了16个链表,链表的每个节点就是实际的内存块,相同链表上的内存块大小都相同,不同链表的内存块大小不同,从8一直到128。配置内存时,需要额外的部分空间存储内存块信息,所以配置大量的小内存块时,还会导致额外内存负担。原创 2024-03-03 19:27:17 · 1134 阅读 · 0 评论 -
STL-容器适配器详解
priority_queue 容器适配器为了保证每次从队头移除的都是当前优先级最高的元素,每当有新元素进入,它都会根据既定的排序规则找到优先级最高的元素,并将其移动到队列的队头;容器适配器的底层实现和模板 A、B 的关系是完全相同的,即通过封装某个序列式容器,并重新组合该容器中包含的成员函数,使其满足某些特定场景的需要。和 queue 一样,priority_queue 也没有迭代器,因此访问元素的唯一方式是遍历容器,通过不断移除访问过的元素,去访问下一个元素。关于如何自定义排序规则。原创 2024-03-02 19:40:25 · 1293 阅读 · 0 评论 -
vector原理及注意事项
先看一下的声明,截取头文件//两个模板参数,第一个是数据类型,第二个std::allocator是标准库中动态内存分配器,最终其实是调用了new运算符从源码的实现来看,其实vector是一个模板派生类,也就是说,它首先是一个模板类,这一点我们应该都猜得到,毕竟我们使用的时候都是使用形如这样的形式来进行声明一个vector对象的,其次它是一个派生类,它的基类是,所以我们首先来看一下这个基类的实现。原创 2024-03-02 19:38:54 · 1033 阅读 · 0 评论 -
标准库 string 源码分析(重要)
*实际上是使用堆来管理的,栈上的_M_local_buf退出舞台,让出位置给_S_local_capacity用例管理当前的string对象能管理的字符串的最大长度。**因此,可以称长度小于_S_local_capacity的字符串为“小串”,长度大于_S_local_capacity的字符串为“大串”。我们可以在debug模式下看一下str的结构,_M_p指向的内存地址为0x72fdf0,和(0)中相比较少了16个字节,根据栈与数组的增长方式,union的对外表现应该是_M_local_buf。原创 2024-03-01 20:15:58 · 1274 阅读 · 0 评论 -
你知道vector底层是如何实现的吗?
一个是_M_shrink_to_fit函数,可以使用这个函数将_M_finish和_M_end_of_storage之间的内存释放掉,正好使分配的内存大小满足vector中元素量。一个是reserve函数,可以使用这个函数控制整个vector容量的作用,避免重新分配内存,例如我们如果知道元素最多有80个,我们就可以使用v.reserve(80)来一次分配80个内存。但是值得注意的是这个函数不能用来缩减容量。原创 2024-03-01 12:19:57 · 996 阅读 · 0 评论 -
STL -萃取特性迭代器
容器(Container)空间配置器(allocator)算法(Algorithm)迭代器(Iterator)仿函数(Function object)适配器(Adaptor)原创 2024-02-29 19:06:37 · 709 阅读 · 0 评论 -
STL Iterator 迭代器全揭秘
Iterator 是 STL 的基础,STL 算法都是基于 iterator。本文基于 gcc-12.2 分析了迭代器实现。原创 2024-02-29 16:43:36 · 516 阅读 · 0 评论