8

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

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

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

谢谢大家!

-R

4

6 回答 6

19

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

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

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

将指针放在堆栈上,然后创建指针指向的实际数据并将其放置在堆上。

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

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

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

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

MyClass obj3;

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

MyClass *pObj3 = &obj3;

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

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

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

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

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

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

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

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

只要你做事正确,是的。

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

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

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

另请注意,您永远不应该分配 withmalloc()和 free with delete,也不应该分配 withnew和 free with free()。确保您的方法正确配对。

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

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

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

现在,C++引用基本上只是一个带有一点糖衣的指针,旨在使某些类型的指针更易于使用。原则上,在 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++ 中的内存管理。

于 2011-01-18T03:04:48.010 回答
3

我假设删除/释放类对象不会释放它的成员指针指向的东西,对吗?

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

class C {
    int* x;

public:
    C () : x (new int[10]) {}  // constructor
};

C* cptr = new C;  // a pointer to a C object

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

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

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

于 2011-01-18T02:31:02.440 回答
2

我不知道从哪里开始。

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

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

我们有课。

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

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

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

于 2011-01-18T02:14:56.163 回答
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 和 new、delete)是否总是正确处理并释放类或数组指向的内存?

首先,尽量避免混用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()类不调用析构函数,你可以使用析构函数来避免内存泄漏。使用delete你可以设置析构函数来删除那个其他对象,否则会导致内存泄漏。

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

于 2011-01-18T02:16:30.013 回答
1

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

class dog {
    int age;
    char *name;
};

int main() {
    int x;
    dog barry;
    ...
}

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

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

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

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

于 2011-01-18T02:17:26.013 回答
0

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

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

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

于 2011-01-18T02:18:32.980 回答