【问题标题】:How are variable names stored in memory in C?变量名如何存储在 C 中的内存中?
【发布时间】:2013-01-14 18:13:29
【问题描述】:

在 C 语言中,假设您有一个名为 variable_name 的变量。假设它位于0xaaaaaaaa,在那个内存地址,你有整数123。换句话说,variable_name包含123。

我正在寻找有关“variable_name 位于0xaaaaaaaa”这一短语的说明。编译器如何识别字符串“variable_name”与该特定内存地址相关联?字符串“variable_name”是否存储在内存中的某个位置?编译器是否只是在看到 0xaaaaaaaa 时将其替换为 variable_name,如果是,它是否不必使用内存来进行替换?

【问题讨论】:

  • 不是一个完整的答案,但这可能会填补一些空白:csee.umbc.edu/~chang/cs313.s02/stack.shtml
  • 好吧,没有调试信息,变量名没有存储在内存中。如果你想了解这一点,你首先需要了解机器语言和汇编语言。

标签: c variables memory symbol-table


【解决方案1】:

编译器运行后变量名不再存在(除非在共享库中导出全局变量或调试符号等特殊情况)。整个编译行为的目的是获取源代码所代表的那些符号名称和算法,并将它们转换为本地机器指令。所以是的,如果你有一个全局的variable_name,并且编译器和链接器决定把它放在0xaaaaaaaa,那么无论它在代码中使用什么,它都会通过那个地址访问。

所以回答你的字面问题:

编译器如何识别字符串“variable_name”与该特定内存地址相关联?

工具链(编译器和链接器)共同为变量分配内存位置。跟踪所有引用是编译器的工作,链接器稍后会放入正确的地址。

字符串"variable_name" 是否存储在内存中的某个位置?

仅在编译器运行时。

编译器是否会在看到 0xaaaaaaaa 时将其替换为 0xaaaaaaaa,如果是,它是否不必使用内存来进行替换?

是的,几乎就是这样,除了链接器是一个两阶段的工作。是的,它使用内存,但它是编译器的内存,而不是程序运行时的任何东西。

一个例子可以帮助你理解。让我们试试这个程序:

int x = 12;

int main(void)
{
    return x;
}

很简单,对吧?好的。让我们拿这个程序,编译它,看看反汇编:

$ cc -Wall -Werror -Wextra -O3    example.c   -o example
$ otool -tV example
example:
(__TEXT,__text) section
_main:
0000000100000f60    pushq   %rbp
0000000100000f61    movq    %rsp,%rbp
0000000100000f64    movl    0x00000096(%rip),%eax
0000000100000f6a    popq    %rbp
0000000100000f6b    ret

看到movl 行了吗?它正在获取全局变量(在这种情况下,以指令指针相对的方式)。不再提及x

现在让我们让它更复杂一点,并添加一个局部变量:

int x = 12;

int main(void)
{  
    volatile int y = 4;
    return x + y;
}

这个程序的反汇编是:

(__TEXT,__text) section
_main:
0000000100000f60    pushq   %rbp
0000000100000f61    movq    %rsp,%rbp
0000000100000f64    movl    $0x00000004,0xfc(%rbp)
0000000100000f6b    movl    0x0000008f(%rip),%eax
0000000100000f71    addl    0xfc(%rbp),%eax
0000000100000f74    popq    %rbp
0000000100000f75    ret

现在有两条movl 指令和一条addl 指令。您可以看到第一个movl 正在初始化y,它决定将在堆栈上(基指针 - 4)。然后下一个movl 将全局x 放入寄存器eax,然后addly 添加到该值。但正如您所见,文字 xy 字符串不再存在。它们为程序员提供了便利,但计算机在执行时肯定不会关心它们。

【讨论】:

  • 非常感谢,这有助于解决问题。
  • 很好地解释了 C 是如何做到的。请注意,其他一些语言(尤其是现代“动态”或“脚本”语言)可能会维护数据的符号名称,并且它们确实在运行时使用内存来保存映射信息。
  • 这是一个很好的答案。只有一个问题:编译器在真正运行之前如何知道地址?我认为内存是动态分配的(所以地址在不同的运行中可能不同)。
  • @JacksonTale - 这在很大程度上取决于特定系统的配置方式,但在大多数常见系统上,虚拟内存意味着您的进程始终具有相同的内存逻辑视图,即使底层物理地址从跑跑。在我上面使用的示例中,变量是相对于指令指针进行寻址的,而不是绝对地址。
  • @CarlNorum 所以你的意思是,在运行时,物理真实内存地址将映射到虚拟内存地址?
【解决方案2】:

所有变量都由编译器替换。首先它们被引用替换,然后链接器放置地址而不是引用。

换句话说。编译器运行后,变量名不再可用

【讨论】:

  • First references..then addresses 是编译多个文件并稍后链接时要知道的重要一点。为此 +1。
【解决方案3】:

编译器是否只是在看到它时将 variable_name 替换为 0xaaaaaaaa

是的。

如果是这样,它不是必须使用内存来进行替换吗?

是的。但它是编译器,它编译了你的代码之后,你为什么要关心内存?

【讨论】:

    【解决方案4】:

    这就是所谓的实施细节。虽然您所描述的是我曾经使用过的所有编译器的情况,但并不一定是这种情况。 AC 编译器可以将每个变量放在哈希表中,并在运行时(或类似的东西)查找它们,事实上早期的 JavaScript 解释器正是这样做的(现在,他们进行即时编译,结果更加原始。)

    特别是对于 VC++、GCC 和 LLVM 等常见编译器:编译器通常会将变量分配给内存中的某个位置。全局或静态作用域的变量得到一个在程序运行时不会改变的固定地址,而函数内的变量得到一个stack地址——即相对于当前堆栈指针的地址,每次调用函数时都会改变。 (这是过于简单化了。)一旦函数返回,堆栈地址就会变得无效,但具有有效零开销的好处。

    一旦为变量分配了地址,就不再需要变量的名称,因此将其丢弃。根据名称的种类,名称可能在预处理时(对于宏名称)、编译时(对于静态和局部变量/函数)和链接时(对于全局变量/函数)被丢弃。如果导出符号 (使其对其他程序可见,以便他们可以访问它),名称通常会保留在“符号表”中的某个位置,确实占用了微不足道的内存和磁盘空间。

    【讨论】:

      【解决方案5】:

      C 编译器首先创建一个符号表,该表存储变量名与其在内存中的位置之间的关系。正如其他人所说,编译时,它使用此表将变量的所有实例替换为特定的内存位置。您可以在 Wikipedia 页面上找到更多信息。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-09-07
        • 2013-07-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多