Malloc on a struct containing a std::vector

2020-03-25 23:01发布

问题:

Here is the situation :

I use a malloc to allocate memory for a struct. The struct contains various items such as pointers, string variables and vectors.

The fact is, when we use malloc, no constructors are called. Using a code similar to the following one, I've experienced some situation where some variables worked while others didn't.

Note : The following code doesn't compile. It's purpose is only to illustrate the situation.

struct MyStruct 
{
    MyClass*    mFirstClass;
    bool        mBool;
    std::string mString;
    std::vector<MyClass> mVector;
};


int main()  
{  

    MyStruct* wMyStructure;  
    wMyStructure = (MyStruct*) malloc (sizeof(MyStruct));  

    MyClass wMyClassObject;

    wMyStructure->mFirstClass = new MyClass();  
    wMyStructure->mFirstClass->func();  
    wMyStructure->mBool = false;  
    wMyStructure->mString = "aString";  
    wMyStructure->mVector.push_back(wMyClassObject);

    return 0;
}

By using pointers instead of those variables (std::string* mString), followed by a call to the object constructor (mString = new std::string;) Exception are not thrown.

However, I've experienced a situation where the mString was used without problem without the constructor being called, but when it came to the vector, the application exit automatically.

This left me with many questions:

  1. When will an object throw an exception if no constructor were used?

  2. In the situation I experienced, only the vector caused problem. Could mString be left as it is or should I call it's constructor?

  3. What would be the safest way, using malloc, to do the whole thing?

回答1:

Your code causes undefined behaviour, because your wMyStructure does not point to an object, so you may not use the accessor operator -> on it.

An object only commences its life after its constructor has completed. Since you don't call any constructor, you do not have an object.

(If your struct were a POD, i.e. just consisting of primitive types and PODs, then this would be OK, because PODs have trivial constructors, which do nothing.)

The concrete problem you're facing is that the string and vector members of your struct didn't get to call their constructors, so those members don't exists, and hence the entire object doesn't.

If you want to decouple memory management from object construction, you can use placement syntax:

// get some memory
char arena[HUGE_VAL];
void * morespace = malloc(HUGE_VAL);

// construct some objects
MyClass   * px = new (arena + 2000) MyClass;  // default constructor
YourClass * py = new (morespace + 5000) YourClass(1, -.5, 'x');  // non-default constructor

(You have to destroy those objects manually, px->~MyClass(); etc., when you're done with them.)



回答2:

Using object without constructing it must be an undefined behaviour. Anything may happen at any moment. If you do this, you must not rely on any part of your code to run smoothly, because the language doesn't guarantee anything in this case.



回答3:

It is undefined behaviour to use a non-initialized object. Exception may be thrown at any time- or not at all.



回答4:

1 ) When will an object throw an exception if no constructor were used ?

If you don't call the constructor, there is no object. You have just allocated some space.

2 ) In the situation I experienced, only the vector caused problem. Could mString be left as it is or should I call it's constructor ?

This is all undefined behavior, just about anything could happen. There are no rules.

3 ) What would be the safest way, using malloc, to do the whole thing ?

The safest way would be not to use malloc, but allocate using new that will call constructors. It is as simple as this

MyStruct* wMyStructure = new MyStruct;


回答5:

None of the other answers appear to explain what the compiler is doing. I'll try to explain.

When you call malloc the program reserve some memory space for the struct. That space is filled with memory garbage, (i.e. random numbers in place of the struct fields).

Now consider this code:

// (tested on g++ 5.1.0 on linux)
#include <iostream>
#include <stdlib.h>

struct S {
  int a;
};

int main() {
  S* s = (S*)malloc(sizeof(S));
  s->a = 10;

  *((int*)s) = 20;

  std::cout << s->a << std::endl; // 20 
}

So when accessing a member of a struct you are actually accessing a memory position, there should be no unexpected behavior when writing to it.

But in C++ you can overload operators. Now imagine what would happen if the overloaded assignment operator need the class to be initialized, like in the code below:

// (tested on g++ 5.1.0 on linux)
#include <iostream>
#include <stdlib.h>

class C {
  public:
  int answer;
  int n;
  C(int n) { this->n = n; this->answer = 42; }
  C& operator=(const C& rhs) {
    if(answer != 42) throw "ERROR";
    this->n = rhs.n; return *this;
  }
};

struct S {
  int a;
  C c;
};

int main() {
  S* s = (S*)malloc(sizeof(S));

  C c(10);
  C c2(20);

  c = c2; // OK

  std::cout << c.n << std::endl; // 20

  s->c = c; // Not OK
            // Throw "ERROR"

  std::cout << s->c.n << std::endl; // 20
}

When s->c = c is executed the assignment operator verifies if s->c.answer is 42, if its not it will throw an error.

So you can only do as you did in your example if you know that the overloaded assignment operator of the class std::vector does not expect an initialized vector. I have never read the source code of this class, but I bet it expects.

So its not advisable to do this, but its not impossible to be done with safety if you really need. You just need to be sure you know the behavior of all assignment operators you are using.

In your example, if you really need an std::vector on the struct you can use a vector pointer:

class MyClass { ... };

struct S {
  std::vector<MyClass>* vec;
}

int main() {
  S s;
  MyClass c;
  s.vec = new std::vector<MyClass>();
  s.vec->push_back(c);
}