C语言关键字详解(二) 带你全面了解 static 关键字

简介: C语言关键字详解(二) 带你全面了解 static 关键字

一、前言

大家好,欢迎来到C语言深度解析专栏—关键字详解第二篇,在本篇中我们将会对static关键字进行详细介绍,其中要求我们掌握我上一篇中所讲到的全局变量、局部变量、作用域以及生命周期的相关概念,如果对这几个概念比较模糊的同学可以先移步我上一篇博客,下面是博客链接。

C语言关键字详解(一)

二、认识多文件

为了理解static修饰函数的作用,我们需要了解多文件的相关内容

1、多文件的创建

这里我先介绍一下头文件的创建:头文件的创建与.c文件的创建十分相似,仅仅是在选择的时候把c++文件改成.h而已

2020062310470442.png

2020062310470442.png

.h:我们称之为头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header)

.c: 我们称之为源文件,一般包含函数实现,变量定义等 (.c:c语言)

多文件就是在一个.h文件下,包含多个.c文件,比如main.c test1.c test2.c teset3.c …  

2、为什么要有多文件

在一个公司的大型项目中,预期产品所要实现的功能往往是十分复杂的,所以一般都会将功能进行模块化处理,从而便于我们进行代码的复用、代码的修改与维护以及多人协作,自然我们一个程序中就需要多个.c文件

3、为什么要有头文件

单纯的使用源文件,组织项目结构的时候,项目越大越复杂维护成本会变得越来越高!

所以我们在组织项目结构的时候会使用头文件来减少大型项目的维护成本问题。

补充:头文件中 #pragma once 的含义

大家在创建一个.h 头文件的时候会发现编译器在头文件的开头会自动加上 #pragma once

2020062310470442.png

相信有许多小伙伴在曾今或者现在都对这东西有着深深的疑惑,其实它是用来防止头文件被反复包含的,举个栗子

2020062310470442.png

2020062310470442.png

如上所示:我在test.h中包含了头文件<stdio.h>,但是在main.c中我又同时包含了test.h 和 stdio.h ,这就造成了stdio.h被包含了两次,使得程序在编译的时候将stdio.h 里面的内容拷贝了两份,造成代码冗余,而#pragma once 会检查该头文件是否已经被包含,如若是就不在进行拷贝。

防止头文件反复包含的另一种方法(涉及预处理内容,暂时不讲,同学们当作了解即可)

2020062310470442.png

4、多文件在代码中的具体体现

2020062310470442.png

在上图中我们在test.c 文件中中定义了一个全局变量和一个函数,然后在test.h文件中对其进行声明,最后在main.c文件中对全局变量和函数进行打印和调用,我们可以发现,这种做法是可行的,也就是说:全局变量和函数可以跨文件访问的(这个结论在解释下文static作用时会被用到)

三、最名不符实的关键字 - static

static 整体阐述

2020062310470442.png

上图是MSDN对static的解释,翻译过来就是:修改变量时,static关键字指定该变量具有静态持续时间(在程序开始时分配,在程序结束时释放),并将其初始化为0,除非指定了其他值。在文件范围中修改变量或函数时,static关键字指定该变量或函数具有内部链接(其名称在声明它的文件外部不可见)。这段话读起来没什么具体的概念,接下来我从static 作用的三个对象来带大家具体了解static。

1、static 修饰局部变量

2020062310470442.png

2020062310470442.png

2020062310470442.png

图一:test 函数里面定义的 a 是局部变量,局部变量在栈区上开辟空间,栈区的使用特点是进入变量的生命周期时自动为其开辟空间,离开变量的生命周期时自动销毁对应空间,所以这里每次调用 test 函数时 a 都会被重新定义并初始化为0,所以屏幕上打印的是10个1;

图二:我们把 a 用 static 修饰后发现屏幕打印的是1到10,就好像每次调用完 test 函数后 a 并没有被销毁,而是继续使用,下次调用 test 函数时 a 直接在之前的基础上进行 ++ 操作。

所以 static 修饰局部变量的作用是:改变局部变量的生命周期,本质上是改变了局部变量的存储位置,让局部变量不再是在栈区上开辟空间,而是直接在静态区上开辟空间,从而使得局部变量拥有和全局变量一样的生命周期,即随着整个程序生成和销毁。

更深入的理解 static 修饰局部变量的作用:图三,我们的程序从源文件(.c文件)变成可执行程序(.exe文件)需要经过编译链接运行三个环节,而编译环节又分为预处理、编译、汇编三个阶段,在汇编阶段,编译器会把我们的C语言代码转换成汇编代码,而每一条C语言语句都对应着多句汇编代码,然而在图三中,我们可以观察到,只有 static int a = 0; 这条语句没有对应的汇编代码,也就是说,C语言在编译的时候会直接跳过这条语句。

本质上是:在编译环节的编译阶段编译器就会为被 static 修饰的局部变量分配空间,所以C程序在运行的过程中会直接跳过 static 修饰的语句,也就是说,在第二次及以上甚至第一次调用 test 函数时 static int a = 0; 这条语句都不会被执行。

补充:内存分布

要弄清楚这个问题,我们首先得知道内存布局是怎样的:

2020062310470442.png

如图,左边是内存的具体划分,右边是内存的大概划分,在C语言阶段我们只需要记住右边的图就可以了,从图中我们可以看到,局部变量的内存开辟是在栈区上的,而栈区的特点是进入代码块开辟空间,离开代码块释放空间,所以局部变量的作用域和生命周期只在代码块内,而用static的变量则直接在静态区开辟空间,所以变量的生命周期得到延长。

2、static修饰全局变量

2020062310470442.png

20200623104134875.png

图一图二对比分析:我在Add.c中定义了一个全局变量g_val,因为全局变量具有外部链接属性,所以我只需要在test.c中对g_val进行声明之后就可以正常使用了,但是当我用 static 来修饰g_val时,我们发现,编译器说g_val是无法解析的外部符号;

所以 static 修饰全局变量的作用是:改变了全局变量的外部链接属性(可以在其他源文件内被访问),使其变成内部连接属性(只能在本文件内部被访问),给我们的感觉是全局变量的作用域变小了。

3、static修饰函数

2020062310470442.png

20200623104134875.png

图一图二对比分析:这里和 static 修饰全局变量非常类似,我在Add.c中定义了一个Add函数,因为函数也具有外部链接属性,所以我只需要在test.c中对Add函数进行声明之后就可以正常使用了,但是当我用 static 来修饰Add函数时,我们发现,编译器说Add是无法解析的外部符号;

所以 static 修饰函数的作用是:改变了函数的外部链接属性(可以在其他源文件内被访问),使其变成内部连接属性(只能在本文件内部被访问),给我们的感觉是函数的作用域变小了。

四、总结

1、 全局变量和函数是可以跨文件访问的,因为有一定规模的项目,一定是多文件的,多个文件之间,后续一定要进行数据“交互”(test.h test.c main.c) ,如果不能跨文件访问,数据"交互"成本会非常高,所以C语言在设计的时候就规定了全局变量和函数可以跨文件访问

2、 static 修饰局部变量的作用:改变局部变量的生命周期,本质上是改变了局部变量的存储位置,让局部变量不再是在栈区上开辟空间,而是直接在静态区上开辟空间,从而使得局部变量拥有和全局变量一样的生命周期,即随着整个程序生成和销毁。

3、static 修饰全局变量的作用:改变了全局变量的外部链接属性(可以在其他源文件内被访问),使其变成内部连接属性(只能在本文件内部被访问)。

4、static 修饰函数的作用是:改变了函数的外部链接属性(可以在其他源文件内被访问),使其变成内部连接属性(只能在本文件内部被访问)。


相关文章
|
26天前
|
存储 数据可视化 编译器
【C语言】union 关键字详解
联合体(`union`)是一种强大的数据结构,在C语言中具有广泛的应用。通过共享内存位置,联合体可以在不同时间存储不同类型的数据,从而节省内存。在嵌入式系统、硬件编程和协议解析等领域,联合体的使用尤为常见。理解和正确使用联合体可以使代码更加高效和灵活,特别是在内存受限的系统中。
66 3
【C语言】union 关键字详解
|
26天前
|
编译器 C语言
【C语言】extern 关键字详解
`extern` 关键字在C语言中用于跨文件共享变量和函数的声明。它允许你在一个文件中声明变量或函数,而在其他文件中定义和使用它们。理解 `extern` 的使用可以帮助你组织和管理大型项目的代码。
72 3
|
26天前
|
C语言
【C语言】break 关键字详解
- `break` 关键字用于提前退出循环体或 `switch` 语句的执行。 - 在 `for`、`while` 和 `do-while` 循环中,`break` 可以帮助程序在满足特定条件时退出循环。 - 在 `switch` 语句中,`break` 用于终止 `case` 代码块的执行,避免代码“穿透”到下一个 `case`。 - 注意 `break` 只会退出最内层的循环或 `switch` 语句,确保在嵌套结构中正确使用 `break` 以避免意外的控制流行为。
87 2
|
26天前
|
传感器 安全 编译器
【C语言】enum 关键字详解
`enum`关键字在C语言中提供了一种简洁而高效的方法来定义一组相关的常量。通过使用枚举,可以提高代码的可读性、可维护性,并减少错误的发生。在实际应用中,枚举广泛用于表示状态、命令、错误码等,为开发者提供了更清晰的代码结构和更方便的调试手段。通过合理使用枚举,可以编写出更高质量、更易维护的C语言程序。
105 2
|
26天前
|
缓存 安全 编译器
【C语言】volatile 关键字详解
`volatile` 关键字在 C 语言中用于防止编译器对某些变量进行优化,确保每次访问该变量时都直接从内存中读取最新的值。它主要用于处理硬件寄存器和多线程中的共享变量。然而,`volatile` 不保证操作的原子性和顺序,因此在多线程环境中,仍然需要适当的同步机制来确保线程安全。
61 2
|
26天前
|
存储 编译器 程序员
【C语言】auto 关键字详解
`auto` 关键字用于声明局部变量的自动存储类,其作用主要体现在变量的生命周期上。尽管现代C语言中 `auto` 的使用较少,理解其历史背景和作用对于掌握C语言的存储类及变量管理仍然很重要。局部变量默认即为 `auto` 类型,因此在实际编程中,通常不需要显式声明 `auto`。了解 `auto` 关键字有助于更好地理解C语言的存储类及其在不同场景中的应用。
46 1
|
25天前
|
C语言
【C语言】continue 关键字详解
`continue` 关键字在 C 语言中用于跳过当前循环中的剩余代码,并立即开始下一次迭代。它主要用于控制循环中的流程,使程序在满足特定条件时跳过某些代码。
60 1
【C语言】continue 关键字详解
|
26天前
|
存储 C语言
【C语言】static 关键字详解
`static` 关键字在C语言中用于控制变量和函数的作用域和生命周期。它可以用于局部变量、全局变量和函数,具有不同的效果。理解 `static` 关键字的用法有助于封装和管理代码,提高代码的可维护性和可靠性。
39 3
|
25天前
|
C语言
【C语言】return 关键字详解 -《回家的诱惑 ! 》
`return` 关键字在 C 语言中用于终止函数的执行,并将控制权返回给调用者。根据函数的类型,`return` 还可以返回一个值。它是函数控制流中的重要组成部分。
62 2
|
26天前
|
C语言
【C语言】sizeof 关键字详解
`sizeof` 关键字在C语言中用于计算数据类型或变量在内存中占用的字节数。它是一个编译时操作符,对性能没有影响。`sizeof` 可以用于基本数据类型、数组、结构体、指针等,了解和正确使用 `sizeof` 对于内存管理和调试程序非常重要。
52 2