• 在C语言中变量和函数有数据类型和存储类型两个属性,因此变量定义的一般形式为:存储类型 数据类型 变量名表;

C语言提供了一下几种不同的存储类型:

(1) 自动变量(auto)

(2) 静态变量(static)

(3) 外部变量(extern)

(4) 寄存器变量(register)

下面一个一个介绍:


 

外部变量(全局变量)extern----全局静态存储区

定义 引用性声明

标准定义格式:extern 类型名 变量名;

如果在所有函数之外定义的变量没有指定其存储类别,那么它就是一个外部变量,它的作用域是从它的定义点到本文件的末尾在单个源文件中的确是这样,如果有多个源文件,全局变量的作用范围不是从变量定义处到该文件结尾,而是在其他文件中也有效)但是如果要在定义点之前或者其他文件中使用它,那么就需要使用关键字extern对其进行声明(注意不是定义,编译器并不为其分配内存)

  • 总结说也就是,函数之外没有指定存储类别的,一般是外部变量,作用域是定义位置到文件末尾。但是要在其他文件中使用需要extern声明,不分配内存。

举例说明:

具体实现步骤如下:

在 file1.c 文件中定义全局变量 count:int count = 0;

在 file2.c 文件中使用 extern 声明 count 变量:extern int count;

这样,在 file2.c 文件中就可以访问 count 变量了,而不需要重新定义它。在编译时,两个源文件将被链接起来,共享同一个 count 变量。


 

注意:extern int i 是声明,但是初始化赋值就是定义了。

extern int i; //是声明,不是定义,没有分配内存
int i; //是定义

如果在声明的时候给变量赋值,那么就和去掉extern直接定义变量赋值是等价的

extern int a = 10;//尽量不要写这种定义方式
int a = 10;//上述两条语句等价

尽量不要这样写,这样就分配内存空间了


 

❗易错点:函数之外未定义的变量一般是外部变量 extern

也就是说一般没指定存储类别的变量咱们常见的是extern定义的,也就是说 全局变量是声明,局部变量(函数变量)是定义! ,区别在于占用空间。

举例如下:

//说一般没指定存储类别的变量咱们常见的是extern定义的,也就是说 **`全局变量是声明,局部变量(函数变量)是定义!`** ,区别在于占用空间。
#include<stdio.h>

//全局变量
int a;
int a;  //函数外边,相当于extern声明??  ,声明可以??????

int main()
{
    int b;
    int b;  //函数内部,相当于局部变量。相当于auto int b;定义只能一次。会报错
   return 0;
}

 

全局变量 与 局部变量的区别

Tips:
定义:表示创建变量或分配存储单元
声明:说明变量的性质,但并不分配存储单元

extern int i; //是声明,不是定义,没有分配内存
int i; //是定义

如果在声明的时候给变量赋值,那么就和去掉extern直接定义变量赋值是等价的

extern int a = 10;//尽量不要写这种定义方式
int a = 10;//上述两条语句等价

(注意上面的不同语句对声明和定义的区分,对源文件中的局部变量来说是成立的(也就是.c文件),而对于源文件中的全局变量(外部变量)int a和在头文件中的int a就不能用上面的语句来解释声明和定义的区别)

补充:定义和声明的一个小坑,对于int a;来说,在源文件中,如果是全局变量的话就是声明,如果是局部变量的话就是定义


 

‼️ 谨记:声明可以多次,定义只能一次


 

extern的生存周期

extern 关键字只是声明一个变量,它并不会直接创建变量的实体,因此没有生命周期的概念。

具体来说,extern 只是向编译器声明某个变量已经在别处定义过,让编译器在编译时知道这个变量的存在。这样,在链接阶段,编译器就能够将所有用到该变量的代码与它的定义进行链接,从而使得程序能够正常运行。

因此,extern 声明的变量的生命周期与其定义的实体的生命周期是一致的,即它们都是在程序运行期间一直存在的。这意味着,如果一个全局变量在某个文件中被定义为 extern,那么它的生命周期将与整个程序的生命周期相同,只要程序运行,该变量就一直存在。


 

静态存储类— static

定义

  • 在 C 语言中,可以使用 static 关键字来定义静态存储类变量或函数。

对于静态存储类变量,static 关键字用于改变变量的存储位置和作用域。具体来说,使用 static 定义的变量存储在静态存储区中,具有静态持续性,即使函数返回,它的值也会保留。而且,static 变量只能在当前文件中访问,不能被其他文件访问。

  • 定义格式:static 类型符 变量名称 = 初始值;

举例:

#include <stdio.h>

void func(void);


static int count = 10; // 定义静态存储类变量

int main()
{
     while (count--)
    {
        func();
    }
    return 0;
    
}
void func(void)
{
    static int i = 5; // 定义静态存储类变量
    i++;
    printf("i is %d and count is %d\n", i, count);
}

在上面的代码中,count 和 i 都是静态存储类变量,它们的值在函数调用之间保持不变。同时,count 只能在当前文件中访问,而 i 只能在 func() 函数中访问。

 

❗易错点:static 变量的值只会在第一次初始化时被赋值,之后在函数调用之间保持不变。

举例说明:

#include <stdio.h>

void func(void);

int main()
{
    for (int i = 0; i < 5; i++)
    {
        func();
    }
    return 0;
}

void func(void)
{
    static int i = 5; // 定义静态存储类变量 static
    int j = 5; // 定义自动存储类变量 auto 

    i++; // 对静态变量进行自增
    j++; // 对自动变量进行自增

    printf("i is %d and j is %d\n", i, j);
}

在上面的示例中, func() 函数中定义了一个静态变量 i 和一个自动变量 j。在 func() 函数的每次调用中,i 的值都会自增1,而j 的值则会在每次调用中被初始化为5,然后自增1。

运行程序,执行效果如下:

i is 6 and j is 6
i is 7 and j is 6
i is 8 and j is 6
i is 9 and j is 6
i is 10 and j is 6

可以看到,static 变量 i 的值在函数调用之间保持不变,并且在每次调用时自增1,而自动变量 j 的值在每次调用时都会被重新初始化为5,然后自增1。

这说明了 static 变量的值不会改变的原因是因为它们具有静态存储期和静态链接性。


如果定义为全局静态变量也不会变,还是会保持值,不会被初始化。

#include <stdio.h>

void func(void);
static int i = 5; // 定义静态存储类变量 static
int main()
{
    for (int i = 0; i < 5; i++)
    {
        func();
    }
    return 0;
}

void func(void)
{
    
    int j = 5; // 定义自动存储类变量 auto 

    i++; // 对静态变量进行自增
    j++; // 对自动变量进行自增

    printf("i is %d and j is %d\n", i, j);
}


不会变化的具体原因是:

static 定义的变量值不会变化的具体原因是因为:

  1. 静态存储期:static 变量被分配在静态存储区中,该区域的变量在程序运行期间一直存在,而不是像局部变量一样在函数调用结束后销毁。
  2. 静态链接性:static 变量具有静态链接性,只能被定义它的源文件内的函数访问,其他文件无法访问。
  3. 变量只被初始化一次:static 变量只被初始化一次,在其它时刻,只是在其原有的基础上进行修改。因此,变量的值在函数调用之间保持不变。

综上所述,static 变量值不会改变的原因是因为它们具有 静态存储期和静态链接性,以及变量只被初始化一次,所以在函数调用之间保持不变。


这个易错点很重要,也是static的特别之处。特点。


 

static的生存周期— 取决于程序运行周期,不仅仅在函数调用期间

static 定义的变量有静态存储期,意味着它们在程序运行期间一直存在,直到程序结束才被销毁。在函数内部,static 定义的变量只会被初始化一次,在其它时刻,只是在其原有的基础上进行修改,因此其值在函数调用之间保持不变。

具体来说,static 定义的变量的生存周期取决于其定义位置如果在函数内部定义了 static 变量,则该变量的生存周期为整个程序的运行期间,而不是仅在函数调用期间。

举例:

#include <stdio.h>

void test()
{
    static int i = 0;
    printf("i = %d\n", i);
    i++;
}

int main()
{
    for (int j = 0; j < 3; j++)
    {
        test();
    }
    return 0;
}

在这个示例中,test() 函数内部定义了一个 static 变量 i,并在每次调用 test() 函数时输出 i 的值。由于 i 是 static 变量,所以它的值在函数调用之间保持不变,并且在每次调用时自增1。

运行程序如下:

i = 0
i = 1
i = 2

可以看到,i 的值在函数调用之间保持不变,每次调用时自增1,这说明了 static 变量的生存周期是整个程序的运行期间,而不是仅在函数调用期间。


 

下面总结下区别 : 对比记忆

extern与static的作用域和周期的不同

extern 和 static 是 C 语言中的两个关键字,它们在变量的作用域和生命周期上有很大的不同。

作用域:

  • extern 关键字用于声明外部变量,也就是在一个源文件中定义,而在另一个源文件中声明。外部变量具有全局作用域,也就是说,它们的作用域不限于定义它们的函数或代码块。

  • static 关键字用于定义静态变量,静态变量的作用域仅限于定义它们的函数或代码块,也就是说,它们只能在其定义的代码块中访问。

周期:

  • extern 关键字用于声明外部变量,其定义和初始化在另一个源文件中进行。因此,外部变量的生命周期和作用域是整个程序的运行期间,也就是说,它们在程序开始时创建,在程序结束时销毁。

  • static 关键字用于定义静态变量,静态变量的生命周期是整个程序的运行期间。在函数内部定义 static 变量,这个变量只会被初始化一次,然后在函数调用之间保持不变。

综上所述,extern 和 static 关键字定义的变量的作用域和生命周期都有所不同,需要根据具体的需求来选择使用哪个关键字来定义变量。


因此,你可以这样理解:

  • extern 关键字声明的变量可以在整个程序中访问,它的生命周期和作用域是全局的,直到程序结束时被销毁。

  • static 关键字定义的变量只能在同一个源文件中访问,它的生命周期是整个程序的运行期间,但作用域是局部的,只能在定义它的函数或代码块中访问,直到程序结束时被销毁。

  • 还有个不同之处是 extern是声明,static是定义。 这取决于extern的特殊性

 


auto 自动存储类别— 默认 — 动态存储

在 C 语言中,auto 存储类型是默认的存储类型,如果没有显式地指定存储类型,则变量会被默认为自动存储类型。

举例:在函数内部定义一个自动变量:

void myFunction() {
    auto int x = 0;
    // ...
}

自动存储类型的变量会在定义它的函数或代码块中被创建,当函数或代码块执行完毕时,自动存储类型的变量也会被销毁。这意味着,变量的生命周期仅限于它所在的函数或代码块内部,不能被其他函数或代码块访问。

在上述代码中,变量 x 被定义为自动存储类型。它的作用域和生命周期仅限于函数 myFunction() 中,在函数执行完毕后会被销毁。

  • 需要注意的是,在 C 语言中,使用 auto 声明变量时,不能同时给出变量的存储类别说明符,例如不能将 auto 与 static 或 extern 同时使用,因为这样会产生二义性。

  • 不赋初值的话,随机值

  • 不专门声明为 static 存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。

  • 关键字 auto 可以省略,auto 不写则隐含定为“自动存储类别”,属于动态存储方式


 

register—— 寄存器存储

register 是 C 语言中的一种存储类别(Storage Class),它用于告诉编译器将变量存储在寄存器中。在 C 语言中,变量的存储位置可以是寄存器、堆栈或静态存储区,使用 register 存储类别可以帮助我们优化代码性能,因为寄存器访问速度比访问内存快。

存储在内存中 CPU寄存器

在 C 语言中,使用 register 关键字声明变量时,它将被存储在 CPU 的寄存器中,而不是存储在内存中。由于寄存器是 CPU 内部的高速存储器,所以变量的访问速度会更快,从而提高程序的执行效率。但需要注意的是,变量的存储位置和寄存器的数量都是由编译器决定的,使用 register 关键字只是向编译器建议将变量存储在寄存器中,但并不保证一定会存储在寄存器中。

需要注意的是,由于寄存器的数量是有限的,所以并不是所有的变量都可以存储在寄存器中。编译器会根据实际情况和编译器选项来决定哪些变量应该存储在寄存器中。因此,使用 register 关键字并不一定会提高程序的性能,有时可能会适得其反。建议只在必要时使用 register 关键字,或者让编译器自动决定变量的存储位置。

举例:例如,在函数内部定义一个 register 变量:

void myFunction() {
    register int x = 0;
    // ...
}

在上述代码中,变量 x 被定义为 register 存储类型,它将被存储在寄存器中(如果编译器允许的话)。由于寄存器的访问速度比内存快,因此访问变量 x 的速度会更快,从而提高程序的性能。


‼️重点:register 的注意事项

  1. 用register修饰的变量只能是局部变量和函数参数,不能是全局变量和静态变量。因为cpu的资源有限,因此不可能一直让一个变量一直占着cpu寄存器

  2. register变量一定是cpu可以接受的值

  3. 不可以用&运算符对register变量进行取址 使用 register 存储类别的变量不能直接获取其内存地址,因为它们可能存储在寄存器中,而寄存器没有地址。 使用 register 存储类别的变量不要取地址,因为这样会导致编译器无法将其存储在寄存器中,从而影响程序的执行效率。

  4. register只是请求寄存器变量,不一定会成功。

总之,使用 register 存储类别应该根据具体的应用场景进行考虑,避免过度使用 register 存储类别,从而影响程序的性能。


 

用于单片机的道理——提高效率

register 存储类别在单片机编程中也经常被使用。由于单片机资源有限,使用寄存器存储变量可以节约存储空间,提高程序执行效率。特别是在需要频繁访问变量的场景下,使用 register 存储类别可以提高程序的响应速度。

举例来说,如果需要在单片机中使用计时器,可以定义一个 register 变量来存储计时器的值。这样,在每次需要使用计时器时,就可以直接访问寄存器中的值,而不需要从内存中读取,从而提高程序的执行效率。

需要注意的是,在单片机编程中使用 register 存储类别需要谨慎,因为寄存器的数量很少,而且需要编译器支持。过度使用 register 存储类别可能会导致编译器无法将变量存储在寄存器中,从而影响程序的性能。因此,需要在具体应用场景下根据实际情况决定是否使用 register 存储类别。


 

总结:四种存储类别对比

下表对 C 语言中的四种存储类别进行了比较,包括了生存周期、作用域、存储方式、存储区域等方面:

存储类别生命周期作用域存储方式存储区域使用场景
auto函数内部块级别RAM用于存储局部变量
static整个程序块级别数据段RAM用于存储局部变量的持久性版本
extern整个程序文件级数据段RAM用于在不同的源文件中共享全局变量
register函数内部块级别寄存器CPU用于提高变量的访问速度

其中,“存储方式”指的是变量在内存中的存储方式,包括栈、堆、数据段和寄存器等;“存储区域”指的是变量在程序执行期间所占用的存储区域,包括 RAM 和 CPU 寄存器等。


Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐