Is there a commonly accepted-as-safe approach to wrapping malloc
in a function in C++? What I am attempting to do is allocat arbitrarily sized blocks of memory for holding the output of a function that writes binary values into a fixed size buffer (ie: machine instructions). The approach I am currently using is like so:
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
In many cases, it seems like explicit casts to (void*)
(at least in the case of my compiler) are not allowed, and I must use the template-based approach above.
Is this approach safe, and are there any implications with respect to the "rule of three" (ie: are there any constructors I need to disable or explicitly define/overload)?
Thank you.
References
<http://www.scs.stanford.edu/~dm/home/papers/c++-new.html>
<https://stackoverflow.com/questions/227897/solve-the-memory-alignment-in-c-interview-question-that-stumped-me>
Edit
The reason I am doing this is because I'm trying to use something potentially better than just allocating a block of characters via void* buffer = new char[100]
. To further elaborate, I am writing some lower-level code that, against my recommendations, must be written in C++, not pure C. I am also required to have dynamic memory allocation methods that create chunks of memory on the heap that are 16-byte, 32-byte, and 64-byte aligned, like in the example below for 16-byte alignment.
{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
My application literally creates group of low level machine instructions in blocks which must be 16/32/64 byte aligned (or it will trigger a CPU-level error), and then passes them off to an on-board DSP chip, which runs them as actual instructions, not just input data.
Topic 1: The old good void*
?
C++ has stronger typing than C. It's not a problem, but rather a solution ! The compiler in such way intercept a lot of nasty issues that would take you hours to find out when debugging.
Simple example. The folowing code compiles in C. When I execute I get a core dump.
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 ?
Why ? I inverted fp and pbuff. Both are victims of the void*
to anything*
and anything*
to void*
constructs. Of course, experimented programmers don't make such mistakes on standard libraries ! But what with the rest ?
With C++ this erroneous code doesn't compile, because converting void*
pbuff to FILE*
is recognized as a problem.
Topic 2: Is template voidp a safe practice ?
Well, C++ has classes and inheritence. And a whole bunch of casts to highlight the weird pointer operations you could (and are still allowed to) do. Again C++ eases the bug finding. For example a reinterpret_cast<>
deserves more care than a more controlled static_cast<>
To do safe things, one need to understand C++ object model and use the appropriate tools. Your voidp()
is very clever, but it's no an appropriate tool for OOP.
Simple example, first the native way:
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 !
Now with your voidp
approach:
voidp pa(&a);
voidp pb(&b);
pb = pa; // no problem.
gpb = pa; // no problem ! Not even a cast to draw attention on potential issue !
So you see, it's rather unsafe ! It really hides nasty errors !
Topic 3: Is malloc()
better than new
?
Honnestly, with malloc()
you can easily create anything. And it's as easy to create something of the wrong size.... With new
, you can also do weird things, but it's more difficult to do basic errors.
new
can call the object constructor. With malloc()
you need extra steps to do so.
Then, when you have malloc()
you have free()
. malloc()
/free()
are excellent for passive data structures that you have in C. But in C++, when you want get rid of an object properly, you have to destoy is. So delete
is really more appropriate .
Conclusion
I've read your reference with interest. It's true, that new
has limitations.
But I don't share the article's conclusions. Instead of avoiding new
and switch back to malloc()
(again: it's perfect for C code, but simply not the best fit for C++), it would be a better way to investigate the standard library and for example use shared_ptr<>
and other smart pointers. These are much better for avoiding memory leaks, than rewriting one own version of malloc...
Edit
The specific use that you make could be done in C++ as well
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
There exist also the function std::align()
that does the calculation you do for ptr.
And there is a placement-new to allocate a C++ object on a fixed address, for example:
char* p = new(ptr)char[1024]; // of course you shouldn't delete this one