【问题标题】:Compiling with different versions of gcc on ubuntu yields different results在 ubuntu 上使用不同版本的 gcc 编译会产生不同的结果
【发布时间】:2019-07-08 12:23:15
【问题描述】:

所以我有一个类似这样的项目设置:

  • myfile.cpp 其中包括:
    • fsl_clock.h

myfile 是一个 c++ 文件,fsl_clock.h 是来自 NXP 的纯 C 头文件,可以看到它的一个版本here

我的文件看起来像:

#include "fsl_clock.h"

现在我的文件中确实有更多东西,但我清空了它,直到只剩下这些。

以下是我尝试过的编译结果:

  • 使用 arm 交叉编译器 arm-none-eabi-g++ 可以正常编译。
  • 使用主机 (x86Linux) g++ --version 7.3.0-16ubuntu3 可以正常工作
  • 使用主机 (x86Linux) g++ --version 7.3.0-27ubuntu1~18.04 会出现大量奇怪的错误。

我得到的错误是这样的:

device/MIMX8MQ6_cm4.h8856:51: error 'reinterpret_cast<CMM_Type*>(808976384)' is not a constant expression

这行代码是纯C,看起来像:

kCLOCK_RootM4 = (uint32_t)(&(CCM)->ROOT[1].TARGET_ROOT)

其中 CCM 定义为:

#define CCM_BASE (0x30380000u)
#define CCM ((CCM_Type*)CCM_BASE)

所以看起来较新的 g++ 7.3.0-27ubuntu1~18.04(也许是正确的)正在 C 风格包含的头代码中做 c++ 的事情(例如 reinterpret_cast)。较旧的编译器 7.3.0-16ubuntu3 的行为方式不同 - 编译正常。

谁能说出这两种编译器之间的区别是什么以及为什么一种有效而另一种无效?两个编译器 gnu g++ 具有相同的 g++ 版本 7.3.0。但我不太明白 16ubuntu327ubuntu1~18.04 的后缀以及为什么这可能会改变行为......

注意现在,我知道对于我的主机构建,我真的不想在我的主机构建中包含特定于板的代码,但那是另一回事。我现在更感兴趣的是了解为什么两个编译器之间存在差异。

更新

对于主机构建,编译器行如下所示:

g++ -w -Isource/drivers -Isource/board -Isource/device -m32 -g -std=c++11 -c source/myfile.cpp -o out.o 

CMM_Type(必须手动复制它,因为原件埋在 NXP 网站中)看起来像(注意它的缩写,因为要复制的内容太多 - 但它的 uint32_t 结构):

typedef struct {
   volatile uint32_t GPR0;
   volatile uint32_t GPR0_SET;
   struct {
      :
   } PLL_CTRL[39];
      :
   struct {
      volatile uint32_t TARGET_ROOT;
      volatile uint32_t TARGET_ROOT_SET;
      volatile uint32_t TARGET_ROOT_CLR;
         :
   } ROOT[142];
} CCM_Type;

最小示例 - 在线 GDB 我做了一个最小的例子——它不能用在线 GDB 编译,但它确实产生了我在编译器中解释的错误。链接是here

极简 - 魔杖盒 与在线 GDB 示例完全相同的代码,但这实际上显示了我得到的相同错误:here

最小的示例代码

#include <stdint.h>

typedef struct {
    struct {
        volatile uint32_t TARGET_ROOT;
    } ROOT[4];
} CCM_Type;

#define CCM_BASE (0x30380000u)
#define CCM ((CCM_Type *)CCM_BASE)

typedef enum _clock_root_control
{
    kCLOCK_RootM4 = (uint32_t)(&(CCM)->ROOT[1].TARGET_ROOT)
} clock_root_control_t;

int main()
{
    return 0;
}

【问题讨论】:

  • 可能是因为您将 C 代码编译为 C++?
  • 各种可能的解释,包括将unsigned 转换为指针并取消引用该指针会产生未定义行为的可能性。如果没有更多信息(例如,CCM_Type 是什么,考虑到它的用法暗示它是一种数据结构?)任何试图提供帮助的人都不得不求助于猜测。阅读有关提供minimal reproducible example 的信息 - 一小段演示问题的代码示例,其他人可以使用它来获得相同的症状。这需要付出努力,但会显着提高您获得有用建议的机会。
  • 您是否尝试将包含在 #extern "C" { ...} 中?
  • 请在此处发布minimal reproducible example,而不是链接。这些链接很有帮助,但还不够。如果您使用 g++ 编译,那么它是 C++ 而不是 C,更不用说 C 中没有 reinterpret_casts。
  • 最小示例中的代码不是有效的 C 和无效的 C++。枚举数必须是任一语言中的常量表达式。 (uint32_t)(&amp;(CCM)-&gt;ROOT[1].TARGET_ROOT) 不是一个。你的编译器没有问题。原包装坏了,需要修理。它依赖于一个不可移植且可能未记录的编译器扩展,没有人承诺永远存在,你瞧,它不再存在了。

标签: c++ c ubuntu gcc g++


【解决方案1】:

reinterpret_cast 和 C 风格的转换不能由编译器在编译时进行评估,尤其是当它们创建然后您取消引用的指针时。 enum 常量需要在编译时获取值。在这种情况下我会做的是使用整数值CCM_BASEoffsetof

#include <cstddef>

typedef enum _clock_root_control
{
    kCLOCK_RootM4 = CCM_BASE + offsetof(CCM_Type, ROOT[1].TARGET_ROOT)
} clock_root_control_t;

您知道原始示例中的所有指针取消引用都是毫无意义的,因为您只是使用&amp; 运算符来获取地址。但是,没关系。该取消引用必须仍然有效且可执行,编译器才能在编译时对其进行评估。您正在使用的地址对您的编译器毫无意义。谁知道那里有什么,或者即使它指的是映射页面?当然,编译一个 C++ 程序不应该在编译器的内存中造成随机的混乱。

对于 C 或 C++ 都是如此。由于您的代码在技术上是未定义的行为,因此它可能会随机适用于某些编译器。并且,大多数针对您希望实际使用此类代码的平台的编译器都会在运行时执行代码时执行您所期望的操作。

但是,对于编译时评估,&lt;csstddef&gt; 中的 offsetof 宏会为您处理所有这些类型的细节,并且是已定义的行为。这是您想要和需要的。

【讨论】:

    【解决方案2】:
    typedef enum _clock_root_control
    {
        kCLOCK_RootM4 = (uint32_t)(&(CCM)->ROOT[1].TARGET_ROOT)
    } clock_root_control_t;
    

    此代码在 C 或 C++ 中均无效。

    在 C++ 中,枚举数必须是常量表达式。常量表达式不能涉及reinterpret_cast。从指针到整数的 C 风格转换等价于 reinterpret_cast

    在 C 中,枚举数必须是整数常量表达式。整数常量表达式不能包含指针操作数。

    解决此问题的一种方法是将表达式替换为等效的常量表达式,例如

    CCM_BASE + offsetof(CCM_Type, ROOT[1].TARGET_ROOT)
    

    Live example

    【讨论】:

      猜你喜欢
      • 2020-04-23
      • 1970-01-01
      • 1970-01-01
      • 2019-02-03
      • 1970-01-01
      • 2019-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多