C语言变量的作用域,生命周期和链接相关

这篇具有很好参考价值的文章主要介绍了C语言变量的作用域,生命周期和链接相关。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文介绍C语言的三个很重要的概念:

  • 变量的作用域
  • 变量的生命周期
  • 变量或者函数的链接

写在前面

先介绍一个概念:翻译单元
C语言中有两种文件,头文件.h代码文件.c
翻译单元指的是包含头文件,并且将头文件展开以后的代码文件.c,而每个翻译单元都有一个文件作用域,实际上,他是编译器编译的完整对象单元

变量的作用域

变量的作用域其实是一个范围,只有在这个范围以内变量才是可用的,作用域是可以嵌套的,变量使用的永远是最内层声明或者定义的那个。注意,声明的变量也算,看下面的例子:

#include <stdio.h>
int main(void)
{
    float PI = 2.14;
    printf("PI = %f\n",PI);
    for(int i = 0;i<1;i++)
    {
        extern float PI;
        printf("PI = %f\n",PI);
    }
    return 0;
}

这个程序运行的结果如下:

PI = 2.140000
PI = 3.141593

我在另外一个calc.c文件中定义了一个PI值,所以第二次打印的时候这个extern表示这是一个声明,他的定义在另外一个翻译单元中。

#include "calc.h"
const float PI=3.1415926;

块作用域

一对大括号括起来就形成了一个块作用域
在一个块作用域内声明的变量的可用范围是从这个块中变量定义处到包含该定义块的结尾,块作用域需要注意几个问题:

  • 函数的参数虽然在块的外面,但是他也属于函数所在的块
    这个一般的理解其实就是函数参数无论是保存在寄存器还是保存在栈上都是在函数调用(call)开始之后进行的
  • 对于if,while,for等循环或者判断的情况,如果没有用花括号括起来,也算是块的一部分,并且我们可以将这些情况理解为两个块,比如for语句,整个for语句看成是一个块,for后面的循环体执行逻辑作为这个块的子块,这样很多问题的比较容易理解了,看下面的例子:
#include <stdio.h>
int main(void)
{
    for(int i = 0;i<10;i++)
    {
        printf("%d|",i);
        int i = 6;
        printf("%d, ",i);
    }
    printf("\n");
    return 0;
}

输出结果:

0|6, 1|6, 2|6, 3|6, 4|6, 5|6, 6|6, 7|6, 8|6, 9|6, 

在第一次执行循环体的时候,定义了一个i = 6,但是再一次回到for,执行i++的时候依然是用的在for语句定义的i,我们可以把for语句这样写一下:

{//第一层块
	int i = 0;
	执行for循环
	{//第二层块
		printf("%d|",i);
		int i = 6;
		printf("%d, ",i);
	}
}

这样每次执行for循环的时候内部定义的i就失效了,使用的还是外部定义的i变量

函数作用域

函数作用域指的是即使在函数的内层块中,也会自动提升作用域到整个函数,这种作用域只用于goto的标签(是不是很没有用!!),也就是即使goto的标签定义在一个函数内层块中,也不能在另一个块中定义相同的标签,因为标签的作用域是整个函数,看下面例子:

#include <stdio.h>
int main()
{
    goto b;
    {
    b:
       printf("bbb");
    }
    return 0;
}

运行会打印字符串bbb,虽然标签b定义在一个函数内层块中,但是他具有函数作用域
否则,我们可以在一个函数的另一个块中定义一个同名的标签跳转,这样我们如果使用goto语句的话就太混乱了。所以C语言才有函数作用域这么个东西

函数原型作用域

函数原型作用域针对函数的声明中形参起作用,形参的作用域从定义开始到函数原型定义结束,一般这种情况是针对于函数声明中有可变数组的情况下,比如下面的声明:

void reset(int m,int n,int arr[m][n],int val);

文件作用域

如果一个变量定义在函数外面,就说该变量具有文件作用域,定义在文件中的函数也具有文件作用域,也就是文件中所有函数都可以直接使用,根据变量或函数定义是否添加static关键字,可以分为具有内部链接的文件作用域和具有外部链接的文件作用域

  • 内部链接的文件作用域:只有当前文件内可以使用
  • 外部链接的文件作用域:外部文件也可以使用,不过外部文件使用时需要使用extern进行声明,这样编译器编译的时候就会在符号表中为该变量或者函数添加符号,链接器在链接的时候就会根据符号去别的文件中寻找定义

变量的生命周期

变量的生命周期就是变量在程序运行过程中可用的时间段,主要有四种:

  • 静态变量
  • 线程变量
  • 局部变量或者叫自动变量
  • 动态变量

静态变量

静态变量也叫全局变量,是指在程序运行过程中一直存在的变量,所谓静态变量,就是指变量不是存储在运行栈或者运行堆上,而是放在固定的内存位置,比如.data数据块等地方。C语言有如下几种静态变量:

  1. 定义在函数外面的变量,也就是具有文件作用域的变量,无论是否使用static修饰,都是全局变量
  2. 使用extern声明的变量,虽然定义在别的地方,但是也是静态变量
  3. 在函数内部使用static关键字定义的变量,该变量虽然作用域只限于当前定义的函数内部,但是在整个程序运行期间都存在,并且我们通过指针使用该变量,看下面例子:
#include <stdio.h>
int* get();
int main(void)
{
    int c = *get();
    printf("%d\n",c);
    return 0;
}

int* get()
{
    static int a = 10;
    return &a;
}

运行结果

10

线程变量

线程变量是通过使用关键字_Thread_local声明的变量,每个线程都会获取变量的一个单独的备份,看下面的例子:

#include <stdio.h>
#include <pthread.h>
_Thread_local int threadVar = 10;
void threadHandle();
int main(void)
{
    int c = 0;
    for(int i = 0;i<6;i++)
    {
        pthread_t threadNo;
        if((c = pthread_create(&threadNo, NULL, threadHandle, NULL)) != 0)
        {
            printf("thread create failed. errno:%d",c);
            continue;
        }
    }
    return 0;
}

void threadHandle()
{
    threadVar++;
    printf("threadHandle::%d\n",threadVar);
    pthread_exit(0);
}

输出

threadHandle::11
threadHandle::11
threadHandle::11
threadHandle::11
threadHandle::11

局部变量或者叫自动变量

局部变量是指在块内部定义的变量,该变量或者在函数执行期间通过寄存器保存,或者保存在程序栈上。该变量在块结束后就失效。
注意:自动变量不会自动初始化,所以一定要初始化自动变量,不然值是不确定的

动态变量

动态变量是指变量的定义和内存分配是通过用户控制,在程序运行期间动态执行的,动态变量一般分配在程序堆上,通过制定的函数接口来分配和释放
使用动态函数需要引用头文件

#include <stdlib.h>

malloc

该函数接受一个参数:所需的内存字节数,然后去找合适的空闲内存块,返回内存的地址。

void	*malloc(size_t __size)

注意:
这个函数分配的内存是未初始化的
如果分配内存失败,返回NULL

free

该函数接受一个参数,就是前面malloc返回的内存指针,该函数的作用是释放分配的内存

可以看出动态内存非常灵活,比如我们可以在程序运行过程中动态创建数组,链表等数据结构,但是动态分配的内存在程序运行期间会一直增加,除非你用free函数释放内存。并且,有些操作系统,即使应用程序结束,动态分配的内存也不会释放,所以,使用动态内存需要注意一下几点:

  1. 需要维护好动态分配的内存指针,并且在不使用的时候用free释放
  2. 释放后的指针需要设置成NULL,避免free多次

calloc

calloc和malloc一样,也是用于动态分配内存,但是calloc是按照存储单元分配内存,该函数接受两个参数,第一个参数是所需的存储单元的数量,第二个参数是存储单元占用的字节数。比如下面的例子:

long* newmem = (long*)calloc(100,sizeof(long));

这种分配方式有个好处就是即使一个系统上long类型占用的字节数不是四个字节,该函数分配的内存依然够用。

free函数也可用于释放calloc分配的内存

变量或者函数的链接

链接的原因

之所以要有链接的概念,是因为我们为了编写大型应用程序,需要将程序进行模块化,分成一个个的翻译单元,比如我们创建一个游戏引擎的程序,需要

  • 日志模块
  • 内存管理模块
  • 音视频解码
  • 图像解码
  • 数学库
  • 材质管理
  • 多媒体播放
  • 事件系统
  • 动画系统
  • 渲染模块
  • 等等
    但是各个模块之间还会相互调用,比如我在数学库中定义的一个矩阵相乘的方法,可能在很多别的模块也需要,但是函数的定义在数学库中,别的模块怎么找到呢,这就是链接的目的,链接就是寻找在外部定义的变量或者函数的地址

链接的方式

C语言中能被外部函数或者内存函数使用的变量或者函数只有两种,前面讲过,就是具有文件作用域的变量或函数:文章来源地址https://www.toymoban.com/news/detail-756075.html

  • 外部链接:函数外部声明的函数或者变量,没有使用static修饰符,具有外部链接。外部使用时必须使用extern进行声明我们通常把一个翻译单元的外部链接变量或者函数使用extern在头文件中集中进行声明,别的翻译单元只需要引用该头文件即可使用
  • 内部链接:函数外部声明的函数或者变量,使用static修饰符,具有内部链接。内部链接的变量或函数只能供当前翻译单元使用。

到了这里,关于C语言变量的作用域,生命周期和链接相关的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++的作用域、变量作用域、生命周期和变量分类介绍

    C++ 中的作用域(scope)指的是变量、函数或其他标识符的可见和可访问的范围。 变量作用域(Variable Scope)是指变量的生命周期和可见性,也就是变量在程序中的哪些部分可以使用。按照作用域(Scope)变量可分类为全局变量和局部变量。 还可按照生命周期(Lifetime)进行分

    2024年02月13日
    浏览(44)
  • 44-js return返回值,全局作用域,局部作用域,隐式作用域,变量的生命周期,delete释放内存

    1. return 返回值:函数执行后剩下结果就是返回值。 2.作用域 查找变量的时候,会从当前作用域开始查找,如果当前作用域查找不到,逐层(外层)向上查找 2.1全局作用域 2.2局部作用域 

    2024年02月02日
    浏览(50)
  • 【C语言】函数(涉及生命周期与作用域)

    函数:是指程序中的实现某项特定需求的一小段代码 (容易跟数学上函数混淆),程序中函数翻译称为 子程序 。通常也称为 接口 ( 接口是内外连续的窗口,实现不同的功能和效果 ) 程序其实是由无数个小的函数组成,比如:我们编写 int main() 也是属于函数。函数就是运用\\\"大事

    2024年04月11日
    浏览(41)
  • Bean作用域和生命周期

    hi,今天为大家带啦Bean的作用域和生命周期的相关知识 Bean的作用域和我们之前学过的不一样,我们之前学的作用域是一个范围,而现在指的是 Bean在Spring框架中的某种行为模式,也就是一个动作. 这样干巴巴的说看我可能无法理解,我们来举个例子 创建一个公共类的一个公共对象

    2024年02月15日
    浏览(52)
  • Bean 作用域和生命周期

    Spring 容器是用来存储和读取 Bean 的 , 因此 Bean 是 Spring 中最核心的操作资源. 编写代码过程中 , bean 对象如果有多个属性 , 创建 Getter , Setter, 构造方法 等方法 , 会产生大量冗长的代码. 那么为了使代码更加简洁 , 我们可以使用 Lombok 框架 , 只需要一行注释 , 就可以避免大量冗长

    2024年02月05日
    浏览(77)
  • Spring的作用域与生命周期

    lombok插件可以提供给我们一些注释,这些注释可以很好的帮助我们消除Java代码中大量冗余的代码,可以使得我们的Java类可以看起来非常的干净整洁 1.安装lombok插件 2.pom.xml导入依赖 3.基本使用 我们创建一个简单的User实体类 如果我们手动的在类中加入setter+getter,toString等等,如

    2023年04月24日
    浏览(33)
  • Bean的作用域和生命周期

    目录 1.作⽤域定义 1.1Bean的6个作用域 1.singleton:单例作用域 2.prototype:多例作用域 3.request:请求作用域 4.session:会话作用域 5.application:全局作用域 6.websocket:HTTP WebSocket作用域 单例作⽤域(singleton) VS 全局作⽤域(application) 1.2设置作用域 1.直接设置值@Scope(\\\"potptype\\\") 2.用枚举设置:@Scop

    2024年02月02日
    浏览(85)
  • Spring Bean作用域与生命周期

    目录 Bean的作用域: Bean有六大行为模式 1、singleton:单例模式(默认) 2、prototype: 原型模式(多例模式) 3、request: 请求作用域(Spring MVC) 4、session: 会话作用域(Spring MVC) 5、application: 全局作用域(Spring MVC) 6、websocket: HTTP WebSocket 作用域(Spring WebSocket) applicationContext和singleton的区别  Bea

    2024年02月02日
    浏览(42)
  • Spring(Bean 作用域和生命周期)

    目录 1. 案例1: Bean作用域的问题 2. 作用域 3. 设置 Bean 的作用域 4. Spring 的执行流程 5. Bean 的生命周期 现在有一个公共的 Bean,通过给 A 用户 和 B 用户使用, 然后在使用的过程中 A 偷偷的修改了公共 Bean 的数据, 导致 B 在使用时发生了预期之外的逻辑错误 (1) 公共 Bean:   [名称是

    2024年02月19日
    浏览(98)
  • Spring | Bean 作用域和生命周期

    Spring 是用来读取和存储 Bean,因此在 Spring 中 Bean 是最核心的操作资源,所以接下来我们深入学习⼀下 Bean 对象 假设现在有⼀个公共的 Bean,提供给 A 用户和 B 用户使用,然而在使用的途中 A 用户却 “悄悄” 地修改了公共 Bean 的数据,导致 B 用户在使用时发生了预期之外的逻

    2024年02月14日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包