1

I'm trying to understand the memory issues associated with dynamic struct arrays inside of dynamic struct arrays. I am dynamically creating an array of structs and attaching it to the variable 'persons' and then dynamically creating an array of structs and attaching it to the variable 'pets' inside of each 'persons' object. A simple example is this:

typedef struct {
  int age;
} Pet;

typedef struct {
  Pet *pets;
} Person

Create 2 Object Person array and attach it to variable 'persons'

persons = (Person*) ::operator new(sizeof(Person) * 2);
persons[0]
persons[1]

Create 3 Object Pet array and attach it to the variable 'pets' inside of object #1 of 'persons'

persons[0].pets = (Pet*) ::operator new(sizeof(Pet) * 3);
persons[0].pets[0].age
persons[0].pets[1].age
persons[0].pets[2].age

Create 2 Object Pet array and attach it to the variable 'pets' inside of object #2 of 'persons'

persons[1].pets = (Pet*) ::operator new(sizeof(Pet) * 2);
persons[1].pets[0].age
persons[1].pets[1].age

If I dynamically create another array of structs and attach it to the 'persons' array will the array previously attached to the variable 'persons' be free?

Create 4 Object Person array and attach it to variable 'persons' replacing the previous 2 Object array.

persons = (Person*) ::operator new(sizeof(Person) * 4);
persons[0]
persons[1]
persons[2]
persons[3]

If not, do I need to free each of the 'pets' arrays inside of each object of the 'persons' array?

Here it is in code running on an Arduino. The code works fine and seems to repeat indefinitely without any hiccups, but that doesn't mean it's legit.

// DYNAMIC MEMORY

// OBJECTS
// Pet object
typedef struct {
  char *name;
  int age;
} Pet;
// Person object
typedef struct {
  char *name;
  int age;
  int numberOfPets;
  Pet *pets;          // OBJECT 'Pet' is part of OBJECT 'Person'
} Person;

#define OBJECT_PET    0
#define OBJECT_PERSON 1


// DYNAMIC MEMORY HANDLING
// ARRAY Creation
void* createArray(int numberOfObjects, int typeOfObject) {
  numberOfObjects;
  int memorySize;
  switch (typeOfObject) {
    case OBJECT_PET:
      Serial.print("Creating Pet array of ");
      memorySize = sizeof(Pet) * numberOfObjects;
      break;
    case OBJECT_PERSON:
      Serial.print("Creating Person array of ");
      memorySize = sizeof(Person) * numberOfObjects;
      break;
  }
  Serial.print(numberOfObjects);
  Serial.println(" objects");

  return ::operator new(memorySize);
}

// GLOBAL VARIABLES
Person *persons;
int numberOfPersons;

void createPersonsAndPetsFirst() {
  numberOfPersons = 3;

  // CREATE 3 empty Person objects [0, 1, 2]
  persons = (Person*) createArray(numberOfPersons, OBJECT_PERSON);

  // ENTER Specific Person information for Person #1
  // Person #1 [0]
  persons[0].name = "Larry";
  persons[0].age = 19;
  persons[0].numberOfPets = 2;
  // CREATE 2 empty Pet objects for Person #1
  persons[0].pets = (Pet*) createArray(persons[0].numberOfPets, OBJECT_PET);
  // ENTER Specific Pet information for Pet #1
  // Pet #1
  persons[0].pets[0].name = "Xander";
  persons[0].pets[0].age = 3;
  // ENTER Specific Pet information for Pet #2
  // Pet #2
  persons[0].pets[1].name = "Shorty";
  persons[0].pets[1].age = 6;

  // ENTER Specific Person information for Person #2
  // Persons #2 [1]
  persons[1].name = "Mark";
  persons[1].age = 29;
  persons[1].numberOfPets = 1;
  // CREATE 1 empty Pet object for Person #2
  persons[1].pets = (Pet*) createArray(persons[1].numberOfPets, OBJECT_PET);
  // ENTER Specific Pet information for Pet #1
  // Pet #1 [0]
  persons[1].pets[0].name = "Fido";
  persons[1].pets[0].age = 5;

  // ENTER Specific Person information for Person #3
  // Person #3 [2]
  persons[2].name = "Larry";
  persons[2].age = 19;
  persons[2].numberOfPets = 2;
  // CREATE 2 empty Pet objects for Person #1
  persons[2].pets = (Pet*) createArray(persons[0].numberOfPets, OBJECT_PET);
  // ENTER Specific Pet information for Pet #1
  // Pet #1
  persons[2].pets[0].name = "Nado";
  persons[2].pets[0].age = 12;
  // ENTER Specific Pet information for Pet #2
  // Pet #2
  persons[2].pets[1].name = "Buster";
  persons[2].pets[1].age = 4;

  Serial.println();
}

void createPersonsAndPetsSecond() {
  numberOfPersons = 2;

  // CREATE 2 empty Person objects [0, 1]
  persons = (Person*) createArray(numberOfPersons, OBJECT_PERSON);

  // ENTER Specific Person information for Person #1
  // Person #1 [0]
  persons[0].name = "Chad";
  persons[0].age = 22;
  persons[0].numberOfPets = 1;
  // CREATE 1 empty Pet object for Person #1
  persons[0].pets = (Pet*) createArray(persons[0].numberOfPets, OBJECT_PET);
  // ENTER Specific Pet information for Pet #1
  // Pet #1
  persons[0].pets[0].name = "Lucky";
  persons[0].pets[0].age = 5;

  // ENTER Specific Person information for Person #2
  // Persons #2 [1]
  persons[1].name = "Lisa";
  persons[1].age = 36;
  persons[1].numberOfPets = 2;
  // CREATE 2 empty Pet objects for Person #2
  persons[1].pets = (Pet*) createArray(persons[1].numberOfPets, OBJECT_PET);
  // ENTER Specific Pet information for Pet #1
  // Pet #1 [0]
  persons[1].pets[0].name = "Chester";
  persons[1].pets[0].age = 7;
  // ENTER Specific Pet information for Pet #2
  // Pet #2 [1]
  persons[1].pets[1].name = "Marlo";
  persons[1].pets[1].age = 12;

  Serial.println();
}

void sendPersonsAndPetsInformationToHost() {
  Serial.print("Number of Persons = ");
  Serial.println(numberOfPersons);

  for (int personsIndex = 0; personsIndex < numberOfPersons; personsIndex++) {
    // SHOW Person
    Serial.print("Person #");
    Serial.print(personsIndex + 1);
    Serial.println(" Information");

    // SHOW Person Information
    Serial.print("Name = ");
    Serial.println(persons[personsIndex].name);
    Serial.print("Age  = ");
    Serial.println(persons[personsIndex].age);

    // SHOW Person number of Pets Information
    Serial.print("Number of Pets = ");
    Serial.println(persons[personsIndex].numberOfPets);

    for (int petsIndex = 0; petsIndex < persons[personsIndex].numberOfPets; petsIndex++) {
      // SHOW Pet
      Serial.print("Pet #");
      Serial.print(petsIndex + 1);
      Serial.println(" Information");

      Serial.print("Name = ");
      Serial.println(persons[personsIndex].pets[petsIndex].name);
      Serial.print("Age  = ");
      Serial.println(persons[personsIndex].pets[petsIndex].age);
    }
    Serial.println();
  }
}


void setup() {
  // OPEN Serial communication
  Serial.begin(115200);

}

void loop() {
  // CREATE First Persons and Pets Arrays
  createPersonsAndPetsFirst();

  // SEND Persons and Pets array information to Host
  sendPersonsAndPetsInformationToHost();

  delay(2000);

  // CREATE Second Persons and Pets Arrays
  createPersonsAndPetsSecond();

  // SEND Persons and Pets array information to Host
  sendPersonsAndPetsInformationToHost();

  delay(2000); 

}
ryan lindsey
  • 145
  • 1
  • 1
  • 9
  • You will likely want to do some more research on memory management and the use of the new and delete keywords. In a very simplistic explanation, everything that is allocated using new needs to be deallocated using delete. This can be challenging, especially with complex data structures. This is why people use RAII (Resource Acquisition is Initialization) to manage resources for them. In your case, you should think about using std::string for names and std::vector for your pets array. This will practically eliminate manual memory management needed by you. – Scott May 06 '15 at 16:18
  • `operator new` at your stage is like wanting to drive a race car on your driver's test after skipping driver's ed classes. Raw `new` is better, and `unique_ptr` even better. – Yakk - Adam Nevraumont May 06 '15 at 16:41
  • @Yakk - Thanks for the heads-up. 'operator new' provided the only method for dynamically creating structs that contain dynamically created structs that I could get to work correctly and repeatedly. I tried 'new' by itself with no luck - I'm sure it was due to my ignorance. However, I am certainly a fan of powering through a problem and having a chunky - yet workable solution before honing in on the most efficient method. I'll look in to 'unique_ptr'. Thanks! – ryan lindsey May 06 '15 at 17:16

2 Answers2

1

NO the previously allocated memory will not be freed. You will lose all allocated memory.

By overwriting your persons pointer, you lose your handle on the allocated storage. So before you do this, you have to call

::operator delete(persons[0].pets);
::operator delete(persons[1].pets);
::operator delete(persons);

This is pretty ugly, isn't it? For that reason exists the std::vector container.

#include <vector>

struct Person {
  std::vector< Pet > pets;
};

std::vector< Person > persons(2);
persons[0].pets = std::vector< Pet >(2);
persons[1].pets = std::vector< Pet >(3);

persons = std::vector< Person >(3); // won't lose memory

Since you have probably not heard of it, I would recommend to read up on RAII or here.

Community
  • 1
  • 1
user1978011
  • 3,419
  • 25
  • 38
  • Your first example explains a very workable solution for me that I can use immediately. I will begin studying RAII, but initially it's greatly complicating my ability to understand what's actually happening - even the syntax is confusing. Either way, I'm on it. Thank you very much for the help. – ryan lindsey May 06 '15 at 16:41
  • @ryanlindsey Don't just do what I told you, please! This is very bad coding style. That way, you will inexorably lose memory at some point! – user1978011 May 06 '15 at 16:48
  • If I follow your first method, how will I have memory issues? Is the idea that I will eventually have difficulty tracking my own arrays and forget to delete some? Or, is there a fundamental issue with this method that it cannot be done correctly? – ryan lindsey May 06 '15 at 17:06
  • @ryanlindsey *Is the idea that I will eventually have difficulty tracking my own arrays and forget to delete some?* YES – user1978011 May 06 '15 at 17:09
  • Do I also need to delete the char arrays for the name of each pet? – ryan lindsey May 06 '15 at 17:41
  • if it is an *array* no. If it is a pointer to an allocated string, yes. – user1978011 May 06 '15 at 17:45
0

In short, no, you need to delete them.

The first Person array that you created will become a garbage, and so you should delete (free) it before you assign another array to the pointer. (Think of the "new" operator as requesting permission (key) for accessing a memory location, and so if you lose the address of that location, you can't use it any more, and nobody else can either (because you have the key!) and so it becomes garbage.)

Now, if you delete the Person array, you will still have the Pet arrays as garbage, so you should delete them first (otherwise you don't have their address, and, again, garbage!), and then delete the Person array.

A better (and the standard) way is to write a destructor method for Person and delete the Pet array inside it. The destructor will be automatically called when a Person object gets deleted.

emjay
  • 420
  • 1
  • 5
  • 16