什么是外部易失性指针。
extern volatile uint32 *ptr;
在这里,*ptr 的行为是什么?这实际上意味着什么?
而且,什么时候应该使用它?
我试图用谷歌搜索它,但没有得到任何满意的答案,关于这种组合的信息并不多。
extern和volatile关键字都可以独立考虑。这些关键字中的每一个的作用都不与另一个相互作用,因此可以单独详细说明它们中的每一个,如下所示。
extern告诉编译器ptr的实际定义在另一个模块(另一个.c
)中。基本上,编译器处理ptr的方式没有太大变化- 拥有extern只是告诉编译器它不必像在 another 的其他地方那样为ptr.c
保留一些内存空间,并且它的实际内存位置将由稍后链接器。
extern uint32 *ptr;
如果您省略extern,编译器将不会抱怨。但是,稍后,链接器在尝试链接所有对象模块以构建最终的可执行程序时,将抛出错误“ ptr已定义两次”(因为它已在另一个中定义.c
)。
uint32 *ptr;
volatile告诉编译器ptr所在的内存位置可能会被一些外部事件改变/修改,并且它(编译器)不应该依赖于一些效率优化,比如考虑到ptr的值在几个范围内不会改变C的顺序行。这样的事件可能是异步中断,发生在CPU执行上述范围并修改ptr值时。
通常(在注释中带有优化 C 的虚拟汇编代码),REGx 是 CPU 寄存器,我们对变量y不太感兴趣...
int x = 10;
int func() {
int y; // REG4
printf("%d\n", x); // Set REG3 = memory(x) and display x
x += 2; // Add 2 to REG3
y = x * x; // REG4 = REG3 * REG3
printf("%d %d\n", x, y); // Do printf(..., REG3, REG4)
x += 5; // REG3 = REG3 + 5
// memory(x) = REG3 (save register to memory)
return y; // return REG4
}
应该显示10, 12, 144
。为了提高效率(内存访问比寄存器访问成本更高)说编译器将x的值存储在内部 CPU 寄存器(REG3)中并在func中安全地使用它,直到它存储x的新值(它是一个全局变量)到x内存位置。x最后是 17。
但是想象一下,这个程序比这更复杂,并且每分钟都会有一个时钟中断,将x减去 10 。如果中断会发生什么...
void inter_call_by_timer_every_minute() {
x -= 10;
}
... 出现在func中说就在该printf("%d\n", x);
行之后?func在 REG3 (10) 中有x,加 2 (12) 最后加 5 (17) 并将 REG3 结果存储到x内存位置 (17)。这是错误的,因为编译器优化已经隐藏了中断效果 (-10),因为它在最后将 REG3 的值存储到 memory(x) 中,而忽略了中断所做的减法。正确的结果是:x最初为 10,printf
在func中的第一个之后将其减去 10(0) ,然后添加 2,然后添加 5。结果 7。
添加挥发物
volatile int x = 10;
将使编译器避免func中的x优化
int func() {
int y; // REG4
printf("%d\n", x); // display memory(x)
x += 2; // memory(x) += 2
y = x * x; // REG4 = memory(x) * memory(x)
printf("%d %d\n", x, y); // Do printf(..., memory(x), REG4)
x += 5; // memory(x) += 5
return y; // return REG4
}
并一直从内存中读取x的值。结果,在第一个 printf 之后从inter_call_by_timer_every_minute中断,是x == 7。
extern
关键字用于声明在其他地方定义的全局变量(这意味着它在其他.c
文件中定义)。
例如,在一个项目中考虑两个.c
文件a.c
并且b.c
在那里。在 中定义了一个全局变量a.c
,并且可以在该文件中定义的所有函数中访问该变量。如果我们想在第二个文件中访问同一个全局变量b.c
,那么该变量应该声明extern
为b.c
a.c
文件如下
int flag = 0;
int main()
{
.......
func1();
printf("\nflag value is %d\n", flag).
.......
}
b.c
文件如下
extern int flag;
void func1();
{
.....
flag = 10;
.....
}
volatile
关键字用于通知编译器在生成可执行指令时避免进行任何类型的优化。
int flag = 0;
int main()
{
while(flag == 0);
printf("\nflag value is %d\n", flag);
return 0;
}
考虑上面的程序,所有编译器都会优化while(flag == 0);
as while(1);
。因为在代码中没有在该循环flag
之前更新值的位置。while
因此,如果该变量值被其他硬件更新,那么它将永远不会反映在程序执行中。因此,如果我们像下面这样声明该变量volatile
,编译器将不会对该变量执行任何优化,并且该程序的行为将符合预期。
volatile int flag = 0;
但如果程序变量的值无法被其他硬件更新,则无需将该变量声明为volatile
. 因为对于可变变量,CPU 需要为访问该变量的每条指令执行 I/O 操作。对于永远不会被其他硬件更新的变量,需要考虑这种对性能的影响。
我会解释我通过关键字知道的方式。extern
表示该变量被定义为在定义范围之外的某处使用。例如,它可以在头文件中定义并在 .c 文件中使用。
volatile
意味着对该变量的修改应该与外部世界一致。这意味着所有更新都应该提交到主内存,以便共享相同执行空间的其他线程可以看到它。
Extern 意味着它在其他地方定义 - 可能在头文件中。Volatile 是编译器的信息,它不应该尝试优化它。