【问题标题】:C++ memory management of reference types引用类型的 C++ 内存管理
【发布时间】:2011-06-10 19:54:18
【问题描述】:

我还是一个相当新手的程序员,我有一个关于使用引用类型的 c++ 内存管理的问题。

首先,我对引用类型的理解:

一个指针被放入堆栈,指针指向的实际数据被创建并放置在堆上。标准数组和用户定义的类是引用类型。它是否正确? 其次,我的主要问题是 c 和 c++ 的内存管理机制(malloc、free 和 new、delete)是否总是正确处理并释放类或数组指向的内存?如果这些指针以某种方式重新分配给堆上相同大小/类型的其他对象,一切仍然有效吗?如果一个类有一个指向另一个对象的指针成员怎么办?我假设删除/释放类对象不会释放它的成员指针指向的东西,对吗?

谢谢大家!

-R

【问题讨论】:

  • 您将 C++ 与 Java 混淆了。获得一本合适的 C++ 书籍,并从头开始学习。您的 Java 知识不适用。 stackoverflow.com/questions/388242/…
  • 感谢大家的回答。我知道我随意乱扔 malloc、free、new 和 delete 意味着我把它们混在一起了,但我知道它们的区别。我只是想同时学习c和c++,然后把所有东西都扔在那里。此外,对于那些认为我在混淆 Java 的人来说,他们很接近。我正在混淆的是 c# :)
  • @Russel、CC++ 是不同的语言。选择一个:)
  • @Russel:除非使用 new 或 malloc 动态分配,否则数组也在堆栈中。
  • @PigBen 对数组分配提出了非常好的观点。这与 C# 的区别并不明显。

标签: c++ c memory-management heap-memory stack-memory


【解决方案1】:

听起来您正在从像 C# 这样的托管语言接近 C++。 C++ 中的情况有些不同。

您所描述的引用类型在 C++ 中不存在。 C++ 中的类型不是“引用类型”,也不是“值类型”。它们只是“类型”。它们是通过引用(或指针)还是通过值处理完全取决于使用类型的代码(而不是类型的定义)。相比之下,在 C# 等语言中,类型声明器决定是否必须将类型作为引用或值来处理。 C++ 确实有一个叫做 reference 的东西,但它与你描述的东西无关。我会在最后提到 C++ 参考。

现在,让我们看看我们是否可以处理您问题的几个部分:

一个指针被放入栈中并且 指针指向的实际数据 被创建并放置在堆上。

也许吧。如果您像这样创建对象,那将是正确的,例如:

class MyClass { /* ... */ };
...

MyClass* pObj1 = new MyClass();
MyClass* pObj2 = (MyClass*)malloc( sizeof(MyClass) );

但如果您像这样创建对象,则不会:

MyClass obj3;

在后者中,对象是在堆栈中分配的,不涉及指针或引用。您正在将其作为“值类型”进行操作。

MyClass *pObj3 = &obj3;

现在pObj3 是指向obj3 的指针(在堆栈中),它也在堆栈中。看?类定义与该类对象的存储位置之间没有联系。这取决于您如何使用类型。将同一类型的基于堆栈和堆的对象组合在一起是很常见的。

标准数组和用户定义的类是引用类型。这是正确的吗?

没有;数组只是一组放置在连续内存位置的相同类型/大小的对象。数组可以分配在堆栈或堆中,就像单个对象一样。

C 和 C++ 不会在数组上放置任何不同的语义(有一个例外,我稍后会提到)。一旦它们被分配,它们只是一堆碰巧是连续的对象。使用数组操作或直接指针操作来访问各个成员取决于您的程序。这意味着:

  • arrayOfClass[i] ((Class*)*(array + i)) 完全相同。在 C 中,您甚至可以说 i[arrayOfClass],它与 arrayOfClass[i] 的含义相同(但 C++ 会抱怨,因为它有更严格的类型规则)。
  • 您可以在指向不属于数组的对象的指针中使用数组运算符(并且可能会崩溃)
  • 您可以对数组中的元素使用普通的指针操作。
  • 您可以分配“大”内存块并“创建自己的数组”,方法是将该内存的较小连续片段解释为好像它们是较小对象数组的成员(这正是您使用 malloc( ))。

数组本身不是类型;它们只是分配多个对象的一种便捷方式,并且是一种在某些情况下更方便的指针方式。

我想到的“数组不是特殊的”规则的唯一例外是通过new 在 C++ 中分配的 case 数组。当您通过new 分配数组时,它会留下有关分配数组时包含多少元素的信息(通常在与数组相邻的堆中,但这不是强制性的)。然后,您必须使用特殊的delete [] 运算符来删除该数组。 delete [] 找到并使用额外的信息来正确删除数组的所有元素。

其次,我的主要问题是 c 和 c++ 的内存管理机制(malloc、free 和 new、delete)是否总是能正确处理并释放类或数组指向的内存?

只要你做事正确,是的。

如果这些指针以某种方式重新分配给堆上相同大小/类型的其他对象,一切是否仍然有效?

free() 是的,尽管在调用 free() 时使用指向不同类型的指针(void* 除外)是一件相当不寻常的事情。这些东西有合法用途,但它们是高级主题。您可能希望让经验丰富的开发人员查看您的设计,看看这是否真的合适。

delete 是另一回事;如果您使用的指针指向不同于 调用 delete 时存储在缓冲区中的类型,则行为是“未定义的”(也就是您可能会崩溃)。那是因为deletefree() 做得更多;它还调用对象的析构方法,编译器依赖指针的类型来调用正确的方法。如果你使用了错误的指针类型,就会调用错误的方法,谁知道会发生什么。在 new'd 之后,您可能会在缓冲区中添加“其他”内容,但这可能需要大量的工作,这又是一个高级主题。

还请注意,您永远不应使用malloc() 分配并使用delete 释放,也不应使用new 分配并使用free() 释放。确保您的方法正确配对。

如果一个类有一个指向另一个对象的指针成员怎么办?我假设删除/释放类对象不会释放它的成员指针指向的东西,对吗?

在 C++ 中,处理这个问题的规范方法是类应该有一个析构函数,而析构函数会负责释放指针成员。在 C 语言中,您别无选择,必须在清除外部指针之前手动清除指针成员。

这一切都假定对象拥有成员指针所指向的内容。在像 C# 这样的托管语言中,所有对象都由运行时“拥有”并在垃圾收集器的控制下被删除,因此您不必担心。在 C++ 中。谁“拥有”成员指针指向的对象是由程序的语义定义的,而不是语言,您必须注意确定何时是删除嵌入对象的正确时间。如果你错过了删除对象的正确时间,你就会泄漏内存;如果你过早删除它,你会得到未定义的行为和崩溃(当一些代码试图使用已经被删除的对象时)。

现在,C++ reference 基本上只是一个带有一点糖衣的指针,旨在使某些类型的指针更易于使用。原则上,在 C++ 中仅使用指针就无法使用引用,几乎没有什么可以做的。少数例外是我将跳过的高级主题(我必须查找它以使主题公正,而且我手头没有资源)。

在您看来,C++ 引用只是一个看起来像堆栈对象的指针。

CMyClass pObj1 = new CMyClass();
CMyClass& myObject = pObj1;      // Create a reference to the object pointed by pObj1

pObj1->Method1();                // Use the pointer to call Method1
pObj1.Method1();                 // Use the reference to call Method1

鉴于您的 C++ 知识水平,我可能暂时远离参考,直到您更好地了解 C/C++ 中的内存管理。

【讨论】:

  • 是一个很好的深入的解释。 +1
【解决方案2】:

一个指针被放入栈中并且 指针指向的实际数据 被创建并放置在堆上。

指针是存储另一个变量地址的变量。是的,可能就是这种情况。指针始终可以指向本地范围(或堆栈)或空闲存储区(堆)。

例子:

class Foo{
    public:
    // For Test1 This is in the Stack
    // For Test2 this is in the free store
    int x; 

    // For Test1 the pointer is in the Stack
    // AND -> It points, to where we set it (could be stack or heap)
    // For Test2 the pointer-variable-location is in the Free Store
    // AND -> Similar
    int *y;
}

int main()
{
    // Lies in the Local Scope
    Foo Test1;
    // Test2 Lies in the Local Scope, and contains the address of the 
    // Object, which is now on the Free Store
    Foo *Test2 = new Foo();
}

我的主要问题是做 c 和 c++ 的 内存管理机制(malloc, 免费和新的,删除)总是处理 这正确并释放内存 一个类或数组指向?

首先,尽量避免混用free和delete。其次,free() 获得一个指针,并且不检查提供的指针是否指向有效的内存位置。这意味着您可以尝试释放可能导致 Seg 错误的未分配内存。一个标准的例子是

int main()
{
     int * x; // Points to random
     free(x); // Undefined behaviour
}

指针的另一个错误使用可能是:

int main()
{
     int * x = malloc(sizeof(int) * 10); //Allocate an array of 10
     ++x; // Now points to x[1];
     free(x); // Give me trouble
}

如果这些,一切仍然有效吗? 指针以某种方式重新分配给 其他相同大小/类型的物体 堆?

是的,事情可以继续工作,但这可能会导致内存韭菜。只要你把旧的东西删掉就可以了。

如果一个类有一个指针成员怎么办 指向另一个对象?我是 假设删除/释放类 对象不会释放它的成员 指针指向,对吗?

free()ing 类不调用析构函数,可以使用析构函数来避免内存泄漏。使用delete可以设置析构函数删除其他对象,否则会导致内存泄漏。

你的帖子告诉我你混淆了一些东西,你开始说 C++ 中的引用,但最终谈论指针和free()delete,通常给人的印象是你很困惑。我认为你应该买一本好书。

【讨论】:

    【解决方案3】:

    我假设删除/释放 类对象不会释放它的内容 成员指针指向的,是那个 对吗?

    默认情况下是正确的,这就是 C++ 类具有析构函数的原因。例如:

    class C {
        int* x;
    
    public:
        C () : x (new int[10]) {}  // constructor
    };
    
    C* cptr = new C;  // a pointer to a C object
    

    如果我删除cptr,将会出现内存泄漏,因为x 没有被释放。所以我会给C添加一个析构函数:

    class C {
        ...
        ~C () {delete [] x;}  // destructor
    };
    

    (您的问题的其余部分存在大量问题。您似乎对 Java 和 C++ 感到困惑,这就是为什么您的大多数问题都没有意义。我只回答了这个片段来举例说明如何自动释放资源。我建议您阅读 C++ 书籍以更好地了解该语言。)

    【讨论】:

      【解决方案4】:

      引用基本上是一个使用自动变量语法的常量指针。您不能更改它所指向的内容,也不能使用它来删除对象(直接;您可以在引用上使用地址运算符来获取指向所引用对象的指针,然后将其删除)。

      函数mallocfree 只是分配和释放内存块。您通常不需要在 C++ 中直接使用这些。运算符newdelete 分配和释放内存,但它们也调用对象的构造函数和析构函数。在一个类的构造函数中,所有自动成员的构造函数都被调用,析构函数也是如此。如果你的类有一个指针成员,它的内存不会自动分配或释放。您必须在构造函数和析构函数中显式执行此操作,或者使用像 std::auto_ptr 这样的智能指针。

      在指针上使用delete 运算符时,会调用指针类型的析构函数,因此如果通过显式强制转换强制指针指向错误的类型,那么在删除时将调用错误的析构函数它。

      【讨论】:

        【解决方案5】:

        类不一定是引用类型。例如,如果您有以下代码:

        class dog {
            int age;
            char *name;
        };
        
        int main() {
            int x;
            dog barry;
            ...
        }
        

        然后在 main 内部,barry 和 x 将在堆栈上彼此相邻。所以堆栈将包含两个整数和一个指向字符的指针。

        您说得对,释放对象不会释放其成员指针所做的内存。 C++ 不会为您进行任何自动内存管理,因此必须手动分配和释放所有内容。在这种情况下,最好给 dog 一个析构函数,比如

        class dog {
            ~dog() {
                delete name;
            }
        }
        

        ~dog 会在你删除一条狗或一条狗超出范围并从堆栈中取出时被调用。

        【讨论】:

          【解决方案6】:

          我不知道从哪里开始。

          我们有原始类型(例如 int)。

          我们有我们的老朋友 c 结构。

          我们有课。

          所有这些都可以是存储类自动的;只是坐在堆栈上。都可以传值。

          然后我们有指向-x 的指针。 (x *)。它们将项目的地址存储在堆栈上,或者分配到其他地方,如new。一旦你得到一个指针,你就可以确保你不会做一些使它无效的事情。如果从函数返回,则指向其动态上下文中的自动项的指针将变为无效。如果您使用delete,则您删除的指针不再有效,当然,它的任何副本也不再有效。

          然后,最后,我们有参考。 x& 只是语法糖。传递对某事物的引用只是传递一个指向它的指针。使用引用类型避免了键入一些* 字符的需要,并且它断言表下的指针永远不会为空。

          【讨论】:

            猜你喜欢
            • 2017-08-09
            • 1970-01-01
            • 2012-03-19
            • 2010-09-06
            • 2018-12-17
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多