就像我们处理宏一样:
#undef SOMEMACRO
我们能否在C语言中取消声明或删除变量,以便节省大量的内存?
我知道malloc()
和free()
,但我希望完全删除变量,这样如果我使用printf("%d", a);
就应该会出错。
test.c:4:14: error: ‘a’ undeclared (first use in this function)
就像我们处理宏一样:
#undef SOMEMACRO
我们能否在C语言中取消声明或删除变量,以便节省大量的内存?
我知道malloc()
和free()
,但我希望完全删除变量,这样如果我使用printf("%d", a);
就应该会出错。
test.c:4:14: error: ‘a’ undeclared (first use in this function)
void foo() {
// some codes
// ...
{ // create an extra minimum scope where a is needed
int a;
}
// a doesn't exist here
}
void foo(void) { { char a[1024 * 1024 * 4] = {0}; } { char b[1024 * 1024 * 4] = {0}; } }
可以正常工作,而 void foo(void) { char a[1024 * 1024 * 4] = {0}; char b[1024 * 1024 * 4] = {0}; }
就行不通。 - alk这并非对问题的直接回答,但可以澄清为什么该问题没有适当的答案以及为什么在C语言中“删除”变量是不可能的。
第一点:什么是变量?
变量是程序员给一个内存空间分配一个名称的方式。这很重要,因为这意味着变量不必占用任何实际空间!只要编译器有一种跟踪相关内存的方法,定义的变量可以被转换成许多方式之一来不占用任何空间。
例如:const int i = 10;
编译器可以轻松地选择将所有 i
的实例替换为立即值。在这种情况下,i
将不占用任何数据存储器(取决于架构,它可能会增加代码大小)。或者,编译器可以将值存储在寄存器中,此时也不会使用堆栈或堆空间。在运行时不存在的标签“未定义”是没有意义的。
第二点:变量存储在哪里?
在第一点之后,您已经了解到这不是一个容易回答的问题,因为编译器可以自由地进行任何操作而不会破坏您的逻辑,但一般来说,变量存储在堆栈上。堆栈的工作原理对于您的问题非常重要。 当调用函数时,机器将CPU指令指针的当前位置和当前堆栈指针推入堆栈中,将堆栈指针替换为堆栈上的下一个位置。然后跳转到被调用的函数代码。
这个函数知道它有多少个变量以及它们需要多少空间,因此它移动框架指针以捕获可以占用所有函数变量的帧,然后只使用堆栈。为了简化事情,该函数从一开始就捕获足够的空间来容纳其所有变量,并且每个变量都有从函数堆栈帧*开始的明确定义的偏移量。变量也是相继存储的。
虽然在执行操作后您可以操纵帧指针,但这样做将非常昂贵且大多数情况下是无意义的 - 运行代码仅使用最后一个堆栈帧,并且如果需要可以占用所有剩余的堆栈(堆栈在线程启动时分配),因此“释放”变量几乎没有什么好处。从堆栈帧中间释放变量需要进行碎片整理操作,这将非常耗费CPU并且恢复少量内存是没有意义的。要点#3:让编译器完成其工作
这里的最后一个问题是简单的事实:编译器可以比您更好地优化程序。在需要时,编译器可以检测变量范围和重叠的内存,以减少程序的内存消耗(-O3编译标志)。您不需要“释放”变量,因为编译器可以在您不知道的情况下完成。
这是为了补充之前所说的关于变量太小而无法实现您所要求的机制的所有内容。
* 支持动态大小数组的语言可以修改堆栈帧,在计算出数组大小后仅为该数组分配空间。
无论是在 C 还是在绝大多数编程语言中,都没有办法做到这一点,至少在我所了解的所有编程语言中都是如此。
而且你也不会节省很多内存。如果你这样做,你将节省的内存量微不足道。非常小。不值得谈论。
用这种方式清理变量的机制可能占用的内存比你要清除的变量还要多。
回收单个变量的代码调用本身所占空间也会比变量本身更大。
因此,即使有一个神奇的方法 purge()
可以清除变量,purge()
的实现大小也比你希望通过清除程序变量来恢复的任何内存量更大,而且在 int a; purge(a);
中,对 purge()
的调用所占空间也比 a
本身还要大。
这是因为你所说的变量非常小。你提供的 printf("%d", a);
示例表明你在考虑以某种方式回收单个 int
变量所占用的内存。即使有一种方法可以做到这一点,你也只能节省大约 4 字节左右。这些变量所占用的总内存非常小,因为它直接取决于你作为程序员手动输入声明变量的数量。在键盘上无意识地声明变量数年之后,你才可能声明出占用值得一提的内存量的 int
变量。
你可以使用代码块({ }
)和尽可能晚地定义变量来限制它存在的范围。
但是,除非变量的地址被取出,否则这样做对生成的代码没有任何影响,因为编译器确定变量值所需保留的作用域并不会受到重大影响。
如果变量的地址被取出,则逃逸分析失败,这主要是由于内联屏障(如独立编译或允许语义插入)而导致的,这会使编译器认为必须将其保持活动状态直到块结束。这很少有影响(不用担心一些 int
变量,通常只需要多几行代码就能保持其活动状态),但最好记住,在极少数情况下可能会有影响。
void foo() {
// some codes
// ...
{ // create an extra minimum scope where a is needed
int a;
}
// a doesn't exist here
}