3

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

  1. My Rant on C++'s operator new - Punching a Whole in the Type System, Accessed 2014-09-05, <http://www.scs.stanford.edu/~dm/home/papers/c++-new.html>
  2. Solve the memory alignment in C interview question that stumped me, Accessed 2014-09-05, <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.

Community
  • 1
  • 1
Cloud
  • 18,753
  • 15
  • 79
  • 153
  • 8
    Why are you usign `malloc` instead of `new`? – GWW Sep 05 '14 at 20:28
  • What's the point of this?You aren't writing a memory manager or not taking care of size numbers requested etc – Cratylus Sep 05 '14 at 20:29
  • TL;DR; Something using [placement `new()`](http://stackoverflow.com/questions/222557/what-uses-are-there-for-placement-new) may be?!? – πάντα ῥεῖ Sep 05 '14 at 20:30
  • maybe store the output of your function in a `std::vector` for arbitrarily sized blocks of memory – YoungJohn Sep 05 '14 at 20:42
  • this sounds like an XY problem. – YoungJohn Sep 05 '14 at 20:42
  • 2
    What's generally accepted is that you should make up your mind whether you're going to write C or C++, *not* trying to continue writing C that's warped enough to be compiled by a C++ compiler (which is what the linked article seems to attempt). – Jerry Coffin Sep 05 '14 at 20:51
  • 3
    The author of your article is nostalgic, has not understood the benefits of strong typing, nor the philosophy of C++. You should really use better sources for your inspiration, such as "Effective C++: 55 Specific Ways to Improve Your Programs and Designs": with smart pointers and some design efforts you could to get rid of need to override malloc() for debugging purpose. – Christophe Sep 05 '14 at 20:52
  • So you want to go back to C code you must wrap because the code that does exactly that in C++ "can't implicitly cast from `void*`"? C or C++, pick one. These are different languages. Mixing the two should be done only for compatibility purpose IMHO (old libs only in C, etc.) – JBL Sep 05 '14 at 21:41
  • @πάνταῥεῖ It's not about **where** I want to allocate the memory (well, in the heap anyways), but the exact size, and the ability to cast it to `void*` so it can be used to store arbitrary binary data. – Cloud Sep 05 '14 at 22:13
  • @Dogbert You're barking up the wrong tree! Using `void*` is the wrong approach. Read more about placement new, and how to use it. If this isn't solving your problems, you're having a XY-problem here, and your design is seriously flawed (which could be suspected using placement new already, besides the rare valid cases). – πάντα ῥεῖ Sep 05 '14 at 22:17
  • So, from the edits above, I now note that I will have instructions that are 16/32/64 bits in width, and they must be "fed" to an external DSP in 16/32/64 BYTE aligned chunks, or it causes a hardware error. – Cloud Sep 05 '14 at 22:24
  • @Christophe I don't feel the "nostalgic" comment is really appropriate. I've worked with C++ for a while, but mainly with higher level work (application level, we do all our driver, firmware, and OS level code in C and assembler). I've updated the question to be more specific (ie: portable 16-byte alignment requirements, etc). This should justify why I find myself compelled to use `malloc`, and `void*` casts, since I'm writing byte streams of variable bit-widths to it. – Cloud Sep 05 '14 at 23:09
  • @GWW I've updated the question. I'm using `malloc` instead of `new` because I don't see any portable way of using `new` to force 16-byte alignment. – Cloud Sep 05 '14 at 23:20
  • @Cratylus As I note in a comment above, I'm technically writing a minimal memory manager, and need to be able to enforce 16-byte alignment in a portable (ie: non-GCC-specific) manner. – Cloud Sep 05 '14 at 23:21

3 Answers3

2

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 
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Would you be able to take a glance at my second edit? I elaborate a bit more on my problem. I need to write binary machine instructions which get passed off to an external chip, and I need to have a portable means of enforcing 16/32/64 byte alignment of the allocated memory. I know how to do this in a portable manner in C via `malloc`/`calloc`, but not with `new`. – Cloud Sep 05 '14 at 22:22
  • 1
    let me have a look - at first sight it would be a candidate for a placement-new. – Christophe Sep 05 '14 at 22:25
  • That's the thing. I could use placement-new (a concept I'm fairly familiar with), but I don't see any way to force either placement-new to do things like 16-byte alignment without the use of GCC-specific compiler extensions. I could always use static memory allocation tricks (ie: re-casting an array of `double`s to char, or some bizarre semi-portable `struct` manipulation trips), but that might not work either. – Cloud Sep 05 '14 at 22:34
  • 1
    I've added an edit to answer to your specific case. – Christophe Sep 05 '14 at 23:20
  • `std::align()` looks like what I need, along with the edits made. Thank you! – Cloud Sep 05 '14 at 23:22
  • For Topic 1, you could use `char * pbuff` and the compiler will catch the problem. – martinkunev Jan 14 '21 at 11:36
1

If you're writing C++ code. You should use new and delete. Writing C style in C++ is generally bad practice, as the C++ compiler will not produce optimal binaries. If you find yourself doing a lot of type casting with void*, you're probably doing it wrong.

Conversely, use C++11 smart pointers!

davepmiller
  • 2,620
  • 3
  • 33
  • 61
0

The new and delete are for low level library development. There are STL containers available for buffer allocation and de-allocation.

In your case, you may try

std::vector<unsigned char> myBuffer(MAX_SIZE);
Garland
  • 911
  • 7
  • 22