使用易失性标准模板对象的方法

Using Methods of Volatile Standard Template Objects

我有一些代码现在希望 运行 在 Teensy 3.6 微控制器上的基于定时器的中断中。该代码访问 class 的 [global] 对象数组。我已经将该数组和所有成员变量标记为易变的,我认为这是正确处理中断的第一步。

我标记为 volatile 的成员变量之一是 std::bitset,我想称它为非易失性方法,但我不能这样做

"passing 'volatile std::bitset<16u>' as 'this' argument discards qualifiers [-fpermissive]"

我想我可以复制 bitset 库并将所有内容切换为 volatile,但我认为这不是必需的,所以我认为要么有更好的解决方案,要么我的想法不正确。

请告诉我应该做什么。

这些答案似乎建议在 ISR 中访问全局变量时使用 volatile: C 'Volatile' keyword in ISR and multithreaded program?,

Why is volatile needed in C?,

What is the correct way of using C++ objects (and volatile) inside interrupt routines?,

这是许多外部资源推荐使用的补充。可能是我原来的信息不清楚,也可能我的情况和这些不一样。

您应该而不是将所有内容都设置为可变的。 Volatile 有一个特定的目的,那就是防止编译器优化内存的读写。让我们看一个非常简单的例子。

int regular_sum(int* ptr) {
    int a = *ptr;
    int b = *ptr;
    return a + b;
}
int volatile_sum(int volatile* ptr) {
    int a = *ptr;
    int b = *ptr;
    return a + b; 
}

当我们查看程序集时,我们看到在 regular_sum 中,编译器意识到您正在两次取消引用同一个指针,并将其优化为仅一次取消引用。但是在 volatile_sum 中,编译器插入了两个解引用:

regular_sum(int*):
        mov     eax, DWORD PTR [rdi]
        add     eax, eax
        ret
volatile_sum(int volatile*):
        mov     eax, DWORD PTR [rdi]
        mov     edx, DWORD PTR [rdi]
        add     eax, edx
        ret

优化很好,而且大多数时候,您不需要使用 volatile。如果您正在执行内存映射 IO,或者您正在将值写入引脚,就好像它们是指针一样,那就是您使用 volatile 的地方。重申 Nathan Oliver 所说的话,

You only need to use volatile on variables where the hardware can change the value of the variable because the compiler can't know about that. This is what volatile is for, letting the compiler know this is a special variable that could be changed in a manner it doesn't know about. If the hardware cant change the value on you, then you don't need volatile.

但是如果您正在对一个对象进行计算,不要使用 volatile。对普通对象进行计算,然后将结果复制到您的易失性指针。

易失性和中断服务例程。

在可能被中断服务程序修改的全局变量上使用 volatile 是合适的。也就是说,volatile 不能与 std::bitset 这样的对象一起使用,因为 std::bitset 不支持易失性操作,并且 std::bitset 不可简单复制。

对此,您有两种选择:

  • 使用包含可变基元的容器(例如,std::vector<volatile bool>
  • 编写您自己的 class,支持 volatile。

如果您有一个 class 并且 可简单复制的,那么您可以执行如下操作。首先,我们必须定义函数以允许我们从 volatile 类型进行复制:

template<class T>
T volatile_copy(T const volatile& source) {
    static_assert(std::is_trivially_copyable_v<T>, "Input must be trivially copyable");
    T dest;
    auto* dest_ptr = dynamic_cast<char*>(&dest);
    auto* source_ptr = dynamic_cast<char const volatile*>(&source);

    for(int i = 0; i < sizeof(T); i++) {
        dest_ptr[i] = source_ptr[i];
    }

    return dest;
}

template<class T>
void volatile_assign(T volatile& dest, T const& source) {
    static_assert(std::is_trivially_copyable_v<T>, "Input must be trivially copyable");
    auto* source_ptr = dynamic_cast<char*>(&source);
    auto* dest_ptr   = dynamic_cast<char volatile*>(&dest);

    for(int i = 0; i < sizeof(T); i++) {
        dest_ptr[i] = source_ptr[i];
    }
}

然后,我们可以正常写一个class,只要它是平凡可复制的,我们就可以从易失性版本创建一个副本:

struct MyBitset {
    uint64_t bits;
    // Logic

    void flip() {
        bits = ~bits;
    }
    void addOne() {
        bits++;
    }
};

volatile MyBitset flags;

void interrupt_handler() {
    auto local = volatile_copy(flags);

    // Do stuff to local

    volatile_assign(flags, local); 
};

我们也可以把这个行为封装在一个class中,这样我们就可以"check out"volatile变量:

template<class T>
struct Checkout {
    T local;
    T volatile& source;
    Checkout(T volatile& source)
      : local(volatile_copy(source))
      , source(source) {}
    void save() {
        volatile_assign(source, local);
    }
    ~Checkout() {
        save();
    }
};

使用它可以创建 volatile 变量的本地副本,对其进行修改,结果将自动保存:

volatile MyBitset flags;

void interrupt_handler() {
    auto f = Checkout(::flags);

    f.local.flip(); //We can call whatever member functions we want on the local

    // When the function exits, changes made to the local are automatically assigned to the volatile global
}