混搭下的C与C++内存操作
源自最近遇到一个的问题,先介绍一下背景。项目中混用了C与C++编程范式,鉴于项目成员背景不一,每个模块的负责人可以自行2选1。同时为了提高效率,C范式的模块被允许使用STL库的部分容器(其实也就仅仅大量使用了vector而已)。开发环境是visual studio 2005 wiht sp1。
那么问题来了,在部分模块中,纯C结构体和包含C++类的结构体共存,但它们的内存布局是不同的,所需要的初始化方式、内存操作函数均不同(malloc、new、memset....)。
巧合的是,在vs2005下包含vector类的结构体可以使用C的内存操作函数,而不会出错。比如用malloc申请结构体内存,用memset清空整个结构体都没有问题,程序可以正确运行。所以使用C规范的模块很开心的无脑使用malloc,且都是全局变量也不涉及free的问题。
悲剧的是因为外部原因,开发环境要升级到vs2010,上面的巧合不复存在,程序会崩溃,并且因为各模块大量使用了memset的方式来初始化包含vector的结构体,还需要解决这类结构体的初始化问题,比如非vector的结构体成员要求清零。
从程序的角度来说,这是一个POD问题,以下是维基百科的POD介绍:Plain old data structure, 缩写为POD, 是C++语言的标准中定义的一类数据结构,POD适用于需要明确的数据底层操作的系统中。POD通常被用在系统的边界处,即指不同系统之间只能以底层数据的形式进行交互,系统的高层逻辑不能互相兼容。比如当对象的字段值是从外部数据中构建时,系统还没有办法对对象进行语义检查和解释,这时就适用POD来存储数据。
简单来说就是POD类型在源代码兼容于ANSI C时非常重要。POD对象与C语言的对应对象具有共同的一些特性,包括初始化、复制、内存布局、寻址。
我们的目标不仅仅是清除现有隐患,而且要建立一个机制避免后续类似问题,因为一旦将来有人误用内存操作,由于错误地点和崩溃地点完全不同,定位会非常麻烦。总的来说有以下几点需求:
- 清理现有的POD内存申请和释放操作,并为POD内存申请提供检查机制,在误用POD内存申请的时候报错,比如用malloc申请了非POD内存。
- 清理现有的memset操作,并为memset提供检查机制,不允许对非POD内存执行memset操作。
- 支持对非POD内存块的内存清零操作
具体操作的思路是清理所有使用malloc的地方,替换为新封装的内存申请函数,然后回归测试,失败的地方肯定就是有非法POD操作,视具体情况逐个解决即可。
- C内存布局(POD类型)的操作
1)动态申请和释放
封装的MemAlloc函数会检查申请的类型是否符合POD要求。释放操作比较简单,封装的MemFree函数直接调用C语言的free。
template<typename T>
T* MemAlloc(size_t a = )
{
assert(std::is_pod<T>::value == true && "MemAlloc POD error");
T* mem = (T*)malloc(a*sizeof(T));
return mem;
}
例:
TEST_STRU* pPdu = MemAlloc<TEST_STRU>(); //申请单个MAC_PDU内存块
TEST_STRU* pPduS = MemAlloc<TEST_STRU>(10); //申请10个MAC_PDU内存块
MemFree(pPdu);
MemFree(pPduS);
2)内存置位操作
用封装的置位函数替换标准memset函数,新函数会检查操作的类型是否符合POD要求。采用宏替换,简单粗暴,注意控制宏的生效范围。
template<typename T>
void pod_memset(T* p,int val, size_t size)
{
assert(std::is_pod<T>::value== true && "pod_memset POD error");
::memset(p, val, size);
}
#define memset pod_memset
- C++内存布局(非POD类型)的操作
1)动态申请与释放
对于需要内存清零的申请操作,分别被封装为MemNew函数和MemNewMulti函数,函数中使用了不太常用的operator new和placement new,相关知识点这里就不介绍了。相应的释放函数为MemDel和MemDelMulti。
对于不需要内存清零的动态内存操作,请使用语言自带的new和delete。
template<typename T>
T* MemNew()
{
T *p = (T*)operator new(sizeof(T));
::memset(p,,sizeof(T));
new (p) T;
return p;
}
template<typename T>
void MemDel(T* p)
{
p->~T();
operator delete(p);
}
template<typename T>
T* MemNewMulti(size_t cnt)
{
T *p = (T*)operator new(sizeof(T)*cnt);
::memset(p,,sizeof(T)*cnt);
for(size_t i=; i < cnt; ++i)
{
new (&p[i]) T;
}
return p;
}
template<typename T>
void MemDelMulti(T* p, size_t cnt)
{
for(size_t i=; i < cnt; ++i)
{
p[i].~T();
}
operator delete( p);
}
例:
TEST_STRUCT* pUser = MemNew<TEST_STRUCT>(); //申请单个TEST_STRUCT内存块,并清零
TEST_STRUCT* pUsers = MemNewMulti<TEST_STRUCT>(3); //申请3个TEST_STRUCT内存块,并清零
MemDel(pUser);
MemDelMulti(pUsers, 3); //释放3个USER内存块,注意需要额外输入释放的个数
2)内存置位操作
因为上面已经禁用了非POD的置位操作,误用替换后的memset会触发断言保护。
对于动态申请的非POD类型的内存清零用上面的申请函数即可,对于栈上定义的非POD结构体可以用以下方式来完成结构体成员的清零操作。
//假设parent下的ueInfo是包含C++类成员的结构体(TEST_STRUCT),需要将其中的其他POD成员初始化为0
TEST_STRUCT temp={}; //临时变量,要求保证TEST_STRUCT的第一个成员可以用0进行初始化,如果第一个成员也是结构体或者是数组,可以写成{{0}}
parent.ueInfo = temp;
混搭下的C与C++内存操作的更多相关文章
- Windows下使用WSRM限制MongoDB内存
有个项目用到了MongoDB,我们是在WINDOWS 2008 64位环境下部署的,为啥不部署到linux下面呢,我们没那么多服务器,只能将就一下了. 大家都知道Mongodb吃内存太厉害了,如果不重 ...
- 【转】《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
原文地址:http://blog.csdn.net/slvher/article/details/9150597 对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构 ...
- Java API —— IO流(数据操作流 & 内存操作流 & 打印流 & 标准输入输出流 & 随机访问流 & 合并流 & 序列化流 & Properties & NIO)
1.操作基本数据类型的流 1) 操作基本数据类型 · DataInputStream:数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型.应用程序可以使用数据输出 ...
- 《深入理解计算机系统》C程序中常见的内存操作有关的典型编程错误
对C/C++程序员来说,内存管理是个不小的挑战,绝对值得慎之又慎,否则让由上万行代码构成的模块跑起来后才出现内存崩溃,是很让人痛苦的.因为崩溃的位置在时间和空间上,通常是在距真正的错误源一段距离之后才 ...
- C语言嵌入式系统编程修炼之三:内存操作
数据指针 在嵌入式系统的编程中,常常要求在特定的内存单元读写内容,汇编有对应的MOV指令,而除C/C++以外的其它编程语言基本没有直接访问绝对地址的能力.在嵌入式系统的实际调试中,多借助C语言指针所具 ...
- Marshal 类的内存操作的一般功能
Marshal类 提供了一个方法集,这些方法用于分配非托管内存.复制非托管内存块.将托管类型转换为非托管类型,此外还提供了在与非托管代码交互时使用的其他杂项方法. 命名空间:System.Runtim ...
- 【C/C++】C语言嵌入式编程修炼·背景篇·软件架构篇·内存操作篇
C 语言嵌入式系统编程修炼之一:背景篇 不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑,汇编语言具备这样的特质.但是,归因于汇编语言 ...
- C和C++的内存操作小贴士(一):const char*的内存释放问题
C和C++的内存操作一直是困扰开发人员的老问题,基本概念相信老司机们都很清楚了,在这里就不做过多的描述了,只是把在实际开发中可能遇到的一些小问题的案例列举下,供大家参考.“C和C++的内存操作小贴士” ...
- Java并发编程--7.Java内存操作总结
主内存和工作内存 工作规则 Java内存模型, 定义变量的访问规则, 即将共享变量存储到内存和取出内存的底层细节 所有的变量都存储在主内存中,每条线程有自己的工作内存,工作内存中用到的变量, 是从主 ...
随机推荐
- docker定义数据卷及数据卷的备份恢复
前言:生产环境中使用docker时,往往需要对数据进行持久化(只有把容器导出为镜像,才能够保存写的数据,否则容器删除或者停止,所有数据都会没有),或者需要在多个容器之间进行数据共享,这必然涉及容器的数 ...
- mybatis报错 Error instantiating interface com.atguigu.mybatis.dao.DepartmentMapper with invalid types () or values ()
mybatis报错 Error instantiating interface com.atguigu.mybatis.dao.DepartmentMapper with invalid types ...
- spring3: Bean的作用域
3.4 Bean的作用域 什么是作用域呢?即“scope”,在面向对象程序设计中一般指对象或变量之间的可见范围.而在Spring容器中是指其创建的Bean对象相对于其他Bean对象的请求可见范围. ...
- C++构造函数知识点整理(C++11标准)
引言 构造函数是c++中的一个比较难的语法知识点.编程实践中,由于在很多情况下可以不显示定义构造函数,或者,虽然定义构造函数的方式并不十分适当,但是程序也能正常运行,故而并不是特别引起开发者的重视. ...
- [eShopOnContainers 学习系列] - 03 - 在远程 Ubuntu 16.04 上配置开发环境
直接把 md 粘出来了,博客园的富文本编辑器换成 markdown,没啥效果呀 ,先凑合吧.实在不行换地方 # 在远程 Ubuntu 16.04 上配置开发环境 ## 零.因 为什么要用这么麻烦的 ...
- 获取整个页面HTML的信息以及注意事项
两种方式: 1.从每个节点获取 var a = '<!DOCTYPE html><html lang="zh-cn">'; var z = "&l ...
- FlashDevelop安装配置
1.下载 FlashDevelop,flash sdk,flash sdk debug,.net framework,java sdk(32位,不管机器是多少位,否则不能单步调试的) 2. 安装Fl ...
- Intellij IDEA基于Springboot的远程调试
简介 本篇博客介绍一下在Intellij IDEA下对Springboot类型的项目的远程调试功能.所谓的远程调试就是服务端程序运行在一台远程服务器上,我们可以在本地服务端的代码(前提是本地的代码必须 ...
- 【MFC】MoveWindow();函数使用详解
摘自:http://blog.csdn.net/yjkwf/article/details/5281207 CWnd::MoveWindow 详解 2010-02-02 16:08 3432人阅读 评 ...
- C++之内核对象进行线程同步
用户模式下的线程同步机制提供了非常好的性能,但他们也的确存在一些局限性,而且不适用于许多应用程序,例如,对Interlocked系列函数只能对一个值进行操作,它们从来不会把线程切换到等待状态.我们可以 ...