C++-如何安全地包装malloc或使用新运算符进行模拟

C++ - How to safely wrap malloc or emulate with the new operator

本文关键字:运算符 模拟 malloc 包装 何安全 安全 C++-      更新时间:2023-10-16

在C++中的函数中封装malloc有一种公认的安全方法吗?我试图做的是分配任意大小的内存块,用于保存将二进制值写入固定大小缓冲区(即:机器指令)的函数的输出。我目前使用的方法是这样的:

class voidp {
  void *p;
public:
  voidp (void *pp) : p (pp) {}
  template<class T> operator T *() { return (T *) p; }
};
When converting C to C++, you can define malloc like this:
inline voidp
mymalloc (size_t size)
{
  return malloc (size);
}
#define malloc mymalloc

在许多情况下,似乎不允许显式转换为(void*)(至少在我的编译器中是这样),我必须使用上面基于模板的方法。

这种方法安全吗?对于"三条规则"(即:是否有任何构造函数需要禁用或明确定义/重载),是否有任何含义?

谢谢。

参考

  1. 我对C++运算符new的看法-在类型系统中打一个整体,访问2014-09-05,<http://www.scs.stanford.edu/~dm/home/papers/c++-new.html>
  2. 解决C面试中困扰我的记忆对齐问题,访问2014-09-05,<https://stackoverflow.com/questions/227897/solve-the-memory-alignment-in-c-interview-question-that-stumped-me>

编辑


我这样做的原因是,我试图使用比通过void* buffer = new char[100]分配一个字符块更好的东西。为了进一步详细说明,我正在编写一些较低级别的代码,根据我的建议,必须用C++而不是纯C编写。我还需要有动态内存分配方法,在堆上创建16字节、32字节和64字节对齐的内存块,就像下面的16字节对齐示例一样。

{
  void *mem = malloc(1024+15);
  void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
  memset_16aligned(ptr, 0, 1024);
  free(mem);
}

我的应用程序在块中创建了一组低级别的机器指令,这些指令必须是16/32/64字节对齐的(否则会触发CPU级别的错误),然后将它们传递给板载DSP芯片,后者将它们作为实际指令运行,而不仅仅是输入数据。

主题1:旧的好void*

C++的类型比C强。这不是问题,而是解决方案编译器以这种方式截获了许多令人讨厌的问题,这些问题在调试时需要数小时才能发现。

简单的例子。下面的代码在C中编译。当我执行时,我得到一个核心转储。

FILE *fp;                     // file 
void *pbuff;                  // pointer to buffeer
pbuff = malloc(BUFSIZ); 
fp=fopen("test.txt", "rw");
fread(fp, BUFSIZ, 1, pbuff);  // ooops !!  Did you notice ? 

为什么?我把fp和pbuff倒过来。两者都是void*anything*anything*void*构建体的受害者。当然,经过实验的程序员不会在标准库上犯这样的错误!但剩下的怎么办?

使用C++时,这个错误代码不会编译,因为将void*pbuff转换为FILE*被认为是一个问题。

主题2:模板作废是一种安全的做法吗

C++有类和继承性。还有一大堆类型转换来突出显示你可以(现在仍然允许)做的奇怪的指针操作。C++再次简化了错误查找。例如,reinterpret_cast<>比控制更严格的static_cast<> 更值得注意

要做安全的事情,需要理解C++对象模型并使用适当的工具。您的voidp()非常聪明,但它不是OOP的合适工具。

简单的例子,首先是原生方式:

   struct A { std::string name; A(std::string s) : name(s) {} };
   struct B : A { int x, y;  B(std::string s, int a, int b) : A{ s }, x(a), y(b) {}  };
    ...
    A a{ "Chris" };
    B b{ "Tophe", 30, 40 };
    A *gpa = &a; 
    B *gpb = &b; 
    gpa = gpb;    // yes, works, because a B is an A and compiler knows it. 
    //gpb = gpa;  // No ! you can't without a recast !  

现在使用voidp方法:

voidp pa(&a); 
voidp pb(&b);
pb = pa;  // no problem.  
gpb = pa;  // no problem ! Not even a cast to draw attention on potential issue !   

所以你看,这相当不安全它真的隐藏了令人讨厌的错误!

主题3:malloc()new好吗

老实说,使用malloc(),您可以轻松创建任何内容。而且很容易制造出错误尺寸的东西。。。。使用new,你也可以做一些奇怪的事情,但更难做基本的错误。

new可以调用对象构造函数。使用malloc(),您需要额外的步骤才能做到这一点。

然后,当你有malloc()时,你就有了free()malloc()/free()非常适合C中的被动数据结构。但在C++中,当你想正确地去除一个对象时,你必须去存储它。所以delete确实更合适。

结论

我饶有兴趣地阅读了你的推荐信。的确,new有局限性。

但我不同意这篇文章的结论。与其避免new并切换回malloc()(同样:它非常适合C代码,但根本不适合C++),它将是研究标准库的更好方法,例如使用shared_ptr<>和其他智能指针。这些对于避免内存泄漏来说比重写自己版本的malloc要好得多。。。

编辑

你所做的具体用途可以在C++和中完成

char buffer[1024 + 15];     // allocation  on stack, or char *mem = new char[1024+15]  
char *ptr = reinterpret_cast<char*>(((uintptr_t)&buffer + 15) & ~(uintptr_t)0x0F);   // Yes
std::fill_n(ptr, 0, 1024);     // is memset_16aligned() really portable ?  
// nothing if on stack         // or delete[] mem if new was used                 

还有一个函数std::align(),用于计算ptr。还有一个新的位置可以在固定地址上分配C++对象,例如:

char* p = new(ptr)char[1024];  // of course you shouldn't delete this one 

如果您正在编写C++代码。您应该使用new和delete。用C++编写C风格通常是不好的做法,因为C++编译器不会生成最佳的二进制文件。如果你发现自己用void做了很多类型的铸造,那么你可能做错了。

相反,使用C++11智能指针!

newdelete用于低级别库开发。有STL容器可用于缓冲区分配和取消分配。

在您的情况下,您可以尝试

std::vector<unsigned char> myBuffer(MAX_SIZE);