想⼀下,为什么两个不同的控制流程调⽤同⼀个函数,访问它的同⼀个局部变量或参数就不会造成错乱?
假设以下是一个简单的func
函数示例(C语言):
int func(int a) {
int b = a * 2;
return b;
}
func
的函数(假设是main
函数或者其他函数)会将参数a
的值通过某种方式(例如将a
的值压栈或者通过寄存器传递等,这里假设是压栈)传递给func
。func
开辟一个栈帧。这个栈帧包含了func
的局部变量b
的存储空间。func
函数内部,b = a * 2
这一操作是在当前栈帧的范围内进行的。它从栈帧中获取参数a
的值,计算a * 2
后将结果存储到栈帧中局部变量b
的存储空间。b
的值(通过某种返回机制,如将b
的值放入寄存器等)返回给调用者。当有两个不同的控制流程调用func
时:
main
函数中调用func
,传入参数a = 3
。 a
的值为3
,计算得到b = 6
,这个过程都在这个栈帧内完成。sighandler
)中调用func
,传入参数a = 5
。 a
的值为5
,计算得到b = 10
。这个过程和第一个控制流程调用func
时是完全独立的,因为它们有各自独立的栈帧。func
的调用不会相互干扰,因为它们操作的是各自栈帧中的参数和局部变量,从而体现了可重入函数访问自己的局部变量或参数不会造成错乱的特性。如果⼀个函数符合以下条件之⼀则是不可重⼊的:
volatile关键字的基本概念
volatile
是一个类型修饰符。它用于告诉编译器,被修饰的变量是易变的,编译器不应对该变量进行优化。int a;
,编译器可能会根据代码的上下文对变量a
的访问进行优化。假设代码中有a = 1;
和a = 2;
两条语句,编译器可能会认为这两条语句是连续的赋值操作,中间没有其他代码改变a
的值,于是可能会将这两条语句合并或者优化访问路径。a
被声明为volatile int a;
时,编译器就不能进行这样的优化。因为volatile
表示变量a
的值可能会在编译器无法预知的情况下发生变化,比如被硬件(如外部设备通过内存映射I/O)或者其他异步执行的代码(如中断服务程序)改变。volatile在并发或异步环境中的作用
volatile unsigned char *device_register = (volatile unsigned char *)0x1000;
device_register
声明为volatile
,它指向一个内存地址0x1000
,这个地址可能是外部设备的寄存器地址。*device_register
的值时,由于它是volatile
的,每次读取编译器都会真正地从内存地址0x1000
获取数据,而不会使用之前缓存的值。同样,当向*device_register
写入数据时,也会真正地将数据写入到内存地址0x1000
,而不会因为优化而忽略这个写入操作。volatile
也非常有用。假设一个全局变量volatile int flag;
用于在主线程和中断服务程序之间通信。主线程可能会检查flag
的值来判断是否有中断发生相关的事件。如果flag
不是volatile
的,编译器可能会优化掉对flag
的检查,导致主线程无法正确地检测到flag
的变化,因为编译器可能认为flag
的值在没有显式赋值的情况下是不变的。而volatile
关键字确保了主线程每次检查flag
的值时,都是从内存中获取最新的值。该关键字在C当中我们已经有所涉猎,今天我们站在信号的⻆度重新理解⼀下
Makefile
文件
sig:sig.c
gcc -o sig sig.c #-O2
#在Makefile中,#后面的内容是注释。在gcc -o sig sig.c #-O2这一行中,-O2是被注释掉的内容。
#正常情况下,如果没有被注释,-O2是gcc编译器的一个优化选项。它用于指定编译器进行一定级别的优化,-O2通常会执行较多的#优化,比如指令重排、函数内联等操作,以提高生成的可执行程序的性能。但是在这个Makefile规则里,因为被注释了,所以gcc#编译sig.c生成sig可执行文件时不会使用-O2这个优化选项。
.PHONY:clean
clean:
rm -f sig
sig.c
文件
#include <stdio.h>
#include <signal.h>
int flag = 0;
void handler(int sig)
{
printf("change flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while(!flag);
printf("processs quit normal\n");
return 0;
}
标准情况下,键入(CTRL-C
,2
号信号被捕捉,执行自定义动作,修改flag=1 , while
条件不满足,退出循环,进程退出
第二种:
优化情况下,键入CTRL-C
,2
号信号被捕捉,执行自定义动作,修改flag=1
,但是 while
条件依旧满足,进程继续运行!但是很明显flag
肯定已经被修改了,但是为何循环依旧执行?很明显while
循环检查的flag,并不是内存中最新的flag,这就存在了数据二异性的问题。while检测的flag
其实已经因为优化,被放在了CPU寄存器当中。如何解决呢?很明显需要volatile
sig:sig.c
gcc -o sig sig.c -O2
.PHONY:clean
clean:
rm -f sig
#include <stdio.h>
#include <signal.h>
int flag = 0;
void handler(int sig)
{
printf("change flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while(!flag);
printf("processs quit normal\n");
return 0;
}
volatile
作用:保持内存的可见性,告知编译器,被该关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
2第三种:
sig:sig.c
gcc -o sig sig.c -O2
.PHONY:clean
clean:
rm -f sig
#include <stdio.h>
#include <signal.h>
volatile int flag = 0;
void handler(int sig)
{
printf("change flag 0 to 1\n");
flag = 1;
}
int main()
{
signal(2, handler);
while(!flag);
printf("processs quit normal\n");
return 0;
}
常用的gcc
优化选项:
int a = 2 + 3;
会直接计算为int a = 5;
。gcc -o output_file input_file.c -O
-O
(或-O1
)的所有优化。gcc -o output_file input_file.c -O2
-O2
的所有优化。gcc -o output_file input_file.c -O3
gcc -o output_file input_file.c -Os