是否通过C标准允许的const
声明访问非const
对象?
例如。是否保证在符合标准的平台上编译和输出23和42以下代码?
翻译单位A:
int a = 23;
void foo(void) { a = 42; }
翻译单位B:
#include <stdio.h>
extern volatile const int a;
void foo(void);
int main(void) {
printf("%i\n", a);
foo();
printf("%i\n", a);
return 0;
}
在ISO / IEC 9899:1999中,我刚刚找到(6.7.3,第5段):
如果尝试通过use修改使用const限定类型定义的对象 如果是非const限定类型的左值,则行为未定义。
但在上面的情况中,对象未定义为const
(但刚刚声明)。
更新
我终于在ISO / IEC 9899:1999中找到了它。
6.2.7,2
引用同一对象或函数的所有声明都应具有兼容类型; 否则,行为未定义。
6.7.3,9
要使两种合格类型兼容,两者都应具有相同的资格 兼容类型的版本; [...]
因此,是未定义的行为。
答案 0 :(得分:5)
TU A包含a
的(唯一)定义。所以a
实际上是一个非const对象,它可以从A中的函数访问,没有任何问题。
我很确定TU B会调用未定义的行为,因为它的a
声明与定义不一致。到目前为止,我发现最好的报价是支持UB为6.7.5 / 2:
每个声明者声明一个标识符,并声明当一个标识符 与声明符相同形式的操作数出现在表达式中, 它指定一个功能或对象的范围,存储持续时间, 和声明规格表示的类型。
[编辑:提问者后来在标准中找到了适当的参考,请参阅问题。]
这里,B中的声明断言a
的类型为volatile const int
。实际上,该对象没有(合格)类型volatile const int
,它具有(合格)类型int
。违反语义的是UB。
实际上会发生的事情是,TU A将被编译为好像a
是非const的。 TU B将被编译为a
为volatile const int
,这意味着它根本不会缓存a
的值。因此,如果链接器没有注意到并且反对不匹配的类型,我希望它能够工作,因为我没有立即看到TU B如何可能发出错误的代码。然而,我缺乏想象力与保证行为不同。
AFAIK,标准中没有任何内容表明文件范围内的volatile
对象不能存储在与其他对象完全不同的存储库中,这些对象提供了不同的读取指令。实现仍然必须能够通过例如volatile
指针读取普通对象,因此假设“正常”加载指令适用于“特殊”对象,并且在读取时使用它指向volatile限定类型的指针。但是如果(作为优化)实现发出了特殊对象的特殊指令,并且特殊指令没有在普通对象上工作,那么繁荣。而且我认为这是程序员的错,虽然我承认我在2分钟前才发明了这个实现,所以我不能完全相信它符合。
答案 1 :(得分:3)
在B翻译单元中,const
只会禁止修改B翻译单元内的a
变量。
从外部(其他翻译单位)修改该值将反映您在B中看到的值。
这更像是一个链接器问题,而不是语言问题。在合并编译的翻译单元时,链接器可以根据a
符号的不同限定条件(如果目标文件中存在此类信息)自由皱眉。
但是,请注意,如果是相反的方式(A中为const int a = 23
,B中为extern int a
),则在尝试修改{{1}时可能会遇到内存访问冲突来自B,因为a
可以放在进程的只读区域,通常直接从可执行文件的a
部分映射。
答案 2 :(得分:1)
具有初始化的声明是定义,因此您的对象确实不是const
限定对象,foo
具有修改它的所有权限。
在B中,您提供对具有额外const
限定条件的对象的访问权限。由于类型(const
限定版本和非限定版本)具有相同的对象表示,因此通过该标识符的读取访问权限有效。
你的第二个printf
有问题。由于您未将a
的B版本限定为volatile
,因此无法保证您看到a
的修改。允许编译器优化并重用他可能保留在寄存器中的先前值。
答案 3 :(得分:0)
将其声明为const意味着该实例被定义为const。你不能从not-const访问它。大多数编译器都不允许这样做,标准说它也不允许。
答案 4 :(得分:0)
FWIW:在H&amp; S5中写入(第4.4.3节类型限定符,第89页):
“当在需要值而不是指示符的上下文中使用时,限定符将从类型中消除。”因此,当有人试图将某些内容写入变量时,const
只会产生影响。
在这种情况下,printf
使用a
作为右值,添加的volatile
(不必要的恕我直言)使程序重新读取变量,所以我想说,程序是需要在所有平台/编译器上生成OP最初看到的输出。
我会查看标准版,如果/当我发现任何新内容时添加它。