在 C 中初始化变量时,编译器是否将初始值存储在可执行文件中

When initializing variables in C does the compiler store the intial value in the executable

Initializing variables in C 我想知道编译器是否会在 运行 时加载代码时已经设置了值或者它是否必须进行显式汇编调用来设置它的初始值?

第一个会稍微更有效率,因为您不会进行第二个 CPU 调用只是为了设置值。

例如

void foo() {
  int c = 1234;
}

编译器不需要来完成其中任何一个。只要程序的行为保持不变,它几乎可以为所欲为。

特别是在使用优化时,可能会发生疯狂的事情。在大量优化后查看汇编代码至少可以说是令人困惑。

在您的示例中,常量 1234 和变量 c 都将被优化掉,因为它们未被使用。

首先,对'compiler'这个词有一个共同的理解很重要,否则我们会end-up争论不休

简单来说,

a compiler is a computer program that translates computer code written in one programming language (the source language) into another programming language (the target language). The name compiler is primarily used for programs that translate source code from a high-level programming language to a lower level language (e.g., assembly language, object code, or machine code) to create an executable program.

(参考:Wikipedia

有了这个共识,现在让我们找到问题的答案:对于任何类型的变量,答案都是 'yes, the final code contains explicit assembly call to set it to an initial value'。之所以如此,是因为最终变量要么存储在某个内存位置,要么它们位于某些 CPU 寄存器中,以防变量数量少到可以将变量容纳到某些 CPU 寄存器中,例如作为你的代码片段 运行 让我们说大多数现代服务器(旁注:不同的系统有不同数量的寄存器)。

对于存储在寄存器中的变量,必须有一种 mov(或等效)指令来将初始值加载到寄存器中。如果没有这样的指令,分配的寄存器将无法分配预期的值。

对于存储在内存中的变量,根据体系结构和编译器效率,必须以某种方式将 init 值推送到 given/assigned 地址,这至少需要几个 asm 指令。

这是否回答了您的问题?

如果它是具有 静态生命周期 的变量,它通常会成为可执行文件静态映像的一部分,与其他变量一起得到 memcpyed静态已知数据,在进程 started/loaded.

时进入进程分配的内存
void take_ptr(int*);

void static_lifetime_var(void)
{
    static int c = 1234;
    take_ptr(&c);
}

来自 gcc 的 x86-64 程序集 -Os:

static_lifetime_var:
        mov     edi, OFFSET FLAT:c.1910
        jmp     take_ptr
c.1910:
        .long   1234

如果未使用,它通常会消失:

void unused(void)
{
    int c = 1234;
}

来自 gcc 的 x86-64 程序集 -Os:

unused:
        ret

如果使用它,可能不需要将它放入函数的框架(它的局部变量在堆栈上)——可能直接将它嵌入到汇编指令中,或者"use it as an immediate" :

void take_int(int);

void used_as_an_immediate(int d) 
{
  int c = 1234;
  take_int(c*d);
}

来自 gcc 的 x86-64 程序集 -Os:

used_as_an_immediate:
        imul    edi, edi, 1234
        jmp     take_int

如果用作真正的本地,则需要加载到stack-allocated space:

void take_ptr(int*);

void used(int d)
{
  int  c = 1234;
  take_ptr(&c);
}

来自 gcc 的 x86-64 程序集 -Os:

used:
        sub     rsp, 24
        lea     rdi, [rsp+12]
        mov     DWORD PTR [rsp+12], 1234
        call    take_ptr
        add     rsp, 24
        ret

思考这些的时候Compiler Explorer along with some basic knowledge of assembly是你的朋友

TL;DR:您的示例声明并初始化了一个自动变量。每次调用该函数时都必须对其进行初始化。所以会有一些说明来做到这一点。


作为我对 How compile time initialization of variables works internally in c? 的回答的调整副本:

该标准没有定义确切的初始化方式。这取决于您的代码开发环境和 运行。

变量的初始化方式还取决于它们的存储时间。你在文中没有提到它,你的例子是一个自动变量。 (正如评论者所指出的,这很可能被优化掉了。)

初始化的自动变量将在每次到达它们的声明时写入。编译后的程序会执行一些机器代码来实现这一点。

静态变量总是初始化,并且只在程序启动前初始化一次。

真实世界的例子:

大多数(如果不是全部)PC 系统将显式(而非零)初始化的静态变量的初始值存储在一个名为 data 的特殊部分中,该部分由系统的加载程序加载到 RAM 中。这样这些变量在程序启动之前就获得了它们的值。未显式初始化或具有 zero-like 值的静态变量放在 bss 部分,并在程序启动前由启动代码填充为零。

许多嵌入式系统的程序都在 non-volatile 内存中,无法更改。在这样的系统上,启动代码将 data 部分的初始值复制到其在 RAM 中分配的 space 中,产生类似的结果。相同的启动代码也会将 bss.

部分归零

注意 1:这些部分不必像这样命名。不过很常见。

此启动代码可能是已编译程序的一部分,也可能不是。这取决于,见上文。但是说到效率,哪个程序初始化变量并不重要。它只是必须完成。

注2:保存期限的种类较多,详见标准6.2.4章节

只要符合标准,系统可以自由实现任何其他类型的初始化,包括逐步将初始值写入其变量。