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

Posted

技术标签:

【中文标题】如何在 C 中取消声明(删除)变量?【英文标题】:How to undeclare (delete) variable in C? 【发布时间】:2018-01-05 08:21:19 【问题描述】:

就像我们使用宏一样:

#undef SOMEMACRO 

我们是否也可以取消声明删除 C中的变量,这样可以节省大量内存?

我知道malloc()free(),但我想完全删除变量,这样如果我使用printf("%d", a); 就会出错

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

【问题讨论】:

在变量周围使用紧密的范围(即一对包含一系列语句的大括号)。变量在定义它们的范围退出时被销毁(并且在进入范围之前不会创建)。否则,答案为“否”。全局变量根本无法销毁。 任何局部变量,当你离开它的作用域时,它就不再存在了。在全局范围内,除了程序终止之外别无他法 相关:***.com/questions/2759371/… 【参考方案1】:

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

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

【讨论】:

实际上,如果您以这种方式定义范围内不重叠的变量,这可能允许编译器重用堆栈变量空间。尽管即使您不这样做,编译器也很有可能进行这样的优化。 @MikeNakis - 你曾经使用过嵌入式系统吗?我曾经有一个只有 128 字节堆栈的控制器和一个在函数之间严重重叠堆栈变量的编译器。当由于函数指针的使用而无法正确计算出重叠的堆栈帧时,它会导致运行时错误。美好时光:) @MikeNakis:“编译器从来没有这样做过[重用堆栈空间],不要那样做,而且很可能永远不会”我的 GCC 确实重用了堆栈就像这里描述的那样:***.com/a/2759834/694576 刚刚测试过: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; 无效。 @MikeNakis 为范围不重叠的变量重用堆栈空间不需要调整堆栈指针。它本质上将堆栈帧的那一部分视为union 编译器不需要限制范围来重用内存寄存器。它只是使用流程图来推断非重叠的生命周期。它甚至可以在不同的时间将相同的变量放在不同的位置/寄存器中(至少在不占用地址时)。由于编译器在优化过程中经常使用SSA form,因此解耦存储位置和变量对编译器来说非常自然。【参考方案2】:

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

第 1 点什么是变量?

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

Point #2 变量存储在哪里?

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

该函数知道它有多少变量以及它们需要多少空间,因此它移动帧指针以捕获可能占用所有函数变量的帧,然后只使用堆栈。为简化起见,该函数从一开始就为所有变量捕获了足够的空间,并且每个变量都有一个从函数堆栈帧开始的明确定义的偏移量*。变量也一个接一个地存储。 虽然您可以在此操作之后操作帧指针,但成本太高且几乎毫无意义 - 正在运行的代码仅使用最后一个堆栈帧,并且可以在需要时占用所有剩余堆栈(堆栈在线程启动时分配),因此“释放”变量几乎没有什么好处。从堆栈帧的中间释放变量需要进行碎片整理操作,这将耗费大量 CPU 资源,而且对于恢复几个字节的内存毫无意义。

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

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

这是对我之前所说的所有关于变量太小而不重要以及没有机制可以实现您所要求的事实的补充。


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

【讨论】:

第 3 点特别相关;一旦不再需要一个变量,编译器就会例行地为其他变量回收堆栈空间,就像它们对寄存器所做的那样;这实际上是您在进行逆向工程时必须注意的一件事——即使在您了解堆栈上给定位置所指的局部变量之后,一旦它被覆盖,它也可能是一个完全不同的变量。 这应该是这里的第一答案 许多编译器,尤其是嵌入式应用程序,(例如 XC8)不允许更高级别的优化,除非您为高级版本付费。话虽如此,您的“让编译器完成其工作”的观点仍然可能是最好的答案。【参考方案3】:

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

而且您不会节省“大量内存”。如果您这样做,您将节省的内存量将是微不足道的。微小的。不值一提。

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

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

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

那是因为你所说的变量非常小。您提供的printf("%d", a); 示例表明您正在考虑以某种方式回收单个int 变量占用的内存。即使有办法做到这一点,你也会节省大约 4 个字节的东西。这些变量占用的内存总量非常小,因为它是作为程序员通过手动键入它们的声明来声明的变量数量的直接函数。在您声明许多占用大量内存的int 变量之前,您需要花费数年时间在键盘上打字,只是盲目地声明变量。

【讨论】:

有R,可以删除包含名称的变量。虽然它是一种脚本语言。 在 Python、Matlab 等中也有可能......否则,这个答案在甚至没有错误的领域都相当遥远。【参考方案4】:

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

但除非获取变量的地址,否则这样做不会对生成的代码产生影响根本,因为编译器决定了它必须保留的范围变量的值没有受到显着影响。

如果变量的地址被占用,转义分析的失败,主要是由于单独编译或允许语义插入等内联障碍,可以使编译器假设它必须保持它在块中的稍后时间而不是严格必要的。这很少很重要(不要担心少数ints,而且通常几行代码更长时间保持它的生命是微不足道的),但最好记住它可能很重要的罕见情况。

【讨论】:

【参考方案5】:

如果您担心堆栈上的少量内存,那么您可能也会对了解编译器的细节感兴趣。您需要了解它在编译时的作用。堆栈帧的实际形状不是由 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 等主流编译器中执行此操作,您会发现它们针对速度而不是堆栈大小进行了优化,因此它们在函数开始时预先分配了所需的所有堆栈空间。他们将使用您的范围和变量使用分析进行分析以找出所需的最小预分配,但这些算法实际上不会受到额外范围的影响。他们已经比那更聪明了。

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

阅读文档 试一试,看看有什么效果

此外,请确保您了解问题的确切性质。我在一个特定的嵌入式项目上工作,除了返回值和一些ints 之外,一切 都避开了堆栈。当我向高级开发人员询问这种愚蠢的做法时,他们解释说,在这个特定的应用程序中,堆栈空间比用于全局分配变量的空间更宝贵。他们必须通过一个过程来证明系统会按预期运行,如果他们预先分配所有内容并避免递归,这个过程对他们来说会容易得多。我保证,除非您首先知道要解决的问题的确切性质,否则您永远不会得出如此复杂的解决方案。

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

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

【讨论】:

以上是关于如何在 C 中取消声明(删除)变量?的主要内容,如果未能解决你的问题,请参考以下文章

Bash 取消声明只读变量(声明 -r var)

如何在 C 中取消设置变量以允许稍后使用具有不同数据类型的相同名称?

c 中结构体变量 如何在其他c文件中声明

如何在 C 中的 for (;;) 循环中声明多个变量?

如何在 Rails 中使用延迟作业取消预定作业?

如何删除或取消设置 Javascript 变量 [重复]