clang/gcc无法将全局变量设置为一个地址常量减去另一个地址常量。

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时,编译器会抛出“error:initializer element is not constant”的错误。
我知道全局变量只能用常量表达式来初始化。 我也知道可以在C标准的第6.6p7节中指定可用于全局初始化程序的常量表达式类型:
更多自由度允许在初始化程序中使用常量表达式。 这样的常量表达式应为以下之一或求值为以下之一: - 算术常量表达式, - 空指针常量, - 地址常量,或 - 完整对象类型的地址常量加上或减去整数常量表达式。
请注意,允许地址常量减去整数常量,但不允许地址常量减去另一个地址常量。
问题:
为什么C标准限制了初始化全局变量的方式?是什么阻止C标准接受“unsigned long my_addr = (unsigned long)addr_b - (unsigned long)addr_a”这样的初始化方式?
你为什么想要这个?
假设addr_aaddr_b分别表示.text部分的开始和结束。程序可能想要映射.text部分,其大小为(unsigned long)addr_b - (unsigned long)addr_atrusted-firmware-a项目在Boot Loader stage 2(BL2)中执行此操作。请参见BL_CODE_END - BL_CODE_BASE,该代码用于arm_bl2_setup.c
2个回答

4

具有静态存储期(即全局变量以及定义为static的局部变量)的对象只能使用常量表达式进行初始化。

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

在初始化程序中更允许使用常量表达式。此类常量表达式应为以下之一或求值为以下之一:

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

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

尽管如此,需注意您当前情况并非完全符合要求,因为您将地址常量转换为整型。 因此,我们还需要查看6.6p6,该段落定义了整数常量表达式

整数常量表达式必须具有整数类型,且仅操作数为整数常量、枚举常量、字符常量、其结果为整数常量的sizeof表达式、_Alignof表达式和是转换操作的直接操作数的浮点常量。 在整数常量表达式中,类型转换操作符应仅将算术类型转换为整数类型,除非作为sizeof_Alignof操作符的操作数的一部分。

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


这段代码不允许将地址常量转换为整数类型。6.3.2.3p6 (C11)是否适用?“任何指针类型都可以转换为整数类型...” - Andrew Henle
1
@AndrewHenle 这适用于一般情况,但我认为这种情况增加了限制。 - dbush
我很好奇为什么C标准限制了使用静态存储期初始化对象的方式。这可能是因为ELF格式的限制吗?也许重定位条目无法保存表示地址常量加上另一个地址常量的信息? - Jorge
1
@Jorge:是的,基本上是这样的。例如,对于x86-64,请参见[psABI](https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf),表4.9,并注意任何“S + S”选项的缺失。严格来说,我想C语言先有了这个限制(该语言早在ELF之前就存在了),但可能在早期,语言和现有工具都仅限于那些具有最清晰实际用途的重定位,因此迄今为止,语言和工具都没有太多动机支持不受其他支持的内容。 - Nate Eldredge
3
@jorge:C标准不是基于ELF的。但我猜想很少有链接器可以处理表示两个地址之和的重定位项;这种东西没有明显的用途。因此,如果C委员会没有准备强制实现创建可以处理这样事情的链接器,那也就不足为奇了。 - rici

0
C标准为什么不接受unsigned long my_addr = (unsigned long)addr_a + (unsigned long)addr_b?
根本原因是“为什么有人想要这样做呢?”将两个绝对地址相加没有意义,结果也不是特定的地址。
这是一种鸡生蛋或蛋生鸡的情况。语言不支持它是因为它是无用的,同时现有的链接器和目标文件格式也不支持这种重定位。例如,在x86-64上的ELF中,请参见psABI表4.9以获取支持的重定位列表,并注意没有S+S。链接器不支持它是因为它是无用的,并且语言不要求支持它。
我猜最初工具可能先于语言出现(最早的C编译器可能使用为汇编程序设计的链接器)。因此,最初的工具可能不支持此功能,语言也没有必要要求它们这样做,随着时间的推移,两者都没有看到需要添加它的必要性。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接