指针范围问题和返回类中封装的指针向量内的指针引用



我对封装在类中的向量内的指针、作用域和指针有一些疑问

我有这个假设的案例和带有问题的示例:

例 1

  1. 变量 int y 作用域在函数内部,当函数完成并消失时,返回的引用将被终止,并且该引用什么都没有引用?
int& createInt() {
int y = 5;
return y;
}

案例1:如果我在主函数或其他函数中执行此操作:

int x = createInt();
std::cout << "x value n";
std::cout << x << "n";
// std::cout return 5
  • 这意味着我保存了我自己的 createInt() 值副本 变量 x 中为 5 的函数,所以安全,因为 int x 包含自己的值?

  • 但是从createInt()函数返回的引用会发生什么,是否存在内存泄漏,因为不是指针,a将随着函数的作用域而死亡。

情况2:如果我在main或其他函数中执行此操作:

int &x = createInt();
std::cout << "x value n";
std::cout << x << "n";
// std::cout return 32767
  • int &x 等于从 createInt() 函数返回的引用,当函数完成/消失时,该引用就会死亡,因此因此 int &x 返回的值是错误的 32767 而不是 5,或者 32767 的值是什么?

    • 所以int &x = createInt(); 是邪恶和非常糟糕的做法,因为引用什么都没有。

示例 2

这个呢? 我正在请求为 int 分配内存,并向指针变量请求初始化内存...

int& createInt() {
int* y = new int(5);
return *y;
}

该指针变量在堆栈中,但存储对堆中新 int 的引用,以便当函数的作用域消失时,新 int 将处于活动状态,因为在堆中,对吗?

因此,当我返回引用时,我返回的是对该新 int 而不是指针变量的引用,对吧? 那么返回引用而不是指针是不好的吗? 为了什么?

案例1:如果我在主函数或其他函数中执行此操作:

int x = createInt();
std::cout << "x value n";
std::cout << x << "n";
// std::cout return 5

我正在我的本地 int x 变量中从 createInt() 创建新 int 值的副本,所以这是内存泄漏吗,因为我正在创建一个副本但没有获取指针,所以我无法删除 int x 变量,因为不是指针,而且我无法删除在 createInt() 函数中创建的 int *y 指针,因为指针丢失了, 我没有它在创建Int()之外

但是如果我这样做会发生什么:


delete &x;

我会得到一个错误:

malloc: *** error for object 0x7ffee204b8c8: pointer being freed was not allocated

因为我正在删除不在堆中的 int x? 还是尝试删除 createInt() 函数中的 int *y?

案例2:如果我使用相同的功能执行此操作:

int &x = createInt2();
std::cout << "x value n";
std::cout << x << "n";
// std::cout return 5

my int &x 是 createInt() 返回的引用

所以我可以做:

delete &x;

这里有内存泄漏吗? 但它是如此糟糕的删除 &x 引用而不是指针 int *y? 也许做删除 &我没有表格来确定这是分配的内存还是堆栈内存,所以好的做法是永远不要尝试使用 &?

矢量部分:

我有一个包含 B 类指针向量的类 A,我还有一个返回向量元素但作为参考的方法(因为我想让它在内存中重新利用它并控制何时像连接池一样被删除,我也把它从使用的向量移动到 notInUsevector,但这是其他历史), 在类 A 的析构函数中,我删除了所有向量元素:


Class A {
//this is singleton
public:
static A& getInstance() 
{
std::call_once(m_once, []() {
instance.reset(new Database());
});
return *instance;
}
B& getFirstElement() {
auto element = connections.front();
return *element;
}
~A() {
for(auto element : myVector){
delete num;
}
}
A(A const &) = delete;
void operator=(A const &) = delete;
private:
A();
static std::unique_ptr<A> instance;
static std::once_flag m_once;
std::vector<B*> myVector;
}

所以在其他地方/功能/类等,我做:

auto element = &A::getInstance().getFirstElement();

或者可能是最好的或相同的:

auto &element = A::getInstance().getFirstElement();

所以当删除 A 类实例时,析构函数将删除 myVector 中的所有指针

这安全吗,有内存泄漏? 在getInstance()函数中返回引用而不是指针是一个非常糟糕的?

谢谢

首先让我们明确一点,范围{}之间的"层"。它可能是函数体、语句(if、switch、for 等)或独立作用域。最重要的结果是在堆栈上创建的对象的生存期仅限于该范围。 以你的函数createInt()为例,内部int y,只存在于该函数内部。当你到达}那一刻,记忆是自由的。

第二件事是术语内存泄漏。内存泄漏是指您有一大块内存(可能是单个字节,可能是几页)并且无法指向它的情况。这就像一个没有钥匙的上锁盒子。它在那里,它是你的记忆,除非你告诉,否则它不会是免费的,问题是你不知道那个记忆在哪里,你没有它的指针。

话虽如此,让我们谈谈您的案例:

  • 例1 案例1:

你很幸运。您的函数通过引用返回其内部y,但在您从函数返回之前,此返回的变量将被释放。实际上,您在便利贴上写下了一个值,然后将其扔进垃圾桶,然后返回说您的值在垃圾纸条上。您仍然可以读取它的唯一原因是因为没有人同时分配该位内存并且它没有被覆盖。请记住,当你的函数到达}所有堆栈定位的变量(y在堆栈上分配)都会被销毁。这也回答了你关于内存泄漏的第二个问题,没有内存泄漏。只要您使用堆栈,分配和解除分配就会"自动"完成,因为析构函数调用(通常)由编译器插入范围末尾。

  • 示例 1 案例 2:

与案例 1 完全相同。返回的值已泄露,因为您返回的堆栈分配变量在您从函数返回时不再有效。现在您可以观察到这一点,因为在分配和读取该值之间,您进行了函数调用。根据原始y的分配位置,该内存位可能会在 std::cout 调用期间重用。当您处理引用使用的相同内存原因时,这一点变得很明显。请记住,当您达到createInt()}时,您释放了那一点内存。作为附加练习,在第int &x = createInt();行上放置一个断点,然后int x = createInt();进入函数并在离开内存状态时观察内存状态。

  • 示例 2 案例 1:

现在,您创建(潜在的)内存泄漏。您在堆上分配内存,以便在离开函数体时它不会被销毁。您还传递了确切的内存地址,因此您还应该负责释放它,并且您不调用delete,所以这就是您的内存泄漏。当您将返回值分配给全新的堆栈分配变量时,还会出现另一个问题。您来自createInt()的原始x被分配,然后通过引用返回,但馈送以将运算符分配给其他自变量(int x具有与int* y不同的地址)。同样,您可以在断点和调试器的帮助下检查这一点。

  • 示例 2 案例 2:

现在,这几乎是正确使用通过引用和赋值返回的变量。您在堆上创建变量x,然后返回其地址并将其分配给y。问题是x是在堆栈上创建的,因此您需要销毁它,因此必须调用delete以避免内存泄漏。放置断点并跟踪xy的内存地址。它们是一样的。

一般的经验法则是,引用返回应该只与存在于函数外部的变量一起使用,例如,您可以将其用于类 memebr,因为它们位于对象内部或全局或静态变量。对于其他目的,请使用按值返回,除非您确实需要该对象,否则请通过指针返回它。当您使用指针时,请始终注意某人在某些时候必须删除基础变量。

我跳过了关于向量的部分,而是为您指出了一个跟踪内存泄漏的好工具。无论如何,它都不是最好的解决方案,但对于初学者来说,它可以解决问题(假设你使用的是Visual Studio)。

https://learn.microsoft.com/pl-pl/visualstudio/debugger/finding-memory-leaks-using-the-crt-library?view=vs-2019

请记住使用断点和调试器,而不是将所有内容打印到输出/控制台。这将对您有很大帮助。跟踪内存和变量状态,并了解代码的真正作用。

我完全没有印象你有一个真正的问题,因为你似乎已经自己发现了所有的技巧、陷阱和良好做法。您似乎知道内存泄漏和悬而未决的引用。静态和动态分配。你的大多数假设似乎是正确的。不过一些提示: * 引用 '&' 和原始指针会产生完全相同的机器指令,但它们是对程序员的提示。实际上,返回引用的方法可能意味着您不必获得该引用的所有权,但是如果没有文档,则无法保证该对象的生存期。原始指针可以表示堆实例或可选参数,但它也可以仅表示引用。C/C++ 中缺乏所有权规则确实很棘手。 * 取消引用悬空引用时,行为未定义。它可能在您的机器上完美运行,可能会刻录我的硬盘。什么都行。 * 在处理堆分配时,我们现在更喜欢尽可能使用智能指针。它的用法没有错。搜索unique_ptr和shared_ptr * 最后,我重新学习了一点 Rust。这是一种专门设计用于解决此类对象生存期/所有权问题的编程语言。安装和运行最基本的示例非常容易。只需尝试使用 Rust 返回一个悬而未决的引用,你会学到很多东西,相信我。

最新更新