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

use*_*118 0 c++ arduino interrupt volatile c++-standard-library

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

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

"passing 'volatile std::bitset<16u>' as 'this' argument discards qualifiers [-fpermissive]"
Run Code Online (Sandbox Code Playgroud)

我想我可以复制位集库并将所有内容切换为volatile,但是我认为这不是必需的,所以我认为有更好的解决方案,或者我在错误地考虑问题。

请让我知道应该怎么做。

这些答案似乎建议在ISR和多线程程序中访问ISR中的全局变量时使用volatile: C'Volatile'关键字?

为什么在C中需要使用volatile?

在中断例程中使用C ++对象(和volatile)的正确方法是什么?

当仅在中断期间读取变量时需要挥发

微控制器编程中ISR功能中的易失性关键字用法

这是许多建议使用的外部资源的补充。也许我的原始信息不清楚,或者我的情况与此不同。

J. *_*rez 7

应该将所有内容都设置为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; 
}
Run Code Online (Sandbox Code Playgroud)

当我们查看程序集时,我们在中看到,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
Run Code Online (Sandbox Code Playgroud)

优化是好的,并且在大多数情况下,您不需要使用volatile。如果您正在执行内存映射的IO,或者正在将值写为引脚,就好像它们是一个指针一样,那就是使用volatile 地方。要重申内森·奥利弗的话,

您只需要在变量上使用volatile,在这些变量上硬件可以更改变量的值,因为编译器对此一无所知。这就是volatile的目的,让编译器知道这是一个特殊的变量,可以用它不知道的方式对其进行更改。如果硬件不能改变您的价值,那么您就不需要volatile。

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

易失性和中断服务程序。

适用于volatile可能由中断服务程序修改的全局变量。话虽如此,volatile不能与之类的对象一起使用,std::bitset因为std::bitset它不支持易失性操作,并且std::bitset不可复制。

在这方面,您有两个选择:

  • 使用包含易失性原语的容器(例如, std::vector<volatile bool>
  • 编写自己的类,并支持volatile。

如果你有一个类,它平凡的可复制,那么你可以做像下面这样。首先,我们必须定义函数以允许我们在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];
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,我们可以正常地编写一个类,并且只要它是可复制的,就可以从易失性版本中创建一个副本:

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); 
};
Run Code Online (Sandbox Code Playgroud)

我们还可以将此行为封装在一个类中,以便我们可以“检出”易失变量:

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();
    }
};
Run Code Online (Sandbox Code Playgroud)

使用此功能,我们可以创建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
}
Run Code Online (Sandbox Code Playgroud)