5

下面的程序编译没有错误。

#include <stdio.h>

char addr_a[8];
char addr_b[8];

unsigned long my_addr = (unsigned long)addr_b - 8;                          // PASS
// unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a;   // FAIL (error: initializer element is not constant)

int main() {
        printf("%lx\n", my_addr);
        return 0;
}

有趣unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a的是,当我设置编译器时抛出“错误:初始化元素不是常量”。

我知道全局变量只能用常量表达式初始化。我也知道可以在全局初始化器中使用的常量表达式的类型在C 标准的第 6.6p7 节中指定:

初始化器中的常量表达式允许更大的自由度。这样的常量表达式应为或评估为以下之一:

  • 算术常数表达式,
  • 一个空指针常量,
  • 地址常量,或
  • 完整对象类型的地址常量加上或减去整数常量表达式。

请注意,允许使用地址常量减去整数常量,但不允许使用地址常量减去另一个地址常量。

问题:

为什么 C 标准会限制初始化全局变量的方式?是什么阻止了 C 标准的接受unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a

你为什么要这个?

假设addr_a并分别addr_b表示该.text部分的开始和结束。程序可能想要映射.text具有 size 的部分(unsigned long)addr_b - (unsigned long)addr_atrust-firmware-a项目在 Boot Loader 阶段 2 (BL2) 中执行此操作。请参阅BL_CODE_END - BL_CODE_BASE,它在arm_bl2_setup.c中使用。

4

2 回答 2

4

具有静态存储持续时间的对象(即全局变量,加上定义为的局部变量static)只能用常量表达式初始化。

可以在此类对象的初始化程序中使用的常量表达式类型在C 标准的第 6.6p7 节中指定:

初始化器中的常量表达式允许更大的自由度。这样的常量表达式应为或评估为以下之一:

  • 算术常数表达式,
  • 一个空指针常量,
  • 地址常数,或
  • 完整对象类型的地址常量加上或减去整数常量表达式。

请注意,允许一个地址常量加上一个整数常量,但不允许一个地址常量加上另一个地址常量。

当然,这仍然不是您所拥有的,因为您将地址常量转换为整数类型。所以让我们检查一下 6.6p6,它定义了一个整数常量表达式

整数常量表达式应具有整数类型,并且应仅具有整数常量、枚举常量、字符常量、其结果为整数常量的 sizeof 表达式的操作数、_Alignof表达式和作为强制转换的直接操作数的浮点常量。整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,但作为 sizeofor_Alignof运算符的操作数的一部分除外。

本段不允许将地址常量转换为整数类型作为整数常量表达式的一部分,但显然这似乎作为扩展被支持。

于 2021-11-24T23:32:38.757 回答
0

是什么阻止了 C 标准接受 unsigned long my_addr = (unsigned long)addr_a + (unsigned long)addr_b?

根本原因是“因为为什么会有人想要那个?” 两个绝对地址相加没有意义;结果不是任何特定的地址。

因此,这是一种先有鸡还是先有蛋的事情。该语言不支持它,因为它没用,还因为现有的链接器和目标文件格式不支持这种重定位。例如,对于 x86-64 上的 ELF,请参阅psABI表 4.9 以获取支持的重定位列表,并注意没有S+S. 链接器不支持它,因为它没用,而且语言不需要支持它。

我猜最初,这些工具可能出现在语言之前(最早的 C 编译器会使用为汇编程序设计的链接器)。所以最初的工具可能不支持这一点,语言认为没有必要要求他们这样做,而且随着时间的推移,也没有人认为需要添加它。

于 2021-11-25T02:14:14.060 回答