在实现循环缓冲区时,我正在努力正确使用
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[buf_index] = c;
head_index = (head_index + 1) % n;
}
}
char buffer_read(void)
{
char c = circ_buf[tail_index];
tail_index = (tail_index + 1) % n;
return c;
buffer_input 从 ISR 调用,buffer_read 从主程序调用。
这个实现有效,但我在某处读到不需要使缓冲区数组本身成为易失性的,因为内容只能通过易失性索引访问。真的吗?内容在中断和主程序中都被访问,所以根据我的理解,缓冲区也应该是易失性的?
这个实现有效,但我在某处读到不需要使缓冲区数组本身成为易失性的,因为内容只能通过易失性索引访问。这是真的吗?
否,因为如果
circ_buf
不符合 volatile
,则 C 标准不要求程序在第二次或后续使用该元素时重新读取缓冲区的元素,无论索引是否为是否具有挥发性。
另一方面,是的,但这样做很危险。围绕
volatile
制定规则的原因是要求 C 实现重新读取可能更改的内容(与编写必须对硬件可见的内容类似,但当我们只考虑 buffer_read
时,这不是问题)
例程),并且不执行可以省略重读的优化。我们可以推断,由于缓冲区的使用方式以及它包含的元素数量(如果足够大),编译器无法进行合理的优化来允许它缓存缓冲区元素。这是一种危险的继续方式,不会在正常的软件工程中使用,但我将其包括在内以使答案完整。
如果
x
是易失性的,那么int t0 = x, t1 = x;
需要读取x
两次。如果 x
不是易失性的,编译器可以轻松优化它以读取 x
一次。但是考虑到 buffer_read
如何遍历缓冲区,编译器不可能读取一次 buffer[0]
并在读取遍历整个缓冲区并返回到开头后保留一个副本以供重用。所以,在实践中,如果你从 volatile
中省略 circ_buf
,你将不会因此看到程序故障。
尽管如此,拥有
volatile
是正确的。如果没有它,C 标准指定的 C 源代码的含义就不是程序所需的含义,即从内存中读取由 ISR 更新的 circ_buf[tail_index]
的最新值。