【问题标题】:how c handles the same variable name along different scopes?c 如何在不同的范围内处理相同的变量名?
【发布时间】:2014-02-27 11:46:17
【问题描述】:

我有以下代码

int main()
{
        static int x = 8;
        {
                static int x = 9;
        }
        printf("%d",x);
}

o/p-8

我的疑问,根据规则,静态变量只创建一次并保留在内存中。因此,如果名称为 x 的变量保留在内存中,那么我如何才能创建一个新变量。

请清除我的疑问。我浏览了谷歌,但它是如何完成的,我在 C 编程中想要什么。编译器如何识别这两个变量以及它如何存储在内存中。

T

【问题讨论】:

  • 范围。范围。范围。范围...
  • 好的,但是它在内存中的情况如何,区分两个同名的变量。
  • 为什么要投反对票。我希望这只是我错过的一个概念。

标签: c linux compiler-construction


【解决方案1】:

objdump 的反汇编输出可能会给你一些关于编译器(gcc,在这种情况下)如何处理这种情况的提示:

$ objdump -d a.out
...
000000000040050c <main>:
  40050c:   55                      push   %rbp
  40050d:   48 89 e5                mov    %rsp,%rbp
  400510:   8b 05 fa 03 20 00       mov    0x2003fa(%rip),%eax        # 600910 <x.2163>
  400516:   89 c6                   mov    %eax,%esi
  400518:   bf fc 05 40 00          mov    $0x4005fc,%edi
  40051d:   b8 00 00 00 00          mov    $0x0,%eax
  400522:   e8 b9 fe ff ff          callq  4003e0 <printf@plt>
  400527:   8b 05 e7 03 20 00       mov    0x2003e7(%rip),%eax        # 600914 <x.2162>
  40052d:   89 c6                   mov    %eax,%esi
  40052f:   bf fc 05 40 00          mov    $0x4005fc,%edi
  400534:   b8 00 00 00 00          mov    $0x0,%eax
  400539:   e8 a2 fe ff ff          callq  4003e0 <printf@plt>
  40053e:   b8 00 00 00 00          mov    $0x0,%eax
  400543:   5d                      pop    %rbp
  400544:   c3                      retq   

使用readelf,我们可以发现每个x在最终的可执行文件中都有自己的符号:

$ readelf -s a.out
...
45: 0000000000600910     4 OBJECT  LOCAL  DEFAULT   25 x.2163
46: 0000000000600914     4 OBJECT  LOCAL  DEFAULT   25 x.2162

这里是 C 代码:

int main()
{
    static int x = 8;
    {
        static int x = 9;
        printf("%d",x);
    }
    printf("%d",x);
    return 0;
}

【讨论】:

  • 你能解释一下这个程序集吗,我是新手。
  • @pradipta 抱歉,我无法真正理解那些汇编代码。但很明显,gcc 为每个 x 赋予了不同的名称。
  • 感谢您的回答。
【解决方案2】:

在内部,编译器重命名具有不同作用域的同义变量。 考虑您的文件:

// file pradipta.c
#include <stdio.h>
int main () {
  static int x = 8;
  {
     static int x = 9;
  }
   printf ("%d\n", x);
}  

然后用

编译它(在 Linux 上使用 GCC)
 gcc -fdump-tree-all -O -Wall pradipta.c -o pradipta.bin

然后你会得到很多 pradipta.c.[0-9]*t.* 文件。它们向您展示了 GCC 表示的部分(因此不完整)转储。一些内部变量可能不同但名称相同。在编译器内部,变量在内部由一些复杂的数据结构表示(GCC 用语中的 tree 节点,在 gimple 指令中使用),并且您可以拥有两个具有相同“可打印”名称的不同此类结构。

您还可以使用MELT 来探索 GCC 内部结构(或通过使用 MELT 扩展扩展 GCC 来自定义其行为)。

还可以在λ-calculus 中了解α-conversion

在实践中,避免不同嵌套范围的同义变量。它们使您的代码非常难以被人类阅读(即使编译器为您的代码赋予了非常精确和明确的含义)。 -Wall 选项要求提供所有警告,您将收到针对此类情况的警告。

【讨论】:

    【解决方案3】:

    静态值将在函数调用期间保持不变。

    但只有在同一个函数范围内很重要。大括号里面放个printf你就明白了

    int main()
    {
            static int x = 8;
            {
                    static int x = 9;
                    **printf("%d",x);**
            }
            printf("%d",x);
    }
    

    【讨论】:

    • 那如果我在 main. 上面定义它是怎么工作的?
    • 如果你在 main 上面定义它,它就像一个全局变量,但由于它是静态的,它只在当前的 .c 文件中可见。
    【解决方案4】:

    静态变量创建一次,并在变量范围内持久保存在内存中。

    当你使用时

    {
         static int x = 9;
         printf("%d",x) // would print 9 only
    }
    

    这会创建一个新的范围,x 不同于在大括号外定义的x

    范围可以用大括号、函数、文件创建

    如果静态变量在全局空间中声明,那么它在整个文件中都是持久的。

    但也要记住,局部范围变量优先于同名的全局变量。

    【讨论】:

      【解决方案5】:

      想象一下,每次打开 { 都会创建一个范围,而每次关闭 } 都会关闭。而且,对于每个新范围,变量的名称(在心理上)以范围名称作为前缀(可以与函数名称同化,或者对于示例中的匿名构造,使用泛型“scope1”、“scope2”、“scope3”序列。

      所以,基本上,你定义了两个变量,而不是一个:

      静态 int main.x = 8;

      静态 int main.scope1.x = 9;

      这两者都是静态的,但它们(对于编译器和程序的逻辑)具有不同的名称,并且作用类似于不同的变量。只有它们的缩写名称相同,“x”,这让你感到困惑,但变量实际上是不同的。他们尊重你所说的“静态”,但没有冲突。

      一句警告:在“scope1”内部,变量“main.scope1.x”隐藏了变量“main.x”。

      请记住,这种使用范围名称的“重命名”只是一个类比,而不是真实的东西。不过,我希望它有助于理解这个问题。

      【讨论】:

        【解决方案6】:

        理论上的编译器可以保留遇到的符号的堆栈。当解析一个'{'时,编译器可以把这个分隔符放入栈中,当遇到'}'符号时,它会删除最后一个'{' ... '}'区域之间的所有符号。

        当编译器解析某些符号时,它可以简单地深入研究符号堆栈以定位匹配或宣布错误。

        在解析这个文件的每个关键点,这个编译器都会有一个符号栈:

        main  main   main    main    main     main    main
              {      {       {       {        {
                     x       x       x        x
                             {       {
                                     x
        

        这种方法实际上会非常接近地映射到 c 编译器的遗留实现——​​x-es 也将被放置在现实中的堆栈中。

        【讨论】:

        • 感谢您的回答。
        猜你喜欢
        • 2015-11-22
        • 1970-01-01
        • 1970-01-01
        • 2021-07-12
        • 1970-01-01
        • 2021-06-23
        • 1970-01-01
        • 1970-01-01
        • 2011-11-09
        相关资源
        最近更新 更多