- 博客(40)
- 资源 (1)
- 收藏
- 关注
原创 知其然,也知其所以然的c++/c面试(1)main()是梦开始的地方吗?
至此,熟悉的main函数终于现身了。gcc默认的编译行为链接了Scrt1.o这个目标文件(/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o)输出的内容包含默认的链接脚本(using internal linker script:),其中ENTRY(_start)指定了程序的入口函数。由此可见,如果采用gcc的默认编译行为,程序的入口必然是_start,_start必然会调用main函数,如果不提供main函数,编译必然报错。
2025-06-18 15:33:47
300
原创 知其然,也知其所以然的c++/c面试(2)一个简单c程序需要的小帮手们
假如库b的数据段和jump func_c这条指令的偏移量为500,地址表格的位置在数据段的偏移量为50,func_c是地址表格中的第5项,那么jump func_c可以转变成jump 500 + 50 + 5 * 8(地址长度在64位机器上是8字节),找到这个地址后,取出该地址的存储内容就可以获取func_c在本进程的实际地址了。一个编程语言要建立自己的生态圈就要提高语言的易用性,其中一个很重要的部分,是把常用的功能封装成函数,以库的形式提供给用户使用。久而久之,图书馆里书的放置位置就乱套了。
2025-06-18 14:57:41
1205
原创 从main()函数的执行发散开来
比如图书馆的某本书现在还剩多少本,最早归还的日期是多少等查询功能就可以直接向读者开放,读者在大厅的自动查询机上直接查询即可,没必要再去劳烦图书管理员饶一道圈圈。拿到食材烹饪的过程中,需要什么配菜,需要什么调料,都由大厨择机投放并严格控制用量,最后也能用脑输出一顿令人口口相传的家庭私房菜。张三去还书,李四去还书,王五也去还书。linux-vdso.so.1是内核镜像的一部分,内核把一部分功能直接暴露给普通用户,普通用户可以直接在用户空间执行指令,不用陷入内核空间获取特权后才能运行这段代码,效率提升明显。
2025-06-16 17:41:06
750
原创 基于protobuf + iceoryx实现共享内存上的零拷贝
2. 容器全部重写,不采用C++标准库的容器实现,因为标准库的容器实现代码不受控制,动态扩容的内存未必一定分配在指定的共享内存上(当然可以定制化内存分配策略,较复杂)。每一个对象在构造的时候,虚函数表的地址被写死了,调用虚函数的时候需要通过虚函数表找到虚函数的地址,但是这两个地址都属于发布者进程地址空间的地址,该地址在订阅者进程中可能无效。可以看出,数据量较小时,零拷贝相较于非零拷贝,数据的传输速度有10%-20%的提升,随着数据量的增加,零拷贝的优势越来越明显,4M数据的延时竟然有三倍的差距!
2025-06-05 16:15:39
1007
原创 protobuf arena实现概述
(1)(2)表明如果是第一个Block,大小为start_block_size(内存分配策略AllocationPolicy中的数据成员),之后如果继续分配Block,其大小为前一个大小的两倍,但是存在上限max_block_size(同样为内存分配策略AllocationPolicy中的数据成员)Arena负责建立和管理内存池,可以通过Arena对象申请内存池中的内存,在该内存上构造新的protobuf message对象。传入的Arena对象的指针存储在arena_or_elements_。
2025-06-03 17:49:22
875
原创 几个正整数常用的位运算操作
位运算在高性能计算、资源敏感型场景(如嵌入式系统)、特定算法(加密、压缩)中具有不可替代的优势。合理使用位运算可以显著提升代码效率和资源利用率。
2025-05-16 19:31:46
646
原创 使用objdump探索c++类成员的内存布局
对于第一个因素,言下之意,class object的静态数据成员,成员函数及静态成员函数并不属于class object大小范畴。那么,在程序的内存布局中,这些成员存放在什么地方呢?class Base基本涵盖了一个c++类的典型成员,包括普通数据成员、初始化过的静态数据成员、未初始化的静态数据成员、普通成员函数、静态成员函数、虚函数等。class Derived是class Base的子类,覆写了父类的虚函数,同时也定义了一个和父类同名的普通函数。一个ELF文件包含不同的段。
2025-05-07 16:17:32
903
原创 读一读冰羚代码(16)去Roudi中心化的策略探讨
共享内存的创建、socket的通信、心跳的检测等都需要Roudi进程来完成,可以说Roudi进程是整个基于共享内存的发布者和订阅者之间通信的基础。冰羚的demo程序中除了常规的三进程启动(Roudi进程、publisher进程、subscriber进程),基于共享内存进行进程间通信之外,还可以将其应用于单进程内多线程之间的通信,代码路径:iceoryx_examples/singleprocess/single_process.cpp。达到了和CyberRT类似的功能设计。那么,冰羚能做到这一点吗?
2025-04-24 16:38:19
352
原创 读一读冰羚代码(15)数据发布的通知机制之event
传入waitImpl函数的可调用对象使用信号量等待信号的产生,如果被成功唤醒返回true,如果信号量发生了错误,等待返回并不是真的被成功唤醒,则返回false。event条件不满足,即使队列中有数据,wait()也会等待信号量的到来,知道下一次被幻想,subscrbier才重新从队列中获取数据。state条件满足,即只要队列中有数据(状态一直持续),wait()会立即返回,subscriber从队列取出数据。event和state的分水岭在代码(1)处。对于event和state,(1)的条件均不满足。
2025-04-24 14:20:04
869
原创 读一读冰羚代码(14)数据发布的通知机制之state
以上代码构造完subscriber对象后,Roudi进程服务匹配过程会调用ChunkDistributor<ChunkDistributorDataType>::tryAddQueue将每个 subscriber的专属队列加入到publisher的subcriber队列缓存容器中,发布数据的时候会将数据push到队列缓存容器中存储的每个subscriber专属队列中,subscriber就可以接收到发布的数据了。publisher将数据放入队列后,敲响铃铛通知subscriber从队列中获取数据。
2025-04-23 17:48:28
1330
原创 读一读冰羚代码(13)subscriber数据订阅之订阅数据的获取
最后构造Sample对象,并利用Sample的指针功能获取发布数据对象的数据成员。),根据配置不同VariantQueue可以存储不同的无锁队列类型(),存储元素类型为ShmSafeUnmanagedChunk(m_queue的类型为VariantQueue(),最终调用的是无锁队列的pop()接口。Sample对象的构造和。
2025-04-22 12:37:58
397
原创 读一读冰羚代码(12)subscriber数据订阅之subscriber对象的构造
BasePort的MemberType_t的实际类型为BasePortData,SubscriberPortUser的MemberType_t类型为SubscriberPortData,SubscriberPortData继承自BasePortData,所以BasePort的构造函数用子类的指针给父类指针m_basePortDataPtr赋值。SubscriberPortData对象构造完成后会将该对象的地址通过socket返回给subscriber进程,流程和之前的文章中介绍的socket通信类似。
2025-04-22 11:05:30
710
原创 读一读冰羚代码(11)publisher数据发布之数据发布过程
m_queues是一个固定大小的vector容器,容器的元素类型是ChunkQueueData,服务发现阶段会将每一个匹配的subscriber队列添加进这个队列。因为subsciber可能动态取消订阅,publisher队列容器中存储的subscriber专属的队列可能会动态变化,只遍历仍然处于订阅状态的队列。(1)根据user payload的起始地址(即共享内存上的数据发布对象地址)还原chunk的起始地址(ChunkHeader对象的地址)
2025-04-21 17:23:16
585
原创 读一读冰羚代码(10)publisher数据发布之神奇的Sample类
模板实例化后,(4)未定义,(5)有定义。(1)中ForProducerOnly模板类型以ForProducerOnly<T, T>的形式传入,(2)中ForConsumerOnly模板类型以ForConsumerOnly<T, T>的形式传入。两者的不同之处在于,Publisher构造Sample对象传入的模板类型参数是T和H(Sample<T, H>),而Subscriber构造Sample对象传入的模板类型参数是const T和const H(Sample<const T, const H>)。
2025-04-21 14:39:54
832
原创 读一读冰羚代码(9)publisher数据发布之寻找共享内存上的可用空间
可以参考std::shared_ptr的实现,引入一个计数值,只用当计数值降为1时,释放chunk才是安全的。以User-Payload起始地址为基准,向前4个字节的区域存储User-Payload起始地址相对Chunk起始地址(等于ChunkHeader的地址)的偏移量,这样做的目的是根据User-Payload起始地址计算Chunk的起始地址。(2)m_lastChunkUnmanaged是ChunkSenderData中的一个数据成员,缓存最近使用的chunk,快速查找满足(1)要求的chunk。
2025-04-18 19:28:46
887
原创 读一读冰羚代码(8)publisher数据发布之publisher对象的构造
BasePort的MemberType_t的实际类型为BasePortData,PublisherPortUser的MemberType_t类型为PublisherPortData,PublisherPortData继承自BasePortData,所以BasePort的构造函数用子类的指针给基类指针m_basePortDataPtr赋值。ChunkSender的MemberType_t类型为ChunkSenderData,上面的构造函数的参数类型为指向ChunkSenderData对象的指针。
2025-04-18 12:54:05
1233
原创 读一读冰羚代码(7)publisher数据发布之初始化运行时环境
处理常规的共享内存映射操作,(1)根据Roudi进程传过来的segmentId建立publisher进程的共享内存地址数据库,也至关重要,后续的大多数操作都是根据数据结构对象的偏移量加上这个地址库还原共享内存上数据结构在publisher进程地址空间的绝对地址,请参考。(1)将Roudi进程创建的两段共享内存映射进publisher进程的地址空间,至关重要,没有它就没有共享内存的通信基础。(3)管理区SegmentManager对象的偏移量,根据此对象可以找到两段共享内存上建立的各种数据结构(
2025-04-17 16:22:56
778
原创 什么是c++中的继承构造函数
在Derived中只能显示定义拥有和Base构造函数一样参数列表的构造函数,然后再在该构造函数中显示调用父类对应参数列表的构造函数。Derived的构造函数没有被调用,反而是父类的默认构造函数可以被调用,似乎子类可以直接调用父类的构造函数构造对象(此时构造父类对象,子类的数据成员使用默认值)。Derived中的构造函数会首先调用父类的默认构造函数Base()构造父类对象,然后再执行其构造函数剩余的语句,num的输出为默认值10。让编译器合成一个子类的默认构造函数,合成的构造函数会调用父类的默认构造函数。
2025-04-16 10:30:25
448
原创 读一读冰羚代码(6)Roudi对进程的心跳监测
(3)如进程异常终止,该进程对应的Heatbeat对象无法周期调用beat()函数及时更新时间戳,时间超时,Roudi进程认为该进程已经终止,会在m_processList中查询该进程对应的Process对象(根据Heatbeat对象在m_heartbeatPool中的index判断),若存在则执行资源清理。(2)如发布者进程需要关闭,向Roudi进程发送IpcMessageType::PREPARE_APP_TERMINATION消息,Roudi进程收到该消息后会进行相关的资源清理,此处不再赘述。
2025-04-14 10:50:14
1195
原创 读一读冰羚代码(5)Roudi与进程之间的通信
根据的介绍,Roudi进程建立了两段共享内存并在管理区共享内存之上搞了很多基建。既然是共享内存,Roudi建立的两段内存肯定要被别的进程共享(比如数据的发布者进程和订阅者进程),进程们怎样才能顺利找到共享内存上已经构造好的数据结构呢?比如下面这张图:在m_segmentManagerBlock中构造了SegmentManager,发布者进程和订阅者进程需要拿到这个对象并借助这个对象找到可以存数据和取数据的内存区。在。
2025-04-10 11:21:58
1113
原创 读一读冰羚代码(4)Roudi共享内存的建立过程
这段代码的主要作用可以总结为一个项目经理(m_managementShm)需要负责为五个总监( m_introspectionMemPoolBlock、m_discoveryMemPoolBlock、heartbeatPoolBlock、m_segmentManagerBlock、m_portPoolBlock,排名有先后)创建一段共享内存并划分地址范围(请参考。(1)中的循环统计各个总监们(MemoryBlock)对内存大小即地址对齐(alignment)的要求,计算总的内存大小及对齐需求。
2025-04-03 13:46:43
959
原创 读一读冰羚代码(3)Roudi共享内存创建的关键数据结构
展示上图只是为了让大家对分配的共享内存有个直观的印象,具体的数据结构会在后续的文章中详细介绍。需要一个更大的管理者管理小内存池的管理者,即大内存池(Segement)的管理者。小的内存池需要有一个组织者,它负责存储小的内存池的起始地址、小的内存池里面包括多少个相同尺寸大小的内存块以及内存块的大小、该内存池对应的freeList_t(请参考。以上是一个大的内存池(Segment),大的内存池又被进一步细分为不同大小和数量的小内存池。2. 大内存池的管理者MemoryBlock(总监)
2025-04-02 14:11:23
647
原创 读一读冰羚代码(2)Roudi配置文件解析
为了提高内存的分配效率,内存池一般是一段预先分配好的内存,为了容纳不同大小的数据结构,内存池内部一般会进一步划分为更小粒度大小的内存。如上-c参数的说明至关重要,-c参数后提供了内存池的配置文件路径,如果用户没有提供该文件,则会优先查找/etc/iceoryx/roudi_config.toml,如果此文件也不存在,最后使用默认的参数设置。文件,但支持更复杂的数据结构和严格的类型定义,适合描述层级化配置。(2)toml的文件路径,根据toml文件的内容建立共享内存上的内存池供数据发布者和订阅者进程使用。
2025-04-01 17:31:34
922
原创 linux ACL权限控制之组权限控制程序设计
在linux的访问控制列表(ACL)中,mask的作用是限制用户和组的最大有效权限。如果某个用户的ACL条目权限超过了mask的限制,那么该用户的实际有效权限将被mask所限制。1和4表明mask的值变成了rw,2表明group的权限仍为r,使用chmod只能改变mask的值,并不能改变group的值,3表明user zoe的实际有效值为rw而不是rwx。简单来说,mask决定了setfacl对user和group设定权限的上限,比如意思mask的值rw,即使zoe被设置成了rwx,最后也只有rw的权限。
2025-03-28 15:46:00
453
原创 linux ACL权限控制之用户权限控制程序设计
linux中的ACL(Access Control List,访问控制列表)是一种比传统UNIX权限更细粒度的权限控制机制,允许为文件和目录设置更为具体的用户和组权限。因为zoe拥有vim的执行权限,可以启动执行vim,其它用户对testfile无任何权限,zoe虽然可以启动vim,且vim的进程所有者为zoe,但该进程却无法读取testfile中的内容。因为vox拥有该文件的读写权限,所以如果登陆的用户为vox,vox可以使用vim查看testfile中的内容。
2025-03-27 14:15:41
1165
原创 读一读冰羚代码(1)从demo开始
本系列文章尝试分析冰羚的代码实现,探讨共享内存零拷贝机制的软件设计。iox-roudi进程会预先分配一段共享内存供数据的发布者进程和订阅者进程进行数据通信,共享内存的相关信息(地址、大小等)通过socket分别发给发布者进程和订阅者进程。订阅者进程会睡眠等待共享内存的数据达到,若被发布者进程的通知唤醒,则检查对应的共享内存上是否有数据到达,若有,则取走数据。发布者进程会将产生的数据放到共享内存,如果有该数据的订阅者,通知订阅者进程从对应的共享内存获取数据。以上就是demo程序的下载、编译及运行的过程。
2025-03-26 15:29:39
648
原创 冰羚杂谈(四)上下游对齐工作节奏
订阅者A并不搬走独占这些数据,A在使用这些数据的同时,B、C也可以使用同一份存储在相同位置的数据,只有当A、B、C全部使用完后,这份数据才被标记为已用状态,此时该位置的数据可被丢弃,后续可以在该位置上存放新的数据。2. 资深健康风险评估师及资深战略运营专家要求队列满后,资深情报搜集专家将队列中现存的最先放入的位置信息弹出或另作他用,然后将本次位置信息放入该位置,资深健康风险评估师及资深战略运营专家下次从次先的位置取出位置信息。5. 专家们一致要求缓存队列的容量可动态伸缩,以此动态调节双方的工作节奏和负荷。
2025-03-17 19:31:17
1162
原创 无锁队列之Mpmc动态可伸缩队列
(3)如果待消费索引队列中索引数目少于当前实际容量大小,说明索引被重新返还给了m_freeIndices队列,再次尝试从该队列取出索引坐冷板凳,该函数的实现参考。Mpmc索引队列管理底层数组容器的底层索引,可以在运行时将部分索引的使用权收回从而达到减小队列容量的目的,反之则可以增加队列容量。(2)如果(1)中decrease的数量不满足toDecrease的设定要求,继续从待消费索引多列中取出元素坐冷板凳。(2)如果(1)失败,则从待消费索引队列中尝试取出最旧的索引,弹出底层数组中该索引对应的数据。
2025-03-14 13:59:50
413
原创 无锁队列之Mpmc Fifo队列
如果(2)条件不满足,(4)继续循环,同样可能造成循环无法终止,因为可能始终竞争不到索引来满足条件(1),但这种corner case发生的条件是多个生产者线程和多个消费者线程满足特定的时序要求,这种异常状况异同样可以认为在实际生产环境中不可能发生。(5)构造函数,m_freeIndices用来存储空闲索引,生产者使用,向队列特定索引位置存放数据。(2)(3)如果待消费索引队列中已满,弹出最旧的索引,并返回该索引中存储的数据,参考。(6)将该索引放入待消费索引队列中,供消费者取出索引并从索引位置取出数据。
2025-03-13 17:13:26
677
原创 无锁队列之Mpmc索引队列
这条语句后被长时间中断,导致其它生产者线程不断pop,消费者线程在使用完这些pop出来的索引值后重新归还给Mpmc索引队列,索引队列中存储的索引最终产生数据溢出,索引值被重置为一个较小值,此后线程A恢复执行value = loadvalueAt(readPosition, std::memory_order_relaxed);如果从Mpmc索引队列A pop出一个索引后,需要将该索引push进另外一个Mpmc队列B缓存,通过两个索引队列的协作可以产生索引先进先出,谁先pop出队列A,谁就先得到使用的功能。
2025-03-13 13:47:43
1403
原创 无锁队列之SpscSofi队列
如果消费者线程先读取了m_writePosition的值,此时生产者线程抢占,且持续向队列中push 几百次数据,可能会造成m_readPosition的值持续递增,甚至最终大于m_writePosition,size()的上述实现返回值可能为负值。观察以上过程发现,在队列为满的情况下,消费者线程和生产者线程共同从队列中取出了三个元素,而不是队列的设定容量2,似乎不匹配设定值,但此时消费者线程仅仅只能从队列中取走两个元素,符合容器大小的设定。如果没有更新,说明队列满,需要将最旧的元素返回给调用方。
2025-03-07 19:31:07
1775
原创 无锁队列之SpscFifo队列
从以上过程可以看出,消费者拿出的是旧数据,而不是生产者最新写入的数据。由此可见,需要先更新数据,再更新m_writePos,且生产者线程中更新的数据要对消费者线程中的pop可见。可见,生产者线程虽然有了新数据,但是消费者却遗漏了,没有及时取走数据,此种情形可以通过额外的线程同步机制或者消费者线程轮询数据规避,因为本文讨论的是纯粹的无锁编程,不再赘述。消费者取出数据后,生产者将数据写入12(对应索引2)的位置(注意,m_writePos存储的是下一个可用空闲索引,m_writePos每更新前的值为12)
2025-03-03 20:02:47
1422
原创 实现基于共享内存的固定容量容器
此外,为了优化迭代效率,需要确保迭代器仅遍历数组中有效的元素(即只遍历没有被删除的元素),避免对无效或未使用的元素进行不必要的处理。为了在共享内存中实现固定容量的数据存储,std::array是一个理想的选择。作为一种固定大小的容器,std::array在编译时确定其容量,且不会动态扩容,这使得它非常适合在共享内存中使用,因为共享内存的大小通常在初始化时就已经固定。(5)(6)分别是当前空闲slot和被占用slot的头部索引,遍历m_data元素时只遍历被占用的slot,忽略未被占用的slot。
2025-02-27 16:18:34
1256
原创 一种容器存储类型的自适应实现方法
在以上测试程序(4)中,容器的大小为255,实际这个数值用uint8_t就可以存储,但(3)中容器的元素类型被固定设置成了uint64_t,只要根据用户输入的255选择能够存储该大小的最小数据类型即可。因为需要存储(2)中数组的索引值,而数组容量(CAPACITY)的数组类型为uint64_t,所以(3)中数组的元素类型自然设为uint64_t。(9)为一个包装类,将输入的类型为uint64_t的value值与不同数据类型的最大值进行比较,以决定采用(5)(6)(7)(8)中的其中一个模板实例。
2025-02-25 18:13:27
511
原创 一种防止数据溢出的循环buffer设计
即当索引变为3时,下一个计算出的索引应该为4,但实际结果却为0。出现上述结果的原因为uint8_t的最大值为255,当255 + 1后值溢出了,重新变为了0。无论时uint8_t还是uint64_t,都有可能出现值溢出的情况,所以为了符合设计预期,必须针对值溢出的情况做特殊处理。(2)计算(1)中相邻的下一个数组索引,我们希望当值溢出时,索引从这个值重新循环。索引看起来循环起来了,即到达数组的最大索引5后,索引重新回到了数组的起始索引0。
2025-02-12 18:17:35
490
原创 c++使用模板元编程实现std::variant
c++模板元编程(Template Metaprogramming,TMP)是一种强大的技术,它利用C++模板的特性在编译期执行复杂的计算和代码生成。它通过模板的递归、特化以及类型推导等特性,实现编译时的代码生成和优化,从而提高程序的执行效率。模板的特化用来终止递归过程。std::variant可以用来存储不同的类型。但同一时刻只能存储一个类型的数据,可以使用索引来获取当前存储的类型的对应值。要实现std::variant的主要功能,关键思想是在一段内存中存储不同类型的值。
2025-01-29 20:56:06
1530
原创 c++ 中new出来的内存只能分配在堆上吗?
在(1)和(3)中分别重载了new和delete运算符,(4)(5)分别定义了类Person的构造函数和析构函数,(7)中使用new表达式构造了一个Person对象,(8)使用delete表达式将这个对象释放。(6)和(2)中得到的Person大小一样,调用new表达式的时候并没有传入Person的大小,但operator new运算符的形参却有size,编译器为我们传递了该形参的实参。(1)将Person的析构函数变成了虚拟的,(2)定义了一个形参size,(3)中将子类对象地址赋值给了其父类指针类型。
2025-01-26 17:38:48
1418
原创 冰羚杂谈(三)找到那只空着的箱子,用完放回去
但是,为了让你增加一点征服欲,让你觉得游戏币花的值,让你觉得你在博弈领域天赋禀异,游戏增加了一个规则,在将机器里的数字变成你想要的数字之前,必须首先猜对机器里面原先那个数字。针对这种问题,聪明的悟空又想出了一个绝妙的办法,除了在头部指示令牌中存储当前空闲箱子队列的头部索引,还存储了一个计数器。此时,如果矿工张三也需要同样尺寸的箱子,悟空拿出头部指示令牌一看,索引是N,只能无奈告诉张三,三哥,这个尺寸的箱子用完了,寻找比这个尺寸更大的箱子的流程比较繁复,要不再等等看,不会等太久的。这次,存放的索引是1。
2025-01-23 15:23:04
1050
原创 冰羚杂谈(二)建设云盘和怎样找到云盘
一从故事开始悟空出色完成了各种尺寸箱子在藏经阁的选址工作,矿工们挖出金子后存放在哪里的问题得以闭环。为了让挖矿项目持续推进,金矿跳动又招募来了三位金子搬运工小甲、小乙、小丙,为矿工甲、乙、丙分别提供一对一的专属服务。每次挖完金子,矿工甲必须在藏经阁找到一个合适尺寸大小的箱子将金子装箱,然后通知小甲,我已经将挖到的金子装进地址为1000的箱子啦,你赶紧运走吧,箱子最好及时清空,要不然下次我再挖完金子,可用的箱子可能会越来越少,我不希望出现上下游对焦节奏问题影响我的工作产出。小甲本来依然昏昏欲睡,但优秀的
2025-01-17 11:21:14
1192
原创 冰羚杂谈(一)在哪存放金子
出发前,悟空被告知了藏经阁的起始位置和大小,并被授予了三个锦囊,每个锦囊以天机相授,分别描述了每种箱子的尺寸、数目及对放置地址的要求。冰羚中BumpAllocator的作用类似开着小车给不同尺寸箱子寻找放置位置的悟空,用比较专业的话表述为,BumpAllocator的作用为在一段指定的内存空间上为特定对象指定构造的内存地址。allocate是BumpAllocator的核心函数,作用为根据对象的大小和地址对齐要求,在内存块中寻找符合要求的内存地址,在该地址可以构造该类型的对象。
2025-01-15 10:47:45
877
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人