什么是extern volatile指针。
extern volatile uint32 *ptr;
在这里,*ptr的行为将是什么? 这实际上是什么意思?
那么,它应该在什么时候使用?
我已经尝试通过谷歌搜索了解它,但没有得到令人满意的答案,在这种组合中没有太多信息。
什么是extern volatile指针。
extern volatile uint32 *ptr;
在这里,*ptr的行为将是什么? 这实际上是什么意思?
那么,它应该在什么时候使用?
我已经尝试通过谷歌搜索了解它,但没有得到令人满意的答案,在这种组合中没有太多信息。
无论是extern还是volatile关键字都可以单独考虑。每个关键字的作用不会与另一个关键字交互,因此每个关键字的解释都可以独立详细说明,如下所示。
extern告诉编译器ptr的实际定义在另一个模块(另一个.c
)中。 基本上,编译器处理ptr的方式没有太多变化 - 使用extern只是告诉编译器它不必为ptr在内存中保留一些空间,因为它在另一个.c
中完成,并且其实际内存位置稍后将由链接器给出。
extern uint32 *ptr;
如果省略 extern,编译器将不会发出警告。 然而,稍后,链接器在尝试链接所有目标模块以构建最终可执行程序时,将抛出一个错误,说“ptr 被定义了两次”(因为它已经在另一个 .c
中被定义了)。 uint32 *ptr;
volatile 告诉编译器,ptr 所在的内存位置可能会被一些外部事件改变或修改,因此(编译器)不应该依赖某些效率优化,比如考虑在几个顺序的 C 语句的作用域内 ptr 的值不会改变。这样的事件可能是异步中断,在执行上述作用域时发生,并修改了 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将x存储在REG3(10)中,然后加2(12),最后加5(17)并将REG3的结果存储到x内存位置(17)。这是错误的,因为编译器优化隐藏了中断效应(-10),由于它在最后忽略了来自REG3到内存(x)的值的减法。正确的结果是:x最初为10,在func的第一个printf
之后,中断将其减去10(0),然后添加2,再添加5。结果为7。
添加volatile
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.
+=
这样的运算符是原子性的,而x * x
肯定不是原子性的。因此,尽管volatile
确实告诉编译器不要缓存读取,但它对原子性不会有任何影响。此外,在这种情况下,添加volatile
可能会导致y
最终不成为任何整数的平方,如果在x * x
的两次读取之间发生了x -= 10
。这甚至可能比跳过单个计时器减少还要糟糕。 - vgruextern
关键字用于声明一个全局变量,该变量在其他地方定义(也就是说,在某个其他的.c
文件中定义)。
例如,在一个项目中有两个.c
文件a.c
和b.c
。在a.c
文件中定义了一个全局变量,并且可以在该文件中定义的所有函数中访问该变量。如果我们想要在第二个文件b.c
中访问相同的全局变量,则应在b.c
中将该变量声明为extern
。
下面是a.c
文件的内容:
int flag = 0;
int main()
{
.......
func1();
printf("\nflag value is %d\n", flag).
.......
}
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);
优化为 while(1);
。因为在代码中,在该 while
循环之前没有任何地方更新了 flag
的值。所以,如果该变量的值由其他硬件更新,则它永远不会反映在程序执行中。因此,如果我们像下面这样将该变量声明为 volatile
,编译器将不会对该变量进行任何优化,并且该程序的行为将如预期一样。volatile int flag = 0;
但如果程序变量的值没有被其他硬件更新的方式,那么不需要将该变量声明为volatile
。因为对于易失性变量,CPU需要执行每个访问该变量的指令的I/O操作。这种影响性能的情况需要考虑一个永远不会被其他硬件更新的变量。
extern
表示变量定义在定义范围之外的某处使用。例如,它可以在头文件中定义,并在 .c 文件中使用。
volatile
表示修改该变量应与外部世界保持一致。这意味着所有更新都应提交到主内存中,以便其他共享相同执行空间的线程可以看到。Extern表示它在其他地方已被定义 - 可能是在头文件中。 Volatile是向编译器提供信息,表明它不应尝试优化此内容。