c++ - 未定义的行为错误 : Changes to a member variable visible only from some contexts

标签 c++ gcc arm embedded

我有一个结构,它等同于:

    struct Interface
    {
        static inline TransmitData TXData{ nullptr, 0 };
        //
        static void TransmitContinue() noexcept
        {
            packet_t packet{ IN_Id };
            //
            if ( auto tx{ TXData.Consume( packet_t::Capacity() ) }; packet.Assign( tx ) )
            {
                // Point E
                // A transmission is triggered here with the tx variable above.
            }
        }
        static bool TransmitComplete() noexcept
        {
            TXData.PopFront( packet_t::Capacity() );
            //
            if ( TXData.Empty() )
            {
                // Point A
            }
            else
            {
                // Point B
                TransmitContinue();
            }
            return true;
        }
        static bool Transmit( TransmitData data ) noexcept
        {
            if ( TXData.Empty() )
            {
                // Point C
                TXData = data;
                TransmitContinue();
                return true;
            }
            else
            {
                // Point D
                return false;
            }
        }
    };

TransmitData::Empty 的实现如下:

    ALWAYS_INLINE constexpr bool Empty() const noexcept
    {
        return ( Size() == 0 ) or ( Data() == nullptr );
    }

TransmitData::Size的实现如下:

    ALWAYS_INLINE constexpr size_type Size() const noexcept
    {
        return m_Length;
    }

TransmitData::PopFront的实现如下(count为弹出的字节数):

    constexpr TransmitData & PopFront(size_type count = 1) noexcept
    {
        auto c{ std::min(Size(), count) };
        m_Data      += c;
        m_Length    -= c;
        return *this;
    }

TransmitData::Consume的实现如下:

    ALWAYS_INLINE constexpr TransmitData Consume( std::size_t index ) const noexcept
    {
        return { m_Data, std::min( index, Size() ) };
    }

函数“Transmit”在 int main() 中运行。

“TransmitComplete”函数是从硬件中断运行的。

“TransmitData”结构与 std::string_view 非常相似,具有一些附加功能。结构中包含的长度变量不是 volatile 的。

调用顺序如下:

  1. int main() 调用传输函数
  2. 到达C点,TX数据赋值参数数据。
  3. 到达点 E,触发传输。
  4. 中断调用 TransmitComplete 函数 发送数据包后到达 A 点(TX 数据为空)。在这个问题案例中这是正确的,TXData 在 Pop 函数之后实际上是空的。
  5. Main 然后发送更多数据
  6. Trigger transmit 又被调用了,问题就出在这里。
  7. 尽管传输完成表明 TXData 为空,但下一次调用和对 Transmit 的每个后续调用都表明 TXData 已满。 这很奇怪,因为:

    1. TXData 仅由“C 点”加载,这只发生一次
    2. 它只被“TransmitComplete”移除
    3. 到达 PointA 表明 TXData 为空。这发生在再次调用 Transmit 之前。
    4. 第二次调用 Transmit 表示 TXData 已满(到达点 D),无需再加载任何数据。

存在问题的系统

这是在只有一个执行线程(主线程)的单核 ARM M0 应用程序上。中断函数总是在调用后续传输之前完成。

我正在使用 GCC 8.2。

当前工作理论

GCC 正在实例化计数器变量的两个实例。它没有将其优化为常量,因为 Empty 成员函数指示 TransmitComplete 和 Transmit 函数中长度变量的变化。

这里奇怪的是,TransmitComplete 函数可以看到 Transmit 函数中对长度变量的更改。但是,TransmitComplete 函数对长度变量的更改对 Transmit 函数不可见。

注意 如果我将 TransmitData 结构(类似于 string_view)中的 m_Length 成员变量更改为 volatile,代码将按预期工作,没有问题。

上面的唯一变化是将 TransmitData 成员 m_Length 从:

size_type   m_Length{ 0u };

收件人:

size_type volatile m_Length{ 0u };

我不认为长度变量应该要求 volatile,因为对变量的所有更改都是在代码中完成的,并且应该对编译器可见。

将 volatile 放在 length 成员上会阻碍程序其他地方的优化,我希望避免它,因为惩罚非常高。

问题

有人知道为什么会出现这个错误吗? 有谁知道上述症状是如何发生的? 我(可能)在做一些愚蠢的事情吗?

最佳答案

我认为问题在于您的 main 函数非常简单。编译器能够分析完整的控制流,并发现 main 中的任何内容都不能更改 m_length 一旦它在第一次调用 Transmit 时被设置.因此它假设 m_length 的值在整个执行时间内都保持不变,因此以下所有 m_length 与零的比较都是错误的。

这解释了为什么添加 volatile 可以解决问题:它强制编译器在每次评估时重新访问 m_length

关于c++ - 未定义的行为错误 : Changes to a member variable visible only from some contexts,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57814559/

相关文章:

c++ - 警告 : narrowing conversion C++11

c - 由 MATLAB MEX 函数中的 "optimised out"值引起的 GCC 段错误

android - 任何带有 NEON 指令集的 Android 平板电脑?

Android Studio - 如何使用 ARM 而不是 HAXM 制作 AVD?

c++ - 我在哪里可以找到 atomicity.h 中的函数定义?

c++ - 在 C++ 中将整数数组转换为位集表示的最佳方法?

c++ - 链接 : fatal error LNK 1104: cannot open file 'LIBCMT.lib'

linux - 在构建期间将静态库链接到共享库?

c++ - g++ std::set 找不到为类定义的运算符<

c++ - 在 ARM 平台上正确检测混合端浮点格式