2

I am working on an application where I am allocating memory from a pool for use with arithmetic data types - all fundamentals, except for std::complex. I have been using malloc, ensuring alignment, and casting the pointer to the data type without any issues. I know placement new is, in general, required for doing this with objects. However, I am not clear if this is required for fundamental data types, and if there are any exceptions to needing this with objects. Specifically, is this legal to do with std::complex?

//Simple example just using malloc
Class AnyClass;
AnyClass *p = malloc(sizeof(AnyClass));
//We have memory allocated for the size of AnyClass, but no object at that memory. Construct an object a location p using placement new
AnyClass *o = new (p) AnyClass;
//Then o can be used. To cleanup:
o->~AnyClass();
free(p);

//In C, we can just do the following. Is this legal in C++ (with fundamental types)?
int *p = malloc(sizeof(int));
//Can just use p as a pointer to int now. Cleanup is just:
free(p);

//If the omitting a call to placement new is legal with fundamental data types in C++, are there ANY objects where this is legal? If so, what exactly are the requirements? Is this ok with std::complex?


Brian
  • 23
  • 3

2 Answers2

1

Is placement new required when using allocated memory with fundamental data types?

Yes, placement new is required in C++ to create objects into mallocated storage whether the type of the object is fundamental or not. Unless you create an object, you may not access it (except through a char* or similar).

The destructor call is not necessary for trivially destructible types.

There is a proposal p0593r2 to introduce "Implicit creation of objects for low-level object manipulation" to the language, which would remove the need for placement new in this case. It was not adopted into C++20.


P.S. AnyClass *p = malloc(sizeof(AnyClass)); is ill-formed, since malloc returns a void* which is not implicitly convertible to AnyClass* in C++.


for trivially copyable types, it is legal to allocate memory using malloc and do a memcpy.

Same as above. The copying is legal, but unless you create objects, you cannot access the memory that you copied.

static_assert(std::is_trivially_copyable_v<AnyClass>);
AnyClass a{};
void* p = malloc(sizeof(AnyClass));      // It is OK to allocate

Bad example:

std::memcpy(p, &a, sizeof a);            // It is OK to copy memory
AnyClass* o = static_cast<AnyClass*>(p); // Still OK (but not useful)
int i = o->some_member;                  // NOT OK; no object exists

Good example:

AnyClass *o = new (p) AnyClass;
std::memcpy(p, &a, sizeof a);
//std::memcpy(o, &a, sizeof a);          // or this; either works
int i = o->some_member;                  // OK; object exists
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Follow up question then - for trivially copyable types, it is legal to allocate memory using malloc and do a memcpy. When doing that, no constructors are called - how is that legal? How are objects created in that new memory? From cppreference: "In general, for any trivially copyable type T and an object obj1 of T, the underlying bytes of obj1 can be copied (e.g. by means of std::memcpy or std::memmove) into an array of char, unsigned char or std::byte or into obj2, a distinct object of T. Neither obj1 nor obj2 may be a potentially-overlapping subobject." – Brian Aug 09 '19 at 16:22
  • Got it - so the copy is legal, but there isn't an object there unless you explicitly create one. So last question I think - why? What actually happens with the instantiation of a fundamental object that differs from implicitly treating a section of memory as an object of that type? – Brian Aug 09 '19 at 17:40
  • @Brian Because the standard says so. I don't know the exact rationale that lead to this design, but possible reasons are 1. Optimisation: When the compiler can prove that no object of particular type exists at some address, it may be able to assume that the memory doesn't overlap some other address which it has proven to contain an object, and that proof can be used to eliminate certain branches, and replace `memmove` calls with the faster `memcpy` etc. Or 2. It allows implementations to diagnose logic errors of accidentally accessing objects outside of their lifetime. – eerorika Aug 09 '19 at 20:57
  • I had a feeling the answer would be along those lines. I guess as much as much as I like to think about the code being processed the way I think about it, it doesn’t always work that way. Thanks for all the answers, I really appreciate the clarification on all of that! – Brian Aug 10 '19 at 04:38
0

Not sure if I understand you correctly, but it seems like you don't need to allocate objects dynamically (from the memory pool aka heap).
You also don't need to use malloc but can use new with delete. This is better than malloc/free at least because new will automatically call a constructor and delete will call a destructor.

shargors
  • 2,147
  • 1
  • 15
  • 21
  • I do need to allocate them using a memory pool - I just showed the simplest case to illustrate my question here. – Brian Aug 09 '19 at 16:24
  • Do you need to use malloc/free? Can you use new/delete instead? By 'new' I mean non-placement new but a regular new. – shargors Aug 09 '19 at 17:12