未定义的行为错误:对成员变量的更改仅在某些上下文中可见

Undefined Behaviour Bug : Changes to a member variable visible only from some contexts

本文关键字:上下文 错误 变量 成员 未定义      更新时间:2023-10-16

>我有一个结构,它相当于这个:

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::P opFront 的实现如下(count 是要弹出的字节数(:

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

TransmitData::Consumption的实现如下:

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非常相似,具有一些附加功能。结构中包含的长度变量不是易失性的。

调用顺序如下:

  1. int main(( 调用 Transmit 函数
  2. 到达 C 点,将 TX 数据与参数数据一起分配。
  3. 到达 E 点,触发传输。
  4. 中断调用 TransmitComplete 函数 发送数据包后到达点A(TX数据为空(。在这个问题的情况下,这是正确的,TXData 实际上是在 Pop 函数之后是空的。
  5. 然后主发送更多数据
  6. 然后再次调用触发传输,这是问题所在。
  7. 尽管传输完成表示 TXData 在下一个调用中为空,但对传输的每个后续调用都表示 TXData 已满。 这很奇怪,因为:

    1. TXData 仅由"C 点"加载,这只发生一次
    2. 它只能通过"传输完成"删除
    3. 到达 A 点表明 TXData 是空的。这在再次调用传输之前发生。
    4. 对传输的第二次调用表示 TXData 已满(到达点 D(,没有加载更多数据。

存在问题的系统

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

我正在使用 GCC 8.2。

当前工作理论

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

奇怪的是,从传输函数中对长度变量的更改对传输完成函数是可见的。但是,对 TransmitComplete 函数中的长度变量所做的更改对 Transmission 函数不可见。

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

上述内容的唯一更改是将传输数据成员m_Length从:

size_type   m_Length{ 0u };

自:

size_type volatile m_Length{ 0u };

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

在长度成员上放置易失性会阻碍程序中其他地方的优化,我希望避免它,因为惩罚非常高。

问题

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

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

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