C - 通过const声明访问非const

时间:2011-11-08 14:31:05

标签: c const extern

是否通过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

  

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

因此,未定义的行为。

5 个答案:

答案 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将被编译为avolatile 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最初看到的输出。 我会查看标准版,如果/当我发现任何新内容时添加它。

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