请考虑以下计划

extern const int foo;
extern void blah(void);

int toto(void) {
  int x = foo;
  blah();
  int y = foo;
  return x + y;
}

arm-linux-gnueabihf-gcc -std=c99 -O2 -fno-pic -S extern_const2.c将其编译为

toto:
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    movw    r3, #:lower16:foo
    movt    r3, #:upper16:foo
    push    {r4, lr}
    ldr r4, [r3]
    bl  blah
    lsls    r0, r4, #1
    pop {r4, pc}

注意,foo被读取once,然后被左移1,这意味着gcc假定它的值在函数调用中保持不变.clang也观察到了类似的行为.

我看不出ISO/IEC 9899:2017标准的哪一部分明确讨论了这一假设.§6.7.3-12解释了关于extern volatile const变量的假设(它的值可以由硬件改变,但不能由程序赋值),但这不适用于extern const.

§6.7.3-6规定程序不能赋值为const变量.我不太清楚这是否意味着外部调用不能改变这个变量.

此外,第6.7.3-6节的含义对我来说也不是很清楚.上面写着

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

然而,在我的示例中,我是declared,而不是defined,fooconst.声明和定义的区别在§6.7-5中给出:

声明指定了一组标识符的解释和属性.识别符的定义是对该识别符的声明:-对于对象,使得为该对象保留存储;

推荐答案

然而,在我的示例中,我是declared,而不是defined,fooconst.声明和定义的区别在§6.7-5:…中给出

编译器可以假定声明为const的对象也被定义为const,根据C 2018 6.2.7 2:

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

和6.7.3 11:

对于要兼容的两个限定类型,两者都应具有兼容类型的相同限定版本;类型限定符在说明符或限定符列表中的顺序不影响指定的类型.

(请注意,这句话没有说明非限定类型与限定类型兼容的要求是什么,除非我们将"无限定符"作为限定类型包括在内.然而,兼容性规则在整个C标准中都是肯定的:两种类型只有在有规则声明它们是兼容的时才是兼容的;任何没有声明它们是合格的类型都是不兼容的.)

§6.7.3-6规定程序不能赋值为const变量.我不太清楚这是否意味着外部调用不能改变这个变量.

根据上面的说明,编译器可以假设foo在整个程序中是const,并且程序中没有任何东西改变它.如果程序之外的东西可以改变它,那么它应该被声明为volatile.

C++相关问答推荐

有什么方法可以检测SunOS上的SparcWorks吗?

为什么已经设置的值在C中被重置为for循环条件中的新值?

字符数组,字符指针,在一种情况下工作,但在另一种情况下不工作?

为什么我得到更多的256假阳性在PKZIP解密密钥验证?

__VA_OPT__(,)是否可以检测后面没有任何内容的尾随逗号?

增加getaddrinfo返回的IP地址数量

C:fopen是如何实现二进制模式和文本模式的?

变量>;-1如何在C中准确求值?

将常量转换为指针会增加.数据大小增加1000字节

初始变量重置后,char[]的赋值将消失

ifdef __cplusplus中的整数文字单引号

变量的作用域是否在C中的循环未定义行为或实现定义行为的参数中初始化?

无法访问共享目标文件内的共享指针

将变量或参数打包到 struct /联合中是否会带来意想不到的性能损失?

如何使用WRITE()以指针地址的十六进制形式写入标准输出

未使用sem_open正确初始化信号量

在我的第一个C语言中观察到的错误';你好世界';程序

如何向 execl 创建的后台程序提供输入?

创建 makefile 来编译位于不同目录中的多个源文件

如何确保 gcc + libc 对于多字节字符串使用 UTF-8,对于 wchar_t 使用 UTF-32?