首页 /  技术专区  /  C 屏幕太窄?试试伸展一下吧 >

存储类——与static、auto、register关键字

1、什么是存储类

存储类这个东西,在不同书籍中可能解释的不太一样,在这里为了便于理解,我们会采用我们的解释方式。

存储类的全称叫“存储类型”,存储类就是用来说明“常量、变量和函数”的存储位置的,比如自动局部变量在栈中,存储位置为栈,那么它的存储类就是栈。

1.1 详细说说存储类,以及与之相关的auto、static、register等关键字

1.1.1 函数指令

存储类为.text,因为函数指令都是存放在.text中的。

函数指令的存储类是固定的,所以函数指令的存储位置是固定不变的。


1.1.2 常量

(1)当常量比较小时

        小到能够和指令放在一起的话,就会和指令一起直接放在.text中。

        此时常量的存储类为.text。

(2)常量比较大时

        大到无法直接成为指令放在一部分,此时就需要单独的存在.rodata中,此时常量的存储类就为.rodata

        常量的存储类也是固定的,要么是.text,要么是.rodata。

1.1.3 全局变量

(1)初始化了的全局变量

        存储类为.data。

(2)未初始化的全局变量

        存储类为.bss

全局变量的存储类也是固定的,要么在.data中要么在.bss,.data、.bss合称为静态数据段,或者静态数据区,所以全局变量的存储类可统称为“静态数据段”。


1.1.4 局部变量

局部变量的存储类不是固定,根据修饰的auto、static、register关键字的不同,存储类不同。

(1)自动局部变量 与 auto关键字

fun()
{
    auto int a; //等价于int a,auto可以省略不写
}

1)auto

为局部变量的存储类关键字,auto只对局部变量有效。

如果不写auto的话,默认就是auto的,所以我们平常定义的局部变量,默认都是auto修饰的。

2)auto所对应的存储类

auto就是automatic自动的意思,以auto int a为例,auto就表示a的空间是自动开辟、自动释放的,我们知道只有栈才满足自动开辟自动释放的特点,因此auto就表示a的存储类为“栈”。

正因为自动开辟、自动释放的特点,auto修饰的局部变量,我们就称为“自动局部变量”。

疑问:怎么感觉auto这个关键字是多余的?

auto这个关键字对于我们程序员来说确实是多余的,既然auto可以省略,那我们就不会傻到去把auto写上,auto在C程序中几乎看不到,因为没有那个程序员是傻子。

其实这个auto关键字主要是给编译器用的,因为编译器必须通过这个关键字来识别“栈”这个存储类,就算auto被省略了,但是在编译时会被自动加,用以标记局部变量是“栈”这种存储类。

(2)静态局部变量 与 staitc关键字

fun()
{
    static int a=100; //static不能省,省了就默认是自动局部变量
    static int b;     //未初始化的静态局部变量
}

1)static

static有两种用法,

第一种:修饰局部变量

        当static修饰局部变量时,static用于标记局部变量的存储类。

第二种:修饰函数和全局变量

        与链接域有关,这个后面再介绍。

2)static对应的存储类

(a)初始化了的静态局部变量,存储类为.data

        比如例子中的a就被初始化了,所以a的存储类为.data。

(b)未初始化的静态局部变量,在.data中

        比如例子中的b就没有被初始化,所以b的存储类为.bss。

        

.data和.bss合称为静态数据段,所以静态局部变量的存储类合称为静态数据段。

3)静态变量

由于全局变量与静态局部变量的存储类都是静态数据段,因此我们就将全局变量和静态局部变量统称为静态变量。

(3)寄存器局部变量 与 register关键字

int fun()
{
    register int a=100; //register不能省略,省略了就变为了默认的自动局部变量
}

1)回顾CPU对存储器的访问

image.png

· 存储容量

        寄存器  < 1级cache < 2级cache < ... < 内存 < 外存

· CPU访问时的访问速度

        寄存器>1级cache > 2级cache > ... > 内存 > 外存


以上情况,是由各存储器的材质和制作工艺来决定的。

2)register

register为寄存器的意思。

使用register修饰局部变量后,局部变量的存储类就为寄存器,也就是说此时局部变量的空间开辟于寄存器中。

register修饰的局部变量,我们就称为寄存器局部变量。


3)将局部变量的存储类设为register有什么好处

由前面的介绍可知,cpu访问寄存器的速度 远远> 访问内存的速度,所以如果你希望cpu能够更快速的访问局部变量的话,我们就可以使用register修饰,让局部变量的空间在寄存器中。

例子:

a.c

#include <stdio.h>
#include <time.h> //time函数所需的头文件

int main(void)
{
    register int a = 0; //寄存器局部变量
    int b = 0; //自动局部变量(栈)
    int old_time = 0; 
    old_time = time(NULL);//记录循环开始时的时刻
    for(a=0; a<1000000000; a++); //循环累加a的值,实现延时
    printf("%ld\n", time(NULL)-old_time); //延时时间 = 结束时刻-其实时刻
    old_time = time(NULL); //起始时刻
    for(b=0; b<1000000000; b++); //延时
    printf("%ld\n", time(NULL)-old_time); //延时时间
    return 0;
}

运行结果:

0
3

a:寄存器局部变量

b:栈中的自动局变量

cpu访问a速度 > 访问b的速度,不过由于时间差太小了,很难感受到,但是就像我们所举的例子一样,当对a和b的访问次数到达一定数量级时,访问a和b的速度差还是可以被明显的感受到。


4)什么时候可以使用“寄存器局部变量”

(a)如果某个局部变量的访问速度要求很高的,我们就可以使用regster来修饰。

(b)如果程序中某个局部变量的使用频次非常高,此时为了提高访问效率,我们也可以使用register修饰。

比如我们后面讲uboot移植时,uboot中使用频次比较高的局部变量,有些就会被register修饰。

5)register令人糊涂的地方

        1)第一个令人糊涂的地方:虽然写了register,但不一定有效

        如果cpu的寄存器数量很少,比如intel cpu的寄存器数量相对ARM CPU来说就偏少,所以很有可能出现寄存器不够用的情况,如果编译器编译时发现寄存器不够用了,编译器就会将register自动改为auto。

        说白了就是虽然是register修饰的,但是最终能不能起作用不一定,看编译器。

        2)第二个令人糊涂的地方

        就算你不写register,以优化方式编译时,编译器也可能会帮你自动改为register,这里说的是可能会。

        比如上面的例子a.c,我们gcc优化编译,

        gcc a.c -O1  //优化级别>=O1就行

        优化编译后你会发现,延时的时间几乎是一样的,说明b的存储类被编译器从auto改为了register。

疑问:你怎么这么肯定。

如果你想确定b到底有没有被改为register,查看汇编是最直接的办法,我已经查看过,b的空间确实开辟于寄存器,而不是栈,那就说明b的存储类确实被改为了寄存器。

疑问:优化编译时,为什么b会从auto变为register?

我们给了编译器优化权限,当编译器觉得代码不够好时,就会进行优化

gcc优化编译例子程序a.c时,编译器发现b的使用频次非常高,觉得很影响效率,所以编译器就将b的存储类从auto自动改为了register。

不过编译器也不是一定会优化为register,因为如果编译器发现寄存器数量不足的话,此时b的存储类就还是auto。

优化有好处,自然也有缺点,最大的缺点就是自作主张修改你的代码,让原有的代码逻辑发生改变,甚至有时完全违背了你的原意,所以如果你不想被优化的话,建议编译时就不要指定优化选项。


6)我们应该如何对待register

(1)明白它的用途

        如果你在别人的代码中看到了这个关键字,你要明白这是什么意思。

(2)在我们自己的程序中,不建议使用

        因为这个关键字并不能一定管用,到底管不管用取决于编译器的处理,带有不确定性,因此我们建议不使用,而且现在确实也用的少了。

1.1.5 形参

一般情况下形参的默认存储类为栈,所以形参空间默认就是开辟于栈中。

(1)auto、static能不能修饰形参?

        不能,这两个关键字不能用于修饰形参,对于形参来说默认的存储类就是栈,不需要auto来说明。

(2)register能不能修饰形参

        可以,此时存储类为寄存器,所以形参的存储类就两种:

        默认:栈

        register修饰:寄存器。

(3)ARM下的一个特殊情况

        ARM cpu的寄存器特别丰富,为了能够提高效率,编译器在编译针对ARM的c程序时,如果函数的形参小于4个的话,形参的存储类默认会定为register。

        只有当形参数量超过5个时,第5个以后的形参的存储类才默认为栈。

        如果编译器编译的是针对Intel CPU的程序的话,由于Intel cpu的寄存器数量相对比较少,所以函数形参的存储类默认都是栈,如果在程序中人为指定为register的话,存储类有可能会是寄存器。


1.1.6 能否使用auto、static、register修饰全局变量

(1)auto和register

        全局变量的存储类是固定的,为静态数据区,如果使用auto和register修饰全局变量的话,其实是在尝试使用auto和register将全局变量的存储类改为栈和寄存器,显然这是不行的,这会导致编译出错。

(2)staitc

        可以,使用static修饰全局变量时,static与存储类半毛钱关系都没有,static修饰全局变量时只与与链接域有关,后面讲到链接域时再来介绍。



0/200
图片验证码