在下面的代码中,变量x,y,z是在运行时分配内存时还是在编译时初始化的?
int main(void)
{
int x = 5;
static int y=5;
int z[] ={1,2,3,4};
return 0;
}
答案 0 :(得分:4)
正如评论所述,未定义变量是否在运行时初始化,即使您在注释中提供的信息是在函数范围内。只有这种行为就像虚拟机一次或多次进行初始化。
但是可以确保它们在编译时没有初始化
无论是否标准,任何真机都无法做到这一点
(我很乐意编辑我的答案并删除此声明,如果有人提出一个如何实现这一点的例子。软件模拟器和任何不基于编译器的东西,如C语言解释器,都不计算在内。)
在编译时,只能确定将初始化这些变量的值。对于您的示例中确实发生了值确定的情况,因为除了程序文本中的数字之外的其他任何地方都没有该值。
初始化(即由标识符表示的存储器片段中的那些值的“到达”)可以在不同的时间发生,但从不在编译时发生。因为在编译时还没有选择这样的存储器片。在交叉编译的情况下,它甚至可能还不存在(运行它的芯片尚未生成)。
以下初始化时间是可能的 (并没有对所有人说“通常已经完成”......):
对于您的示例,在main()
的范围内,这些选项中的任何一个都可能适用
所以,它还不知道。
注意:
我在这里加载时间和内存设置时间之间存在差异,因为内存设置时间通常与上电或复位情况有关,这有些例外,而加载要执行的程序则被认为是正常情况。登记/>
然而,从正在执行的C程序的角度来看,两者都可以被视为相同的事物,因为它是“在main()
之前”。
我在运行时(由您编写的C代码定义的程序控制)和加载/设置时间之间也有所不同,它们是C运行时环境准备代码的一部分。 C-runtime可以通过OS或(例如嵌入式环境)通过从复位向量链接的机器代码(不基于C)准备。
答案 1 :(得分:2)
这个问题没有明确的答案。初始化是在C标准用于描述程序必须如何表现的抽象机器中发生的,并且实现该机器的唯一要求是必须生成标准描述的可观察行为。对象的初始值可以在编译时生成(即使计算初始值需要复杂的计算)或在运行时(即使初始值是简单常量)或根本不生成(只要可观察的行为)该计划以某种方式产生)。此外,初始值可能出现在编码到指令中的立即值中,在目标文件描述的存储器中,在程序启动期间写入存储器时,在执行函数时写入存储器,寄存器或其他地方。对象可能在不同的时间保存在不同的位置,它可能与其他对象共享存储或与其自身的其他实例共享(如同函数递归执行时),甚至可以同时共享。
一个更有用的问题是:各种对象初始化的成本和效果是什么?同样,这不是由C标准定义的,但它具有现实世界的后果。许多对象或大型对象的初始化可能会对性能产生明显的拖累,并可能影响产品质量。
本回答的其余部分假设一个人正在使用高质量的编译器并且正在请求优化(与使用Clang或GCC的-O3
或-Os
开关一样)。
在块范围内的int x = 5
声明可能会导致5被硬编码到指令中(如果需要的话)。可能不需要初始值,因为编译器优化了x
的进一步工作,以便将初始值合并到使用x
的第一个表达式中。 (在问题中显示的代码中,根本不需要x
,因为它从未被使用过,因此编译器将完全从程序中删除它。)
static int y = 5
之类的声明可能会导致5被写入目标文件中的数据部分(并且,当链接目标文件以生成可执行文件时,数据部分将被复制或合并到可执行文件中)。同样,这可以进行优化。
如果在文件范围声明int x = 5
,则它是外部定义,并且编译器必须使其可用于其他翻译单元(其他源文件的编译),因此必须将其存储在内存中(除非编译器和其他开发人员工具能够跨翻译单元进行优化。在这种情况下,编译器可能会将5写入目标文件的数据部分。
对于小对象和简单的初始值(如这些),几乎没有任何理由关注它们何时或如何初始化,因为成本非常低廉并且没有可用的改进。
在块范围内使用int z[] = { 1, 2, 3, 4 };
时,数组足够小,编译器可能会以与上面的单个int x
相同的方式处理它,具体取决于具体情况。在阵列较大的情况下,可能会处理这种情况:
将值1,2,3和4写入目标文件中的常量数据部分。
当执行有效地达到声明时,编译器可能在堆栈上为z
分配存储空间,并将数据从常量数据部分复制到z
。 (可能很难说执行何时到达声明,因为抽象机器模型意味着C实现可以自由地重新安排许多事情。声明之前和之后的语句执行可以重新排列并混合在一起。)
但是,假设你有这样的代码:
int z[] = { 1, 2, 3, 4 };
for (int i = 0; i < 4; ++i)
z[i] = f(z[i]);
在这种情况下,编译器可以看到z
的成员在其初始值传递给f
之前没有更改。实现此操作时,编译器可能会跳过将1,2,3和4复制到为z
分配的存储中的上述步骤。相反,它可以通过读取常量数据,将每个值传递给f
并将结果写入为z
分配的存储来实现循环。在这种情况下,z
是否已初始化?该程序将初始值存储在常量数据部分中,但它从未将它们写入特别为z
分配的存储中。它稍后才会写出派生值。
但是,真正的问题是,这个成本是多少?我们必须将数据存储在常量数据部分中,我们不得不重复调用f
。假设z
有数千个元素而不是4个元素。我们可以降低这些成本吗?
如果f
是一个纯函数(仅依赖于它的参数,而不是任何全局状态),那么存储1,2,3和4是没有意义的。我们应该存储{{1} },f(1)
,f(2)
和f(3)
并在运行时跳过f(4)
的执行。如果您有一个好的编译器并且在编译器编译此代码时可以看到f
的源代码,那么编译器可能会这样做 - 它可能存储调用f
的结果而不仅仅是显式初始值值。 (即使f
不可见,编译器也可能针对已知函数执行此操作,例如f
或cos
。)
如果您正在处理一个重要的项目,您应该了解您的编译器以及它对这样的事情的处理方式。没有统一的答案,特别是随着技术的不断发展。
如果这样的初始化对您的应用程序很重要,另一种方法是编写一个计算初始值(log
,f(1)
等)的程序,并将它们写入新的源代码中。文件。在编译时,您将执行该程序,然后编译生成的源代码。
在C中,初始化成本通常不是问题,除非您有大量可以使用辅助程序预处理的数据,如上所述。在C ++中,它更像是一个问题,类的构造函数可能非常广泛。
答案 2 :(得分:1)
编译器的工作就是按照它所说的去做。你已经发布了程序
int main(void)
{
int x = 5;
static int y=5;
int z[] ={1,2,3,4};
return 0;
}
C标准允许编译器生成编译代码,该代码完全复制程序的可观察行为。
为此,任何具有优化效果的C编译器都会产生最大值,这将产生相当于
的编译代码int main()
{
}
如果您想知道特定编译器如何处理源代码,请检查生成的输出。