为什么按位-或不会导致一个常量表达式,但是加法会

时间:2019-10-22 01:38:44

标签: c compilation linker

在我的一个C文件中,我声明了一个数组foo。然后,我将该变量的地址分配为整数类型,并想用3对其进行位掩码以设置最低的两位。但是,位掩码在编译期间失败,但是添加+3似乎可以工作。为什么?

uint64_t foo[1];
uint64_t bar = (uint64_t)foo | 3;

此操作失败,并显示以下信息:

main.c:6:16: error: initializer element is not constant
 uint64_t bar = (uint64_t)foo | 3;

但这可行:

uint64_t foo[1];
uint64_t bar = (uint64_t)foo + 3;

据我了解,foo的位置在编译时未知,因为它是全局的(将在.data或.bss部分中)。但是,在重定位部分中会添加一个条目,以便链接器可以在链接时修补地址。

它如何处理按位或或加法?为什么一个起作用而另一个却不起作用?

3 个答案:

答案 0 :(得分:2)

静态对象的初始值必须是常量表达式或字符串文字。 (C 2018 6.7.9 3:“具有静态或线程存储持续时间的对象的初始化程序中的所有表达式都应为常量表达式或字符串文字。”)

6.6 7指定初始化程序的常量表达式形式:

  

在初始值设定项中,常量表达式可以更大的自由度。这样的常数表达式应为以下值之一或评估为以下值之一:

     

-算术常数表达式,

     

-空指针常量,

     

-地址常数,或

     

-完整对象类型加上或减去整数常量表达式的地址常量。

考虑uint64_t bar = (uint64_t)foo + 3;foo名义上是先前声明的静态数组,该数组会自动转换为指向其第一个元素的指针。这相当于一个地址常量(6.6 9:“一个地址常量是…指向左值的指针,该指针指定了静态存储持续时间的对象,”但是,它强制转换为uint64_t,不再符合地址常量,地址常量加上或减去常量表达式或空指针常量的条件。

是算术常数表达式吗? 6.6 8排除了它:

  

…算术常数表达式中的强制转换运算符只能将算术类型转换为算术类型,…

因此,(uint64_t)foo + 3不符合C标准要求的任何形式的常量表达式。但是,6.6 10表示:

  

实现可以接受其他形式的常量表达式。

因此C实现可以接受(uint64_t) foo + 3(uint64_t) foo | 3作为常量表达式。那么我们的问题是,为什么您的C实现接受前者却不接受后者。

链接器和对象模块格式的一个共同特征是,对象模块可以记录某些表达式的占位符,并且链接器可以评估这些表达式并将占位符替换为计算值。此功能的主要目的是允许程序中的代码引用数据或其他代码中的位置,这些位置的位置在编译过程中并不完全清楚,但可以确定(至少相对于某些基础语言而言)参考点)。

数据或代码中的位置是相对于对象模块中定义的符号(名称)(或相对于节或段的开头)进行测量的。因此,实际上可以将位置描述为“例程栏开始之后的34个字节”或“对象baz开始之后的8个字节”。因此,对象模块支持由位移和符号名称组成的占位符。链接器为符号分配地址后,它将检查每个占位符,将位移添加到分配的地址,然后将占位符替换为计算结果。

尽管进行了uint64_t强制转换,看来您的编译器仍能够识别出(uint64_t) foo仍然是foo的地址,因此可以实现(uint64_t) foo + 3定期使用这些占位符之一。

相反,在这些占位符中不支持按位OR运算符,因此编译器无法实现(uint64_t) foo | 3。它无法评估表达式本身(因为它不知道foo的最终地址),并且无法为表达式编写占位符。因此它不接受将此作为常量表达式。

答案 1 :(得分:1)

当你说

sometype *p = f(x);

其中p是全局变量(或具有静态持续时间的变量),而f(x)不是实际的函数调用,而是一些编译时操作序列,涉及另一个符号的地址{ {1}}直到链接时才知道,编译器显然无法立即计算初始值。实际上,它发出一个汇编语言指令,该指令使汇编程序构造一个重定位记录,一旦知道符号x的最终位置,链接器便会计算f(x)

因此x(实际上无论它的操作顺序)必须实际上是一个链接程序知道如何评估的函数(并且有一个重定位记录,并在必要时提供汇编语言指令)对于)。尽管传统的链接器擅长执行加法和减法(因为它们一直都在执行),但它们不一定知道如何执行其他种类的算法。

因此,所有这些的结果是,在构造指针常量时,还有一些其他规则可以执行哪种算术运算。

今天早上我很着急,没有时间深入研究标准,但是我很确定在初始化一个a时,在某处有一个句子指出了对常量表达式的其他限制。指针,您只能使用一个地址加上或减去一个整数常量表达式(因为这是C标准愿意假设链接程序将知道该怎么做的全部内容)。

您的问题还有一个额外的麻烦,您实际上不是在初始化指针变量,而是在初始化整数。在那种情况下,实际上,您会遇到两全其美的情况:要么根本不允许这样做,要么如果编译器允许您这样做,则右边的初始化程序(因为它涉及地址/指针)是如上所述,限于构造指针常量时可以执行的算术类型。您不必在运行时执行整数表达式中可以摆脱的任意运算(也许会混淆转换)。

答案 2 :(得分:0)

根据标准,将指针强制转换为整数类型的结果不是常量表达式。因此,您的两个示例都可能被合格的编译器拒绝。

但是有C11 6.6 / 10条款:

  

实现可以接受其他形式的常量表达式。

不幸的是,这意味着任何特定的编译器都不能接受您的一个或两个示例。