案件1. 文件。test1.c
:
unsigned long val = (unsigned long)&"test";
int main()
{
return 0;
}
编译器调用。cl test1.c /c
结果:
Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test1.c
test1.c(1): warning C4311: 'type cast': pointer truncation from 'char (*)[5]' to 'unsigned long'
test1.c(1): error C2099: initializer is not a constant
案例2. 文件: : 编译器调用:结果:案例 2。test2.c
:
union { unsigned long val; } val = { (unsigned long)&"test" };
int main()
{
return 0;
}
编译器调用。cl test2.c /c
结果:
Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28611 for x64
Copyright (C) Microsoft Corporation. All rights reserved.
test2.c
test2.c(1): warning C4311: 'type cast': pointer truncation from 'char (*)[5]' to 'unsigned long'
问题:为什么在把 unsigned long
变成 union
(案例2,文件test2.c)。error C2099: initializer is not a constant
是走了吗?
笔记。
两个版本的代码 cl x86
(与x64版本相同)不会产生任何错误和警告。
两个版本的代码 gcc x86/x64
(9.3.0版本)产生无错误,无警告。
更新。请注意:这个问题不是关于 safe code / unsafe code
或 wrong code / right code
. 问题是关于 cl
编译器行为。即为什么在第2种情况下,cl认为 initializer IS a constant
这样的结论是来自于没有错误信息)。
静态存储持续时间的对象的初始化器必须是 常量表达式 .
在C89标准中(唯一符合该标准的标准)。微软C语言符合),在一个常量表达式中不允许从指针到整数的转换。
这是一个语义规则,而不是约束规则,这意味着程序具有未定义的行为,不需要诊断。因此,允许编译器拒绝该程序(但不要求拒绝该程序),它可以发出诊断,也可以不发出诊断,而且对未定义的各种程序之间的一致性没有要求。
我们不能因为没有Error消息就断定初始化器被接受为常量 。
从C99开始,标准中还包含了 "一个实现可以接受其他形式的常量表达式 "的文字。编译器应该发布一致性文档,列出它接受哪些表达式为常量,虽然我找不到MSVC的文档。(如果可以的话,请留下评论!)
可能还需要注意关于将指针投向 unsigned long
. 从最新的标准。
任何指针类型都可以转换为整数类型。除了之前指定的情况外,结果是由实现定义的,如果结果不能用整数类型表示,则行为未定义。如果结果不能用整数类型表示,那么行为是未定义的。结果不需要在任何整数类型的值范围内。
因此,即使符合C99或C11标准的编译器在文件中说明它接受在常量表达式中从指针到整型的转换,仍然有可能在启动时,转换的结果会导致一个陷阱,或者导致未定义的行为(这 ,和之前一样,意味着不需要诊断,程序可能被拒绝)。