C – 通过const声明访问非const

是通过C标准允许的const声明访问非const对象吗? 例如,以下代码保证在符合标准的平台上编译和输出23和42?

翻译单位A:

 int a = 23; void foo(void) { a = 42; } 

翻译单位B:

 #include  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段):

如果尝试通过使用具有非const限定类型的左值来修改使用const限定类型定义的对象,则行为未定义。

但在上面的例子中,对象没有定义为const (只是声明)。

UPDATE

我终于在ISO / IEC 9899:1999中找到了它。

6.2.7,2

引用同一对象或函数的所有声明都应具有兼容类型; 否则,行为未定义。

6.7.3,9

要使两种合格类型兼容,两者都应具有相同类型的兼容类型; […]

所以,它未定义的行为。

TU A包含a的(唯一)定义。 所以真的是一个非const对象,它可以从A中的函数访问,没有任何问题。

我很确定TU B会调用未定义的行为,因为它声明的a与定义不一致。 到目前为止,我发现最好的报价是支持UB为6.7.5 / 2:

每个声明符声明一个标识符,并断言当与声明符相同forms的操作数出现在表达式中时,它指定一个函数或对象,其范围,存储持续时间和声明指定符所指示的类型。

[编辑:提问者后来在标准中找到了适当的参考,请参阅问题。]

这里,B中的声明声明a类型为volatile const int 。 实际上该对象没有(限定)类型volatile const int ,它有(限定)类型int 。 违反语义的是UB。

在实践中会发生的事情是,TU A将被编译为好像是非const。 TU B将被编译为好像是一个volatile const int ,这意味着它根本不会缓存a的值。 因此,如果链接器没有注意到并且反对不匹配的类型,我希望它能够工作,因为我没有立即看到TU B如何可能发出错误的代码。 然而,我缺乏想象力与保证行为不同。

AFAIK,标准中没有任何内容表明文件范围内的volatile对象不能存储在与其他对象完全不同的存储库中,这提供了不同的指令来读取它们。 实现仍然必须能够通过例如volatile指针读取普通对象,因此假设例如“正常”加载指令对“特殊”对象起作用,并且当通过指向a的指针读取时使用它。挥发性合格的类型。 但是如果(作为优化)实现发出了特殊对象的特殊指令,并且特殊指令对普通对象不起作用,那么繁荣。 我认为这是程序员的错,虽然我承认我只是在2分钟前发明了这个实现,所以我不能完全相信它符合。

在B转换单元中, const只会禁止修改B转换单元本身内的变量。

从外部(其他翻译单位)修改该值将反映您在B中看到的值。

这更像是一个链接器问题,而不是语言问题。 在合并编译的翻译单元时,链接器可以根据符号的不同限定条件(如果目标文件中存在此类信息)自由皱眉。

但请注意,如果它是相反的方式(A中的const int a = 23和B中的extern int a ),在尝试修改B时可能会遇到内存访问冲突,因为可以放置a在进程的只读区域中,通常直接从可执行文件的.rodata部分映射。

具有初始化的声明是定义,因此您的对象确实不是const限定对象,并且foo具有修改它的所有权限。

在B中,您提供对具有附加const限定条件的对象的访问权限。 由于类型( const限定版本和非限定版本)具有相同的对象表示,因此通过该标识符的读取访问权限是有效的。

但是,你的第二个版本有问题。 由于您没有将B版本的版本限定为volatile您无法保证看到修改版本。 允许编译器优化并重用他可能保存在寄存器中的先前值。

将其声明为const意味着该实例被定义为const。 你不能从not-const访问它。 大多数编译器都不会允许它,标准说也不允许这样做。

FWIW:在H&S5中编写(第4.4.3节“类型限定符”,第89页):“在需要值而不是指示符的上下文中使用时,将从类型中删除限定符。” 因此,当有人试图将某些内容写入变量时, const只会产生影响。 在这种情况下, printf使用a作为右值,并且添加的volatile (不必要的IMHO)使程序重新读取变量,所以我想说,程序需要生成OP最初看到的输出,所有平台/编译器。 我会看看标准版,如果/当我发现任何新内容时添加它。

编辑:我在标准中找不到任何明确的解决方案(我使用了C1X的最新草案 ),因为对链接器行为的所有引用都集中在名称相同的上。 外部声明的类型限定符似乎没有被涵盖。 也许我们应该把这个问题提交给C标准委员会。