0

To make my code more readable, I tested to export the dynamic memory reservation (HEAP) in an extra function. Getting back a pointer to the startposition of the reserved memory.

But... how I can free the reserved memory after usage?

int i;
#define buffersize    5

// -------------------------------------------------------------------------------
char * dynReservation_of_Memory(long sizeOfBuf_in_Bytes) {
// -------------------------------------------------------------------------------
    char *Buf = new(std::nothrow) char[sizeOfBuf_in_Bytes];
    if (!Buf) { Serial.println("Error dyn. memory allocation >> Programmstopp");  while(1){}; }
    //
    for (byte i = 0; i<sizeOfBuf_in_Bytes; i++) {Buf[i] = i+20;} // test: filling the buffer with 20...24
    //
  return(&Buf[0]); // adress of first byte in buffer
} // ende Fkt

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Test-Sketch");
  //
  char * Buffer = dynReservation_of_Memory(buffersize);           
  for (byte i = 0; i<buffersize; i++) 
    { Serial.println(Buffer[i], DEC); }         // does the buffer contains the testdata 20...24?
  Serial.println("");
  //
  for (byte i = 0; i<buffersize; i++)         // fill the buffer with new testdata 10...14
    { Buffer[i] = i+10; }
  //
  for (byte i = 0; i<buffersize; i++)         // does the buffer contains the new testdata 10...14?
    { Serial.println(Buffer[i], DEC); }
  Serial.println("");
  // -----------------------------------------------------
  delete[] Buffer;                            // free reserved memory
  if (!Buffer) { Serial.println("Memory no more reserved..."); }                // check, whether memory is free
    else       { Serial.println("why the <delete[] Buffer> doesn´t worked?");}
  // -----------------------------------------------------
} // end of setup()

void loop() {
// -------------------------
  Serial.print(i);  i++; delay(5000);
} // end of loop

The response of my testprogram is:

*

Test-Sketch

20

21

22

23

24

10

11

12

13

14

why the <delete[] Buffer> doesn´t worked?

*

Paulsburg
  • 1
  • 2
  • `return(&Buf[0]); // adress of first byte in buffer` -- Why are you returning a pointer to the first character instead of the actual pointer returned by `dynReservation_of_Memory`? – PaulMcKenzie Jul 19 '22 at 05:36
  • 3
    `delete[] Buffer; if (!Buffer)` Deleting the buffer won't set `Buffer` to `nullptr`. – tkausl Jul 19 '22 at 05:38
  • @PaulMcKenzie Isn't `&Buf[0]` exactly synonymous with `Buf` (in this case, anyway)? – Adrian Mole Jul 19 '22 at 05:38
  • Yeah, you're right. But it seems odd to do things that way than to simply have `return Buf;` – PaulMcKenzie Jul 19 '22 at 05:40
  • 3
    *why the doesn´t worked?* -- It *did* work. The issue is that you thought that the pointer is guaranteed to be set to `nullptr` after the `delete`. – PaulMcKenzie Jul 19 '22 at 05:45
  • 1
    Looking for a duplicate, [but this is related](/questions/5002055/is-the-pointer-guaranteed-to-preserve-its-value-after-delete-in-c). – PaulMcKenzie Jul 19 '22 at 05:51
  • 1
    Use `std::array`, `std::vector`, `std::string`, `std::unique_ptr` or `std::shared_ptr`. Calling `new[]` yourself is almost always wrong. – Goswin von Brederlow Jul 19 '22 at 07:08
  • To add to @GoswinvonBrederlow comment: I'll go further and suggest that if you need a reliable embedded application, and I mean really reliable, you should not use heap at all, ever. Source of this recommendation: 15+ years of doing this, including patents for FDA-approved embedded-system medical devices where someone can DIE if it fails. – TomServo Jul 20 '22 at 20:24
  • @TomServo I've been fighting with this for some time now for my own embedded work and sometimes you just need dynamic structures. An allocator using a limited memory resource is a nice alternative to the general heap and a middle ground. You can nicely limit dynamic allocations by coupling the memory resource to the overall container and then the whole thing will go out of scope together without risks of memory leaks. – Goswin von Brederlow Jul 20 '22 at 21:01
  • @GoswinvonBrederlow Different industries (read: different regulatory agencies) have different standards. When I was building medical devices, our rule was no non-static data, period. Different projects have different requirements, from avionics, medical devices, electromechanical safeties and braking, etc. at one end of the spectrum, all the way to hobbyist projects at the other. – TomServo Jul 22 '22 at 15:39
  • @TomServo That is kind of the point. You create a static memory resource and pass an allocator for that into some algorithm like `merge_sort`. While `merge_sort` runs it can do "dynamic" allocations using that static memory as storage and when sort returns the static memory goes away statically. Obviously that only works if you know the maximum temporary memory required by the algorithm. – Goswin von Brederlow Jul 22 '22 at 17:45

1 Answers1

1

A simple MCVE may convince the OP that the delete[] does what's supposed to. For this, a sample struct is used which logs the calls of constructor and destructor.

MCVE on coliru:

#include <iostream>

struct Test {
  static inline unsigned idGen = 0;
  const unsigned id;
  
  Test(): id(++idGen)
  {
    std::cout << "Test::Test() -> Test { id: " << id << " } created\n";
  }

  ~Test()
  {
    std::cout << "Test::~Test() -> Test { id: " << id << " } destroyed\n";
  }
};

Test* makeTests(size_t n)
{
  Test* pTests = new(std::nothrow) Test[n];
  return pTests;
}

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ ; std::cout << '\n'

int main()
{
  DEBUG(Test *pTests = makeTests(5));
  DEBUG(std::cout << (void*)pTests << '\n');
  DEBUG(delete[] pTests);
  DEBUG(std::cout << (void*)pTests << '\n');
}

Output:

Test *pTests = makeTests(5);
Test::Test() -> Test { id: 1 } created
Test::Test() -> Test { id: 2 } created
Test::Test() -> Test { id: 3 } created
Test::Test() -> Test { id: 4 } created
Test::Test() -> Test { id: 5 } created

std::cout << (void*)pTests << '\n';
0xbd0c38

delete[] pTests;
Test::~Test() -> Test { id: 5 } destroyed
Test::~Test() -> Test { id: 4 } destroyed
Test::~Test() -> Test { id: 3 } destroyed
Test::~Test() -> Test { id: 2 } destroyed
Test::~Test() -> Test { id: 1 } destroyed

std::cout << (void*)pTests << '\n';
0xbd0c38

After delete[], the pointer still points to the same address in memory. Nevertheless the contents has been released. De-referencing the pointer after delete[] would result in Undefined Behavior.


There might be the expectation that the delete/delete[] could reset the pointer which is overhanded but that's not the case. This topic is mentioned in

Bjarne Stroustrup's C++ Style and Technique FAQ:

Why doesn't delete zero out its operand?

Consider

 delete p;
 // ...
 delete p;

If the ... part doesn't touch p then the second "delete p;" is a serious error that a C++ implementation cannot effectively protect itself against (without unusual precautions). Since deleting a zero pointer is harmless by definition, a simple solution would be for "delete p;" to do a "p=0;" after it has done whatever else is required. However, C++ doesn't guarantee that.

One reason is that the operand of delete need not be an lvalue. Consider:

delete p+1;
delete f(x);

Here, the implementation of delete does not have a pointer to which it can assign zero. These examples may be rare, but they do imply that it is not possible to guarantee that any pointer to a deleted object is 0.'' A simpler way of bypassing that rule'' is to have two pointers to an object:

T* p = new T;
T* q = p;
delete p;
delete q; // ouch!

C++ explicitly allows an implementation of delete to zero out an lvalue operand, and I had hoped that implementations would do that, but that idea doesn't seem to have become popular with implementers. If you consider zeroing out pointers important, consider using a destroy function:

template<class T> inline void destroy(T*& p) { delete p; p = 0; }

Consider this yet-another reason to minimize explicit use of new and delete by relying on standard library containers, handles, etc.

Note that passing the pointer as a reference (to allow the pointer to be zero'd out) has the added benefit of preventing destroy() from being called for an rvalue:

int* f();
int* p;
// ...
destroy(f()); // error: trying to pass an rvalue by non-const reference
destroy(p+1); // error: trying to pass an rvalue by non-const reference
Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56