【问题标题】:Malloc on a struct containing a std::vector包含 std::vector 的结构上的 Malloc
【发布时间】:2011-08-16 22:17:20
【问题描述】:

情况如下:

我使用 malloc 为结构分配内存。 该结构包含各种项目,例如指针、字符串变量和向量。

事实上,当我们使用 malloc 时,不会调用任何构造函数。使用类似于以下代码的代码,我遇到了一些变量有效而其他变量无效的情况。

注意:以下代码无法编译。其目的只是为了说明情况。

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;
}

通过使用指针而不是那些变量 (std::string* mString),然后调用对象构造函数 (mString = new std::string;) 不会引发异常。

但是,我遇到过这样一种情况,即使用 mString 没有问题而没有调用构造函数,但是当涉及到向量时,应用程序会自动退出。

这给我留下了很多问题:

  1. 如果不使用构造函数,对象何时会抛出异常?

  2. 在我遇到的情况下,只有矢量引起了问题。 mString 可以保持原样还是我应该称它为构造函数?

  3. 什么是最安全的方式,使用 malloc 来完成整个事情?

【问题讨论】:

  • 可能有一个有趣的问题,但请从头开始重写这个问题,并着手进行演示。问题中没有任何意义或者是 C++(没有 void main() 开头)。为了您尊敬的读者,请不要将您的班级称为“班级”。
  • 我在您的代码中没有看到对 malloc 的调用。查找'placement new operator'。
  • @bmargulies:更贴切的可能是“placement-new 表达式”。接线员很无聊,反正你一般不会直接打电话给接线员的。
  • 对不起,我写这个问题的时候分心了。我希望这一次更有意义。
  • 好吧,我不能真正发布我正在处理的代码,因为它不是我的。至于选型,我弄错了,应该是 MyStruct*。我的主要目标并不是真正找到问题的解决方案,而是了解问题的原因。

标签: c++ vector constructor memory-management malloc


【解决方案1】:

使用对象而不构造它必须是未定义的行为。任何事情都可能随时发生。如果你这样做,你不能依赖代码的任何部分来顺利运行,因为在这种情况下,语言不能保证任何事情。

【讨论】:

  • 同意。即使它看起来正常工作,也可能会发生坏事。
【解决方案2】:

您的代码会导致未定义的行为,因为您的 wMyStructure 未指向对象,因此您不得在其上使用访问器运算符 -&gt;

一个对象只有在它的构造函数完成后才开始它的生命。由于您不调用任何构造函数,因此您没有对象。

(如果您的结构是一个 POD,即仅由原始类型和 POD 组成,那么这没问题,因为 POD 有简单的构造函数,它们什么都不做。)

您面临的具体问题是结构的字符串和向量成员没有调用它们的构造函数,因此这些成员不存在,因此整个对象也不存在。

如果要将内存管理与对象构造解耦,可以使用放置语法:

// 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

(你必须手动销毁这些对象,px-&gt;~MyClass(); 等,当你完成它们时。)

【讨论】:

  • 好的,这个我能理解。我不明白为什么即使没有调用构造函数,字符串变量也能正常工作。当我尝试为变量赋值时,它不应该导致异常吗?
  • @Sim:嗯,这是未定义的行为,所以任何事情都可能发生,包括(最危险的)你所期望的。作业覆盖,因此您可能已经侥幸逃脱;访问(甚至只是打电话给size() 可能会让你更快陷入麻烦。无论如何,不​​要这样做,这是不允许的和未定义的,而且没有什么有意义的理由。
  • @Kerrek:好的,这很好地回答了我的问题。我只会在结构中包含的每个对象上调用构造函数/析构函数。
  • @Sim:不能直接调用成员对象的构造函数。您只能使用 new 表达式显式构造对象。但是为什么您需要这样做?这几乎可以肯定是没有意义的。发布您的真实情况,我们一定能找到合适的解决方案。
  • @Kerrek :这就是我调用构造函数的意思。至于我的情况,我必须使用一个可以在我的结构上创建 malloc 的函数。我相信某些结构对象成员必须通过此函数分配,但我无法确定,因为我无权访问它的源。除了这些对象,还有一些向量和字符串变量。使用向量时抛出异常。我已经能够通过简单地将向量更改为指向该向量的指针并使用新表达式来解决问题。这也是让我对字符串大小写感到疑惑的原因。
【解决方案3】:

使用未初始化的对象是未定义的行为。任何时候都可能抛出异常——或者根本不抛出。

【讨论】:

  • 学究式地,没有“未初始化对象”之类的东西。对象在构造函数完成时开始其生命。否则没有对象。
【解决方案4】:

1) 如果没有使用构造函数,对象什么时候会抛出异常?

如果你不调用构造函数,那么就是没有对象。您刚刚分配了一些空间。

2 ) 在我遇到的情况下,只有向量引起了问题。 mString 可以保持原样还是我应该称它为构造函数?

这都是未定义的行为,几乎任何事情都可能发生。没有规则。

3 ) 使用 malloc 完成整个操作的最安全方法是什么?

最安全的方法是使用 malloc,而是使用将调用构造函数的new 进行分配。就这么简单

MyStruct* wMyStructure = new MyStruct;

【讨论】:

    【解决方案5】:

    似乎没有其他答案可以解释编译器在做什么。我会尽力解释。

    当您调用malloc 时,程序会为结构保留一些内存空间。该空间充满了内存垃圾(即随机数代替结构字段)。

    现在考虑这段代码:

    // (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 
    }
    

    因此,当访问结构的成员时,您实际上是在访问内存位置,应该在写入时不会出现意外行为。

    但在 C++ 中,您可以重载运算符。现在想象一下,如果重载的赋值运算符需要初始化类会发生什么,如下面的代码:

    // (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
    }
    

    s-&gt;c = c被执行时,赋值操作符会验证s-&gt;c.answer是否是42,如果不是就会抛出错误。

    因此,如果您知道 std::vector 类的重载赋值运算符不需要初始化向量,则只能按照示例中的操作进行操作。我从来没有读过这个类的源代码,但我敢打赌它期望。

    因此不建议这样做,但如果您确实需要,也不是不可能安全地进行。你只需要确保你知道你正在使用的所有赋值运算符的行为。

    在您的示例中,如果您确实需要结构上的std::vector,您可以使用向量指针:

    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);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-22
      • 2014-11-24
      • 2014-12-13
      • 2012-07-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多