static函数_跟涛哥一起学嵌入式 21:一个static关键字引发的思考

今天有个学员问了一个C语言的static静态变量的细节问题,以前自己也没怎么注意过,感觉挺有意思,就跟大家分享下。

3343ac888e1b2489de3ce19e705132b1.png

在上面的程序中,分别定义了一个静态变量 i 和全局变量 j,然后在main函数中循环调用10次后,再分别打印 i 和 j 的值,不用纠结地去想,答案分别是 i=10, j=1。

然后问题就来了:为什么i 的值等于10,而 j 的值等于1呢?

全局变量 j 的值等于1,这个很好理解:我们每次调用 fun2 函数时,都会将全局变量 j 重新赋值为0,然后来个 ++ 操作,所以 j 的值在调用10次之后依然是 1。

而对于静态变量 i,当我们10次调用fun1时,fun1函数体内的语句static int i = 0; 会不会每次都会将 i 初始化为0?答案是不会。

这里就要涉及到普通局部变量和静态变量的区别了:普通局部变量是定义在函数体内的变量,是在栈中存储的。我们可以通过栈指针来访问和修改它,当函数退出时,栈销毁,普通局部变量也就灰飞烟灭,生命周期结束。而静态变量则不同,当一个局部变量使用static修饰时,我们可以改变这个局部变量的存储方式从栈中迁移到数据段或BSS段中,升级为静态变量,但是这个静态变量的作用域不变,仍然由大括号{}决定。

比如下面这段程序,我们定义了一个静态变量 i:

695d826285cb6918f15e83e1bbf2539d.png

使用ARM交叉编译器编译,然后使用readelf 文件查看其符号表:

$ arm-linux-gnueabi-gcc  main.c
$ readelf -s  a.out

9e340af756e4ee61f6f6069a2227dad5.png

我们会看到一个叫 i.4673 的变量保存在BSS段中,这就说明了,当我们使用static修饰一个局部变量时,它的存储方式会发生变化,有栈中迁移到数据段或BSS段中。

还需要注意的是,当我们使用static修饰一个局部变量时,如果我们不初始化,默认值是0;而普通局部变量如果不初始化,默认值则是一个随机值。

如果我们使用static定义一个静态变量时对其进行初始化,这个初始化语句只有第一次执行才有效。这也解释了为什么我们多次调用fun1时,i 的值不会重新初始化为0,而是保存上一次函数退出时的值。我们接着再看一个例子:

c0c1f67cd84a447af8fab86a3d99e221.png

在上面的程序中,我们在调用fun1时,使用一个变量 arg 来给静态变量 i 进行初始化。编译这个程序,你会发现编译错误:

error: initializer element is not constant
static int i = arg;

这是另外一个需要注意的地方:static静态变量初始化语句需要使用常量进行初始化。

为什么static修饰的局部变量需要常量才能初始化呢?其实这个也很好理解:static修饰的静态变量,是存储在数据段或BSS段中的,这两个段中的变量在编译阶段就要给它们分配存储空间,然后初始化。这跟函数内的局部变量在运行时才给它们分配存储空间是不同的。在编译阶段,因为数据段或BSS段的变量需要一个确定的值来初始化(要么是0,要么是指定的常量值),当static静态变量也要保存到这块区域时,因此必须也要用一个常量来初始化。

以上就是我们使用static关键字去修饰一个静态变量时,需要注意的一些细节。接下来我们就要思考了:当我们在函数体内去定义一个静态变量时,编译器到底是如何处理它的,或者说生成的指令代码到底是什么样的?我们以下面的代码为例:

695d826285cb6918f15e83e1bbf2539d.png

交叉编译上面的程序,然后再反汇编,生成汇编代码:

$ arm-linux-gnueabi-gcc  main.c
$ arm-linux-gnueabi-objdump -D a.out > 1.s

分析生成的汇编文件1.s,找到fun1函数的实现:

3e83d366c9e36189866c82e71f09685d.png

分析fun1的反汇编代码,我们可以看到,当我们多次调用fun1函数时,并没有每次都将变量 i 赋值为0,在函数体内压根就没有这样的指令,而是一上来就对 i 做++操作,i 变量存储在0002102C这个地址,而这个地址在哪里呢?在我们的BSS段空间内:

72788be4ae39943131fa1cf5fbd5e5a3.png

通过以上分析,我们可以得出结论:当我们在一个函数体内使用static定义一个静态局部变量时,在编译阶段,遇到static int i = 0;这样的语句,编译器会将该变量存储在数据段或BSS段中。而且这个初始化语句只有一次有效,阅后即焚。当我们多次调用fun1时,我们在函数体内并没有找到这条语句的汇编指令,这说明编译器在首次编译后,然后就可能把它当作一个声明语句来处理了。

C语言博大精深,任何一个细节细细品味,都能牵涉出一系列自己想不到的知识来,进而能不断更新和完善我们的知识体系。感谢这位学员的问题,让我们对C语言的语法理解又加深了一层。

8d00ede7eec110f515a0f4eff735a20c.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值