Dav*_*aux 4 c standards language-lawyer
考虑以下程序
\nextern const int foo;\nextern void blah(void);\n\nint toto(void) {\n int x = foo;\n blah();\n int y = foo;\n return x + y;\n}\n
Run Code Online (Sandbox Code Playgroud)\narm-linux-gnueabihf-gcc -std=c99 -O2 -fno-pic -S extern_const2.c
将其编译为
toto:\n @ args = 0, pretend = 0, frame = 0\n @ frame_needed = 0, uses_anonymous_args = 0\n movw r3, #:lower16:foo\n movt r3, #:upper16:foo\n push {r4, lr}\n ldr r4, [r3]\n bl blah\n lsls r0, r4, #1\n pop {r4, pc}\n
Run Code Online (Sandbox Code Playgroud)\n请注意,foo
被读取一次,然后左移 1,这意味着假设gcc
其值在函数调用期间保持不变。观察到类似的行为clang
。
我没有看到 ISO/IEC 9899:2017 标准的哪一部分明确讨论了这个假设。\xc2\xa76.7.3-12 解释了对extern volatile const
变量的假设(其值可以由硬件更改,但不能由程序分配),但这不适用于extern const
.
\xc2\xa76.7.3-6 指出程序不能分配给const
。我不太清楚这意味着外部调用不能改变这个变量。
另外,\xc2\xa76.7.3-6的含义我不太清楚。它说
\n\n\n如果尝试通过使用非 const 限定类型的左值来修改使用 const 限定类型定义的对象,则行为未定义
\n
但是,在我的示例中,我将 声明为,而不是定义foo
为const
。声明和定义之间的区别在\xc2\xa76.7-5中给出:
\n\n声明指定一组标识符的解释和属性。标识符的定义是该标识符的声明: \xe2\x80\x94 对于对象,导致为该对象保留存储;
\n
\n\n但是,在我的示例中,我将 声明为,而不是定义
\nfoo
为const
。声明和定义的区别在\xc2\xa76.7-5:\xe2\x80\xa6中给出
根据 C 2018 6.2.7 2,编译器可能会假设声明为 的对象const
也定义为:const
\n\n引用同一对象或函数的所有声明应具有兼容的类型;否则,行为是未定义的。
\n
和 6.7.3 11:
\n\n\n为了使两个合格类型兼容,两者都应具有兼容类型的相同合格版本;说明符或限定符列表中类型限定符的顺序不会影响指定的类型。
\n
(请注意,该句子没有说明非限定类型与限定类型兼容的要求是什么,除非我们包含 \xe2\x80\x9cno qualifiers\xe2\x80\x9d 作为限定类型。但是,在整个 C 标准中,兼容性都以肯定的方式进行了说明:仅当有规则声明它们兼容时,两种类型才是兼容的;没有断言它们兼容的声明的任何类型都是不兼容的。)
\n\n\n\xc2\xa76.7.3-6 指出程序不能分配给变量
\nconst
。我不太清楚这意味着外部调用不能改变这个变量。
根据上述内容,编译器可能会假设整个程序foo
都是如此const
,并且程序中没有任何内容会改变它。如果程序外部的某些东西可能会改变它,则应该声明它volatile
。