【问题标题】:cl x64: unsigned long outside / inside union: error C2099: initializer is not a constant / NO errorcl x64: unsigned long outside / inside union: error C2099: initializer is not a constant / NO er​​ror
【发布时间】:2020-09-21 10:35:59
【问题描述】:

案例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.文件: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)都不会产生错误和警告。

UPD。请注意:问题与safe code / unsafe codewrong code / right code 无关。问题是关于cl 编译器行为。 IE。为什么在第二种情况下,cl认为initializer IS a constant(这样的结论是由于没有错误消息)。

【问题讨论】:

  • 你做错了。最重要的是,因为 Microsoft Visual Studio 编译器将long(因此unsigned long)保留为 32 位类型。在 64 位系统上,指针是 64 位宽的,它不能放入 32 位变量中。
  • 是什么让您认为尝试将指针值塞入unsigned long 是安全的?
  • @Someprogrammerdude:C 标准明确允许从指针到整数类型的转换,即使目标太窄而无法表示完整的地址信息。结果是实现定义的,可用于例如对齐问题。无论如何,代码是否“错误”不是问题。编译器行为的区别是。
  • @AndrewHenle:OP 给了我们一个关于 MRE、编译器版本识别和准确编译器消息的恰当问题。该问题证明了对语言问题的认识,并且确实提出了一个关于编译器行为的重要问题。 OP 可能已将其他代码减少到此 MRE,并且问题的上述质量表明他们也知道转换警告的含义。他们应该得到怀疑的好处,即他们知道自己在做什么,不应该对代码中的问题进行下意识的挑剔,这些问题可能是由于是 MRE 而不是完整的真实代码。

标签: c initialization long-integer unions cl


【解决方案1】:

静态存储对象的初始化器必须是常量表达式

在 C89 标准(Microsoft C conforms 的唯一标准)中,常量表达式中不允许从指针转换为整数。

这是语义规则而不是约束,这意味着程序具有未定义的行为,无需诊断。因此,允许编译器拒绝程序(但不需要拒绝它),它可能会或可能不会发出诊断,并且不要求在未定义的各种程序之间保持一致性。

我们不能从没有错误消息的情况下得出初始化器被接受为常量的结论。

从 C99 开始,该标准还包括文本“一个实现可以接受其他形式的常量表达式”。编译器应该发布一致性文档,列出它接受哪些表达式作为常量,尽管我找不到 MSVC 的文档。 (如果可以,请发表评论!)

还可能需要注意有关将指针转换为unsigned long 的规则。来自最新标准:

任何指针类型都可以转换为整数类型。除先前规定外, 结果是实现定义的。如果结果不能用整数类型表示, 行为未定义。结果不必在任何整数的值范围内 输入。

因此,即使符合 C99 或 C11 的编译器记录它接受从常量表达式中的指针到整数的转换,转换的结果仍然可能导致启动时出现陷阱,或导致未定义的行为(其中,和以前一样,意味着不需要诊断并且程序可能会被拒绝)。

【讨论】:

  • 感谢您的回答!不得不考虑。一个快速的问题:任何想法,为什么cl x86 在这两种情况下都不会产生错误和警告?因为现在的行为看起来像 cl x86cl x64 是两个不同的实现(是吗?)并使用不同的逻辑。另请参阅(可能很有趣):gcc.gnu.org/bugzilla/show_bug.cgi?id=66618.
  • @pmor 我怀疑除了cl 的开发人员之外是否有人可以回答这个问题,也许其中有人会看到这个问题。我们只能推测封闭源代码在内部是如何工作的。不同之处可能与我最后一段中的一点有关,即将 64 位指针转换为 32 位 int 肯定会有一些有损情况,而 32 位指针到 32 位 int 可能是实现定义为明智的所有情况。或者可能与泄漏到 C 编译器中的 C++ 逻辑有关(在 C++ 中,强制转换格式不正确,当且仅当它可能是有损时才需要诊断)
  • 谢谢!同意64-bit pointer to 32-bit int definitely has some lossy cases while 32-bit pointer to 32-bit int is likely to be implementation-defined to something sensible in all cases。关于可能的C++ logic leaking into the C compiler:有趣的想法。看看这个相关的无效错误:gcc.gnu.org/bugzilla/show_bug.cgi?id=60165。引用:there is no guarantee you get the same warnings between different optimization levels。因此,在cl 的情况下,无法保证您在目标处理器的不同位深度(即 32/64 位)之间得到相同的错误。
猜你喜欢
  • 2022-12-26
  • 1970-01-01
  • 2021-01-20
  • 2022-11-20
  • 1970-01-01
  • 2018-05-09
  • 2017-08-01
  • 2022-12-02
  • 1970-01-01
相关资源
最近更新 更多