在C++中释放非动态内存

Getting free of non-dynamic memory in C++

本文关键字:动态 内存 释放 C++      更新时间:2023-10-16

C/C++中是否有一个函数可以去除非动态数据,类似于动态分配内存的函数free();

我尝试使用函数free(i);,但编译器报告了一个错误:

invalid conversion from 'int' to 'void*' [-fpermissive]

在我使用free(&i);的下面的代码中,编译器没有报告错误,但也没有释放内存。

#include<iostream>
#include<stdlib.h>
int main()
{
    int i, n;
    cin >> n;
    for(i = 0; i < n; i++);
    cout << i << endl;
    free(&i);
    cout << i << endl;
    return 0;
}

对于输入15,输出为:

15
15
Process returned 0 (0x0)   execution time : 11.068 s
Press any key to continue.

我收到了编译器的警告:

warning: attempt to free a non-heap object 'i' [-Wfree-nonheap-object]

我将在这里的高级答案集中添加一个初学者级别的答案,以防真正的新手偶然发现问题:

您不需要释放堆栈上的数据结构,—正如重复数据删除程序所指出的—也不允许这样做除某些特殊情况外,所有未动态分配的数据(例如通过new)都由编译器放置在堆栈上。堆栈是程序在运行时的一个内存部分,它随着每次函数调用而增长,并随着每次函数退出而收缩。它被划分为所谓的堆栈帧,提供函数的本地内存。在编译时,编译器会发现一个函数需要多少内存——在您的例子中,对于两个4字节的整数,这将是8字节(假设您正在编译到32位目标)——并生成指令,创建一个足够大的堆栈帧,以便在调用函数时所有局部变量都可以驻留在其中。不过,有一种方法可以告诉编译器,你只需要在函数内的有限时间内使用一个变量:Scopes,它是由大括号创建的,正如niklasfi所指出的那样。一个例子:

int foo() {
    int outerScopeVariable = 5;
    {
        int innerScopeVariableA = 8;
    }
    {
        int innerScopeVariableB = 20;
    }
}

变量innerScopeVariableA将只"存在"在它周围的大括号中。在高级上,这意味着你不能在声明它的{}块之外的作用域中引用它,对于块末尾的类,调用对象的析构函数。在低级别上,编译器知道在块的末尾不再需要为innerScopeVariableA保留的内存。因为它是堆栈内存,尽管它不能像释放动态内存那样释放它。请记住,堆栈内存只在函数结束时被丢弃。但它所能做的是将innerScopeVariableA的内存重新用于innerScopeVariableB。因此,对于foo,一个优化编译器实际上只需要8字节的堆栈内存。

有关堆栈和堆(这是分配动态内存的地方)的更多信息,您可以看看这个问题:堆栈和堆是什么以及在哪里。可以在这里找到对堆栈的深入讨论:http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/


编辑:如果在堆栈上放置大数据结构,您实际上可以观察到这种堆栈内存重用。运行以下用g++4.8.1编译的代码,您的输出是123987

#include <iostream>
using namespace std;
void foo() {
    {
        int a[1024];
        a[0] = 123987;
    }
    {
        int b[1024];
        cout << b[0] << endl;
    }
}
int main() {
    foo();
    return 0;
}

看看二进制g++在堆栈上为foo保留了4136个字节,其中4096个字节对于一个包含1024个元素的整数数组是必需的(我为32位目标编译过)。这一点点额外的空间可能与内存对齐有关。您也可以通过打印内存地址来观察这种效果,下面是一个使用在线编译器的示例:http://codepad.org/r5S1hvtV。

始终使用与分配方法互补的解除分配方法
对于堆栈变量,这意味着退出块,甚至可能退出函数
否则,您将得到未定义的行为,因此一切都可能发生。

在您的示例中,free()似乎要么是

  • 接受堆栈指针并破坏堆栈和堆管理结构(可能导致后续的进一步破坏)
  • 或者它检查传递的指针是否已经由相应的分配器返回,然后什么也不做

对于调试来说,嘈杂的崩溃或至少明显的不当行为是最好的,但不能保证。

您可以将代码放在括号{}中。任何对象都位于堆栈上,直到周围的括号闭合为止。例如

int b;
{
 int a;
} // a gets destroyed, b is still alive

正如重复数据删除程序所说,通常情况下,您不需要像编译器那样负责变量的销毁。

我不会手动调用析构函数,因为除非使用重载形式的运算符new()构造对象,否则这是一种糟糕的做法,除非使用std::nothrow重载。

去除堆栈内存的一个常见习惯用法(从技术上讲,这可以作为你问题的答案)是将其与默认构建的临时进行交换

MyType t1; 
std::swap(t1, MyType());

第二行用临时实例交换实例,因此原始实例在该行被销毁。

现在,您仍然在堆栈中留下一个实例,因此有两种情况,这将具有的意义

  • 您希望在该行调用t1的析构函数(就好像去掉堆栈对象一样)
  • 默认构造的类型要小得多,因此您希望清除并最小化