像初始化的全局变量数据放在.data节中,而单元化的全局变量放在.bss节中,编译器在目标文件的哪个部分中放置了初始化的自动变量(局部变量)数据?
答案 0 :(得分:2)
自动变量没有(也不能)在目标文件中分配位置,因为它们在声明它们的块作用域的每个活动实例中存在于一个实例中,而不是像静态存储对象那样存在于单个实例中。如果已初始化,则必须为函数的每个实例进行初始化,并且编译器可以自由发出它喜欢设置其初始值的任何代码。当值是常量表达式时,可以从程序映像中的某个部分(因此存在于对象/可执行文件中)中以memcpy
的形式完成 ,但是它通常可以执行涉及与可执行代码内联的立即操作数。
答案 1 :(得分:2)
通常,具有自动存储期限的对象(例如在函数内部定义的int x
不能)存储在对象文件中。这是因为函数可以递归地(直接或间接地)调用任意次,并且每次调用都必须存在对象的不同实例,因此在对象文件中只有一个位置(允许对象文件的各个部分)可能在程序执行期间以一种或另一种方式映射到内存)不能用作对象的内存。
在特定情况下,对象模块中的一个数据副本可用于自动存储持续时间的对象,因为当编译器可以确定函数不是递归调用时,因此只能存在该对象的一个实例一次。
但是,要实现初始化的对象,编译器确实必须提供将对象设置为其初始值的条件。程序执行期间使用的实际对象可能在堆栈上或在寄存器中,但是编译器必须设置其初始值。对于某些初始值,例如零或小的常数,编译器可能在程序执行期间“即时”创建初始值,可能使用指令中的立即操作数。 (在这种情况下,初始值有效地存储在对象模块的代码部分中。)对于不容易动态构造的常量,编译器可以将值作为数据存储在对象模块中,通常以读-仅(恒定)数据部分。当然,由于这些数据是供编译器内部使用的(它是编译器保存用于初始值的东西;它不是对象本身),因此不会用对象名称标记。编译器将使用内部名称或某个位置的数字偏移量来标识它。
此外,可以使用非恒定值来初始化自动存储持续时间的对象。当然,这些根本不存储在对象模块中。它们是在程序执行期间计算的。
以上所有内容均可能通过优化和C标准的“仿佛”规则进行修改-如果编译器可以获取所需的相同标记,则在执行过程中程序存储器中可能根本不存在自动对象。行为,例如将对象的用途折叠到其他计算中。
这意味着没有一个位置会始终存储自动存储持续时间的对象或其初始值。
答案 2 :(得分:0)
这取决于。通常,像int x = 1;
这样的局部声明将分配给一个寄存器,并编译成一条指令以将常数1加载到该寄存器中。较少地,将在堆栈上分配一个内存位置,并将值存储在堆栈中。
如果编译器(认为它)可以证明以这种方式重构程序不会改变其可观察的行为,则可以完全优化任何变量。例如,如果您编写static const int ok = 0;
,那么当您编写x = ok;
时,任何明智的编译器都只会将x
设置为常量0
。
如果使用局部变量的地址并取消引用,则编译器必须将变量置于某个内存位置。在编译时已知的常量可能会存储在文本段的只读存储器中。静态局部变量通常会与其他静态变量一起存储在数据段中。否则,它将进入堆栈。
在更复杂的情况下,例如
double matrix[4][3] = { { 1, 0, 0, -1 },
{ 0, 1, -1, 0 },
{ 9, 0, 0, 1 } };
通常,您将看到初始值存储在静态数组中,然后复制到堆栈上的存储位置,或者如果编译器可以对算法进行矢量化,则将其复制到矢量寄存器。换句话说,就好像您将初始值声明为static const
本地数组,然后将它们复制到工作副本中一样。
您可能会发现在GodBolt上测试程序的一些变体会很有帮助。
double f (const double x)
{
double matrix[2][2] = { {1, 0},
{0, 1} };
double* const begin = &matrix[0][0];
const double* const end =
begin + sizeof(matrix)/sizeof(matrix[0][0]);
for ( double* p = begin; p < end; ++p ) {
*p *= x;
}
return (matrix[0][0] * matrix[1][1]) -
(matrix[0][1] * matrix[1][0]);
}
编译器生成的代码差异很大。 GCC优化了循环和数组初始值以外的所有变量,将数组的初始值存储在地址.LC0
的静态存储器中并复制到寄存器中。 Clang生成一些向量指令,除了寄存器外,它们根本不分配任何存储空间。从理论上讲,好的静态分析器可以将此功能优化为return x*x;
。