如何在C中取消声明(删除)变量?

时间:2017-07-29 09:04:23

标签: c

就像我们使用宏一样:

#undef SOMEMACRO 

我们还可以取消声明删除 C中的变量,以便我们可以节省大量内存吗?

我知道malloc()free(),但我想彻底删除变量,这样如果我使用printf("%d", a);我就会收到错误

test.c:4:14: error: ‘a’ undeclared (first use in this function)

5 个答案:

答案 0 :(得分:37)

不,但您可以创建小的最小范围来实现此目的,因为范围退出时会销毁所有范围的局部变量。像这样:

void foo() {
    // some codes
    // ...
    {    // create an extra minimum scope where a is needed
        int a;
    }
    // a doesn't exist here
}

答案 1 :(得分:23)

这不是问题的直接答案,但它可能带来一些顺序和理解为什么这个问题没有正确答案以及为什么在C中“删除”变量是不可能的。

第1点是什么变量?

变量是程序员为内存空间分配名称的一种方式。这很重要,因为这意味着变量不必占用任何实际空间!只要编译器有办法跟踪有问题的内存,定义的变量就可以通过多种方式进行转换,根本不占用任何空间。 考虑:const int i = 10;编译器可以轻松选择将i的所有实例替换为立即值。在这种情况下,i将占用0个数据内存(取决于架构,它可能会增加代码大小)。或者,编译器可以将值存储在寄存器中,并且不再使用堆栈或堆空间。 “取消定义”主要存在于代码中且不一定存在于运行时的标签是没有意义的。

第2点?变量存储在哪里?

在第1点之后你已经明白这不是一个容易回答的问题,因为编译器可以在不破坏你的逻辑的情况下做任何想做的事情,但一般来说,变量存储在堆栈中。堆栈的工作原理对您的问题非常重要。 当一个函数被调用时,机器获取CPU指令指针和当前堆栈指针的当前位置,并将它们推入堆栈,将堆栈指针替换为堆栈上的下一个位置。然后它跳转到被调用函数的代码中。

该函数知道它有多少变量以及它们需要多少空间,因此它移动帧指针以捕获可占用所有函数变量的帧,然后只使用堆栈。为简化起见,函数从一开始就为其所有变量捕获足够的空间,并且每个变量都具有从函数堆栈帧*开头的明确定义的偏移量。变量也一个接一个地存储。 虽然你可以在这个动作之后操纵帧指针,但是它太昂贵而且大多没有意义 - 运行代码只使用最后一个堆栈帧,并且如果需要可以占用所有剩余堆栈(堆栈在线程启动时分配)所以“释放”变量带来的好处很少。从堆栈帧的中间释放变量将需要碎片整理操作,这将是非常昂贵的CPU并且无法恢复几个字节的内存。

第3点:让编译器完成工作

这里的最后一个问题是一个简单的事实,即编译器可以在优化程序方面做得比你可能做得更好。鉴于需要,编译器可以检测变量范围和重叠内存,这些内存无法同时访问以减少程序内存消耗(-O3编译标志)。 您无需“释放”变量,因为无论如何编译器都可以在您不知情的情况下执行此操作。

这是为了补充我之前所说的关于变量太小而无关紧要的事实以及没有机制来实现你所要求的事实。

*支持动态大小数组的语言可能会改变堆栈帧,以便在计算数组大小后才为该数组分配空间。

答案 2 :(得分:16)

在C语言和绝大多数编程语言中都无法做到这一点,当然在我所知道的所有编程语言中都是如此。

你不会拯​​救“大量记忆”。如果你做了这样的事情你将节省的内存量将是微不足道的。小。不值得谈论。

以这种方式促进变量清除的机制可能比你要清除的变量占用更多的内存。

调用回收单个变量代码的代码也会占用比变量本身更多的空间。

因此,如果存在一个清除变量的魔术方法purge(),那么purge()的实现不仅会大于您希望通过清除程序中的变量来回收的任何内存量,而且,在int a; purge(a);中,对purge()的调用会占用比a本身更多的空间。

那是因为你所谈论的变量非常小。您提供的printf("%d", a);示例表明您正在考虑以某种方式回收单个int变量占用的内存。即使有办法做到这一点,你将保存4字节的顺序。这些变量占用的内存总量非常小,因为它是程序员通过手工输入声明声明的变量数量的直接函数。在你声明一些int变量占据一定数量的内存之前,在键盘上打字需要多年才能做什么,只能盲目地声明变量。

答案 3 :(得分:5)

好吧,您可以使用块({ })并尽可能晚地定义变量,以限制其存在的范围。

但是除非采用变量的地址,否则对所有生成的代码的影响 no ,因为编译器确定了必须保留的范围变量的值不会受到显着影响。

如果采用变量的地址,则逃逸分析的失败(主要是由于内联障碍,如单独编译或允许语义插入)可能使编译器认为它必须使其保持活动直到块中的后期而不是严格的必要。这很少有意义(不要担心少数int,并且最常见的是保持活着的几行代码是微不足道的),但最好还是记住它可能很重要的罕见情况

答案 4 :(得分:0)

如果你担心堆栈中的内存很少,那么你可能也会对了解编译器的细节感兴趣。你需要在编译时找出它的功能。堆栈框架的实际形状未由C语言指定。由编译器来决定。以currently accepted answer

为例
void foo() {
    // some codes
    // ...
    {    // create an extra minimum scope where a is needed
        int a;
    }
    // a doesn't exist here
}

可能会也可能不会影响该功能的内存使用量。如果你在gcc或Visual Studio等主流编译器中这样做,你会发现它们针对速度而不是堆栈大小进行了优化,因此它们在函数开始时预先分配了所需的所有堆栈空间。他们将使用您的范围和变量使用分析来进行分析以找出所需的最小预分配,但这些算法确实不会受到额外范围的影响。他们已经比那更聪明了。

其他编译器,尤其是嵌入式平台的编译器,可能会以不同方式分配堆栈帧。在这些平台上,这样的范围可能是您需要的技巧。你怎么说出差异?唯一的选择是:

  • 阅读文档
  • 试一试,看看哪些方法有效

另外,请确保您了解问题的确切性质。我参与了一个特定的嵌入式项目,该项目避开了所有的堆栈,除了返回值和一些int s。当我向高级开发人员强调这种愚蠢时,他们解释说,在这个特定的应用程序中,堆栈空间比全局分配变量的空间更重要。他们有一个过程,他们必须通过证明系统按预期运行,如果他们事先分配所有内容并避免递归,这个过程对他们来说要容易得多。我保证除非你第一次知道你所解决的问题的确切性质,否则你永远不会得到这样一个错综复杂的解决方案。

作为您可以看到的另一种解决方案,您始终可以构建自己的堆栈帧。构造结构的联合,其中每个结构包含一个堆栈框架的变量。然后自己跟踪它们。您还可以查看alloca之类的函数,如果您的编译器支持它,它可以允许在函数调用期间增加堆栈帧。

结构联盟会起作用吗?试试吧。答案取决于编译器。如果所有变量都存储在特定设备的内存中,则此方法可能会最大限度地减少堆栈使用。但是,它也可能使寄存器着色算法大大混淆,导致堆栈使用量增加!试着看看它是怎么回事!