我有一个结构,它等同于:
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 的。
调用顺序如下:
- int main() 调用传输函数
- 到达C点,TX数据赋值参数数据。
- 到达点 E,触发传输。
- 中断调用 TransmitComplete 函数 发送数据包后到达 A 点(TX 数据为空)。在这个问题案例中这是正确的,TXData 在 Pop 函数之后实际上是空的。
- Main 然后发送更多数据
- Trigger transmit 又被调用了,问题就出在这里。
尽管传输完成表明 TXData 为空,但下一次调用和对 Transmit 的每个后续调用都表明 TXData 已满。 这很奇怪,因为:
- TXData 仅由“C 点”加载,这只发生一次
- 它只被“TransmitComplete”移除
- 到达 PointA 表明 TXData 为空。这发生在再次调用 Transmit 之前。
- 第二次调用 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/