在实现循环缓冲区时,我正在努力正确使用关键字volatile .缓冲区在ISR中写入,并在主程序中读取.我运行的是裸机微控制器,所以没有多线程.

我目前的实现如下所示:

volatile char circ_buf[n] = {0};
volatile size_t head_index = 0;
volatile size_t tail_index = 0;

void buffer_input(const char c)
{
    if((head_index+ 1) % n != tail_index) {
        //Only if buffer is not full
        circ_buf[head_index] = c;
        head_index = (head_index + 1) % n;
    }
}

char buffer_read(void)
{
    if (tail_index != head_index)
    {
        char c = circ_buf[tail_index];
        tail_index = (tail_index + 1) % n;
        return c;
    }
    //Lets assume 0 will never be content of the buffer for this example
    return 0;
}

从ISR调用BUFFER_INPUT,从主程序调用BUFFER_READ.

这个实现是有效的,但我在某处读到,不需要使缓冲区数组本身成为易失性的,因为内容只能通过易失性索引来访问.这是真的吗?内容是在中断和主程序中访问的,所以在我的理解中,缓冲区也应该是易失性的?

推荐答案

这个实现是有效的,但我在某处读到,不需要使缓冲区数组本身成为易失性的,因为内容只能通过易失性索引来访问.这是真的吗?

不需要,因为如果circ_buf不符合volatile,那么C标准不要求程序在第二次或以后使用缓冲区的元素时重新读取该元素,无论索引是否是易失性的.

另一方面,是的,但危险的是这样做.规则在volatile左右的原因是要求C实现重新读取可能发生变化的内容(类似地,编写必须对硬件可见的内容,但当我们只考虑buffer_read routine 时,这并不是问题),并且不执行可以省略重新读取的优化.我们可以推断,由于缓冲区的使用方式和它包含的元素的数量(如果它足够大),编译器没有任何合理的优化可以允许它缓存缓冲区元素.这是一种危险的继续方式,不会在正常的软件工程中使用,但我将其包括在内是为了使答案完整.

如果x是易失性的,则int t0 = x, t1 = x;需要读取x两次.如果x不是易失性的,编译器会很容易地将其优化为读取x一次.但是,考虑到buffer_read步进缓冲区的方式,编译器不可能读取例如buffer[0]一次,并且在读取步进遍历整个缓冲区并返回到开头之后保留要重新使用的副本.编译器必须重新读取buffer[0],因为它没有实际的方法来避免它.因此,在实践中,如果您省略了circ_buf中的volatile,您将不会从中看到程序故障.

尽管如此,拥有volatile个是正确的.如果没有它,C标准规定的C源代码的含义不是程序所希望的含义,即从内存中读取ISR更新的最新值circ_buf[tail_index].

C++相关问答推荐

为什么这个select()会阻止?

C strlen on char array

无效使用未定义类型'structsquare'?

使用单个字节内的位字段

将整数的.csv文件解析为C语言中的二维数组

C语言编译阶段与翻译阶段的关系

致命:ThreadSaniizer:在Linux内核6.6+上运行时意外的内存映射

初始变量重置后,char[]的赋值将消失

轮询libusb_pollfd struct 列表的正确方式是什么?

为什么用非常数指针变量改变常量静态变量时会出现分段错误?

S和查尔有什么不同[1]?

可变宏不能编译

如何在VSCode中创建和使用我自己的C库?

无法识别C编程语言的语法,如书中所示

将非连续物理内存映射到用户空间

挥发性语义的形式化理解

变量值不正确的问题

既然我们在 if 中将 int 的值更改为 10,为什么在第二个 fork 后,子进程及其创建的子进程都会打印 33 ?

如何确保 gcc + libc 对于多字节字符串使用 UTF-8,对于 wchar_t 使用 UTF-32?

将字节/字符序列写入标准输出的最简单形式