【C语言程序设计】第26篇:变量的作用域与生命周期

1 引言

考虑下面这段代码:

c

复制代码
#include <stdio.h>

int global = 100;  /* 全局变量 */

void func(void)
{
    int local = 10;      /* 局部变量 */
    static int static_local = 0;  /* 静态局部变量 */
    
    local++;
    static_local++;
    
    printf("local=%d, static_local=%d, global=%d\n", 
           local, static_local, global);
}

int main(void)
{
    func();
    func();
    func();
    return 0;
}

输出:

text

复制代码
local=11, static_local=1, global=100
local=11, static_local=2, global=100
local=11, static_local=3, global=100

为什么每次调用funclocal总是11,而static_local会累加?为什么global一直存在?这就是作用域和生命周期在起作用。


2 基本概念

2.1 作用域

作用域(Scope)是指程序中可以访问该变量的区域。通俗地说,就是变量在哪些地方"可见"。

C语言中的作用域分为:

  • 块作用域 :在代码块 { } 内定义的变量

  • 文件作用域:在所有函数之外定义的变量

  • 函数作用域 :仅适用于goto标签

  • 函数原型作用域:函数声明中的参数

本章主要关注块作用域和文件作用域。

2.2 生命周期

生命周期(Lifetime)是指变量在内存中存在的时间段。从变量分配内存到释放内存的整个过程。

C语言中的生命周期分为:

  • 静态存储期:程序开始到结束

  • 自动存储期:进入代码块到退出代码块

  • 动态存储期mallocfree(后续章节讨论)

2.3 存储类别

C语言提供了几种存储类别说明符,用于控制变量的作用域和生命周期:

存储类别 关键字 作用域 生命周期 默认初始值
自动 auto 块作用域 自动存储期 不确定
寄存器 register 块作用域 自动存储期 不确定
静态无链接 static 块作用域 静态存储期 0
静态外部链接 无(或extern 文件作用域 静态存储期 0
静态内部链接 static 文件作用域 静态存储期 0

3 局部变量

3.1 定义与特性

局部变量(Local Variable)是在函数内部或代码块内定义的变量。

c

复制代码
void func(void)
{
    int local_var;        /* 局部变量 */
    int another = 10;     /* 可初始化 */
    
    if (local_var > 0) {
        int block_var;     /* 块级局部变量 */
    }
}

特性

  • 作用域:从定义处开始,到所在块结束

  • 生命周期:进入块时创建,退出块时销毁(自动存储期)

  • 默认初始值:不确定(垃圾值)

  • 存储位置:栈

3.2 作用域规则

c

复制代码
#include <stdio.h>

void func(void)
{
    int x = 10;  /* 函数级局部变量 */
    
    if (x > 0) {
        int x = 20;  /* 块级局部变量,隐藏外层的x */
        printf("块内:x = %d\n", x);  /* 输出20 */
    }
    
    printf("函数内:x = %d\n", x);    /* 输出10 */
}

int main(void)
{
    func();
    /* printf("%d\n", x); */  /* 错误!x在此处不可见 */
    return 0;
}

变量隐藏:内层块中可以定义与外层同名的变量,此时内层变量"隐藏"外层变量。

3.3 生命周期示例

c

复制代码
#include <stdio.h>

void func(void)
{
    int count = 0;
    count++;
    printf("count = %d\n", count);
}

int main(void)
{
    func();  /* 输出 count = 1 */
    func();  /* 输出 count = 1 */
    func();  /* 输出 count = 1 */
    return 0;
}

每次调用funccount都被重新创建并初始化为0,所以始终输出1。

3.4 auto关键字

auto是C语言中用于显式声明自动存储类别的关键字,但几乎从不使用,因为局部变量默认就是auto

c

复制代码
void func(void)
{
    auto int x;  /* 等价于 int x; */
}

4 全局变量

4.1 定义与特性

全局变量(Global Variable)是在所有函数之外定义的变量。

c

复制代码
#include <stdio.h>

int global_count = 0;        /* 全局变量,可被其他文件访问 */
static int file_count = 0;   /* 静态全局变量,仅本文件可见 */

void func1(void)
{
    global_count++;
    file_count++;
}

void func2(void)
{
    global_count++;
    file_count++;
}

int main(void)
{
    func1();
    func2();
    printf("global_count = %d\n", global_count);  /* 2 */
    printf("file_count = %d\n", file_count);      /* 2 */
    return 0;
}

特性

  • 作用域:从定义处到文件结束(文件作用域)

  • 生命周期:程序开始到结束(静态存储期)

  • 默认初始值:0(如果没有显式初始化)

  • 存储位置:数据段(初始化)或BSS段(未初始化)

4.2 跨文件访问全局变量

如果要在其他文件中访问全局变量,需要使用extern声明:

c

复制代码
/* file1.c */
int global = 100;  /* 定义全局变量 */

/* file2.c */
extern int global;  /* 声明,告诉编译器这个变量在其他文件 */

void func(void)
{
    global = 200;  /* 可以访问 */
}

4.3 静态全局变量

static修饰的全局变量具有内部链接,只能在定义它的文件内访问:

c

复制代码
/* file1.c */
static int hidden = 100;  /* 只能在本文件使用 */

/* file2.c */
extern int hidden;  /* 错误!无法链接到static变量 */

4.4 全局变量的风险

c

复制代码
#include <stdio.h>

int counter = 0;  /* 全局变量 */

void increment(void)
{
    counter++;
}

void reset(void)
{
    counter = 0;
}

int main(void)
{
    increment();
    increment();
    printf("%d\n", counter);  /* 2 */
    reset();
    printf("%d\n", counter);  /* 0 */
    return 0;
}

全局变量的问题

  1. 命名冲突:容易与其他文件或库的变量重名

  2. 代码耦合:函数依赖于全局变量,难以独立测试

  3. 调试困难:任何函数都可能修改它,难以追踪

  4. 线程不安全:多线程环境下需要同步

最佳实践:尽量少用全局变量,必要时用静态全局变量限制作用域。


5 静态局部变量

5.1 定义与特性

静态局部变量 (Static Local Variable)是用static修饰的局部变量。

c

复制代码
#include <stdio.h>

void counter(void)
{
    static int count = 0;  /* 静态局部变量 */
    count++;
    printf("count = %d\n", count);
}

int main(void)
{
    counter();  /* 输出 count = 1 */
    counter();  /* 输出 count = 2 */
    counter();  /* 输出 count = 3 */
    return 0;
}

特性

  • 作用域:块作用域(和普通局部变量一样)

  • 生命周期:程序开始到结束(静态存储期)

  • 默认初始值:0(如果没有显式初始化)

  • 存储位置:数据段(初始化)或BSS段(未初始化)

  • 初始化:只在程序启动时初始化一次

5.2 初始化时机

c

复制代码
#include <stdio.h>

void func(void)
{
    static int x = 10;  /* 程序启动时初始化一次 */
    static int y;       /* 程序启动时初始化为0 */
    
    printf("x = %d, y = %d\n", x++, y++);
}

int main(void)
{
    func();  /* x=10, y=0 */
    func();  /* x=11, y=1 */
    func();  /* x=12, y=2 */
    return 0;
}

静态局部变量的初始化只在程序启动时执行一次,后续调用不再重新初始化。

5.3 应用场景

5.3.1 保持函数状态

c

复制代码
#include <stdio.h>

int next_id(void)
{
    static int id = 1000;  /* 从1000开始 */
    return id++;
}

int main(void)
{
    for (int i = 0; i < 5; i++) {
        printf("ID: %d\n", next_id());
    }
    return 0;
}
5.3.2 一次性初始化

c

复制代码
#include <stdio.h>

void initialize(void)
{
    static int initialized = 0;
    
    if (!initialized) {
        printf("执行一次性初始化...\n");
        /* 执行初始化操作 */
        initialized = 1;
    }
    
    printf("函数正常执行\n");
}

int main(void)
{
    initialize();
    initialize();
    initialize();
    return 0;
}

输出:

text

复制代码
执行一次性初始化...
函数正常执行
函数正常执行
函数正常执行
5.3.3 计数器

c

复制代码
#include <stdio.h>

int factorial(int n)
{
    static int depth = 0;  /* 递归深度计数 */
    depth++;
    printf("depth: %d\n", depth);
    
    if (n <= 1) {
        depth--;  /* 返回时减少深度 */
        return 1;
    }
    int result = n * factorial(n - 1);
    depth--;
    return result;
}

6 存储类别对比

6.1 三种变量的对比

特性 局部变量 静态局部变量 全局变量
作用域 定义它的块内 定义它的块内 整个文件(可扩展)
生命周期 函数调用期间 整个程序运行期 整个程序运行期
存储位置 数据段/BSS 数据段/BSS
默认初始值 垃圾值 0 0
可被其他文件访问 是(除非static)
同名隐藏 可被内层隐藏 可被内层隐藏 可被局部隐藏

6.2 存储类别小结

c

复制代码
#include <stdio.h>

int g1 = 10;           /* 全局变量,外部链接 */
static int g2 = 20;    /* 全局变量,内部链接(本文件私有) */

void func(void)
{
    int a = 30;            /* 局部变量,自动存储期 */
    static int b = 40;     /* 静态局部变量,静态存储期 */
    register int c = 50;   /* 建议放到寄存器(编译器决定) */
    
    a++;  /* 每次调用重新初始化,所以永远是31?不,每次都从30开始 */
    b++;  /* 保持上次的值 */
    
    printf("a=%d, b=%d\n", a, b);
}

int main(void)
{
    func();  /* a=31, b=41 */
    func();  /* a=31, b=42 */
    func();  /* a=31, b=43 */
    return 0;
}

7 常见错误与注意事项

7.1 使用未初始化的局部变量

c

复制代码
void func(void)
{
    int x;          /* 未初始化,值是垃圾 */
    if (x > 0) {    /* 危险!x的值不确定 */
        /* ... */
    }
}

7.2 在函数内返回局部变量的地址

c

复制代码
int* get_int(void)
{
    int x = 100;
    return &x;  /* 危险!x是局部变量,函数返回后销毁 */
}

7.3 混淆静态局部变量和全局变量

c

复制代码
int counter = 0;  /* 全局变量 */

void func(void)
{
    static int counter = 0;  /* 静态局部,隐藏全局的counter */
    counter++;
    printf("%d\n", counter);
}

int main(void)
{
    func();  /* 输出1 */
    func();  /* 输出2 */
    printf("%d\n", counter);  /* 输出0(全局的) */
    return 0;
}

7.4 全局变量滥用

c

复制代码
/* 不好的设计 */
int total;  /* 全局变量,到处被修改 */

void add(int x) { total += x; }
void sub(int x) { total -= x; }
void mul(int x) { total *= x; }

/* 更好的设计:封装成模块 */
static int total;  /* 文件内静态,外部不可见 */

void add(int x) { total += x; }
void sub(int x) { total -= x; }
int get_total(void) { return total; }  /* 提供访问接口 */

7.5 忽略静态局部变量的初始化

c

复制代码
void func(void)
{
    static int x;  /* 自动初始化为0 */
    x++;
    printf("%d\n", x);  /* 第一次输出1 */
}

void func2(void)
{
    static int x = 0;  /* 显式初始化,效果相同 */
    x++;
    printf("%d\n", x);
}

7.6 误解register关键字

c

复制代码
register int x;  /* 建议编译器将x放到寄存器 */
/* 但不能对register变量取地址 */
int *p = &x;     /* 错误!不能取register变量的地址 */

现代编译器优化能力很强,很少需要手动使用register


8 综合示例

8.1 银行账户模拟

c

复制代码
#include <stdio.h>
#include <string.h>

/* 账户模块 - 使用静态全局变量封装内部状态 */
static double balance = 0.0;      /* 余额,文件内私有 */
static int transaction_count = 0; /* 交易次数统计 */

/* 公开接口 */
double get_balance(void)
{
    return balance;
}

int get_transaction_count(void)
{
    return transaction_count;
}

void deposit(double amount)
{
    if (amount > 0) {
        balance += amount;
        transaction_count++;
        printf("存入 %.2f,当前余额 %.2f\n", amount, balance);
    }
}

int withdraw(double amount)
{
    if (amount <= 0) {
        return -1;  /* 金额无效 */
    }
    if (amount > balance) {
        return -2;  /* 余额不足 */
    }
    balance -= amount;
    transaction_count++;
    printf("取出 %.2f,当前余额 %.2f\n", amount, balance);
    return 0;  /* 成功 */
}

int main(void)
{
    deposit(1000);
    deposit(500);
    withdraw(200);
    withdraw(2000);  /* 余额不足 */
    
    printf("最终余额:%.2f\n", get_balance());
    printf("交易次数:%d\n", get_transaction_count());
    
    return 0;
}

8.2 生成唯一ID

c

复制代码
#include <stdio.h>

/* 生成唯一ID的函数 */
unsigned long generate_id(void)
{
    static unsigned long counter = 0;
    return counter++;
}

/* 带前缀的ID生成器 */
const char* generate_prefixed_id(char *buffer, size_t size, const char *prefix)
{
    static unsigned long counter = 0;
    snprintf(buffer, size, "%s-%lu", prefix, counter++);
    return buffer;
}

int main(void)
{
    for (int i = 0; i < 5; i++) {
        printf("ID: %lu\n", generate_id());
    }
    
    char id_buf[50];
    for (int i = 0; i < 3; i++) {
        printf("订单号:%s\n", generate_prefixed_id(id_buf, sizeof(id_buf), "ORD"));
    }
    
    return 0;
}

8.3 函数调用次数统计

c

复制代码
#include <stdio.h>

void important_function(void)
{
    static int call_count = 0;
    call_count++;
    
    printf("important_function 被调用 %d 次\n", call_count);
    
    /* 函数的主要功能 */
}

int get_function_call_count(void)
{
    static int count = 0;  /* 这其实没用,因为没法从外部获取 */
    /* 需要另外的方式,这里只是演示 */
    return 0;
}

int main(void)
{
    important_function();
    important_function();
    important_function();
    important_function();
    
    return 0;
}

9 本章小结

本章系统介绍了变量的作用域与生命周期:

1. 基本概念

  • 作用域:变量可见的区域(块作用域、文件作用域)

  • 生命周期:变量存在的时间段(自动存储期、静态存储期)

2. 局部变量

  • 作用域:定义它的块内

  • 生命周期:进入块时创建,退出块时销毁

  • 默认值:不确定(必须初始化后才能使用)

  • 存储位置:栈

3. 全局变量

  • 作用域:整个文件(可通过extern扩展)

  • 生命周期:程序开始到结束

  • 默认值:0

  • 存储位置:数据段/BSS

  • 静态全局变量(static):限制在当前文件

4. 静态局部变量

  • 作用域:定义它的块内(同局部变量)

  • 生命周期:程序开始到结束(同全局变量)

  • 默认值:0

  • 存储位置:数据段/BSS

  • 初始化只执行一次

5. 对比总结

变量类型 作用域 生命周期 默认值 存储位置
局部变量 块内 函数调用期间 垃圾值
静态局部 块内 整个程序 0 数据段/BSS
全局变量 文件 整个程序 0 数据段/BSS

6. 最佳实践

  • 尽量使用局部变量

  • 限制全局变量的使用

  • 用静态局部变量保持函数状态

  • 用静态全局变量实现模块封装

  • 避免返回局部变量的地址

相关推荐
健康平安的活着1 小时前
java8案例对list[过滤、分组,转换,查找等]清洗逻辑
java·数据结构·list
2401_898075121 小时前
C++中的智能指针详解
开发语言·c++·算法
花间相见1 小时前
【JAVA基础09】—— 赋值与三元运算符:从基础到实操的避坑指南
java·开发语言·python
格林威1 小时前
工业相机图像高速存储(C#版):直接IO(Direct I/O)方法,附Basler相机实战代码!
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
大头流矢1 小时前
STL中的string容器和迭代器iterator
开发语言·c++
IOT-Power1 小时前
Qt+C++ 控制软件架构实例
开发语言·c++·qt
顾温1 小时前
c# 多线程
开发语言·c#
Elsa️7461 小时前
排序算法实现(基于408)
数据结构·算法·排序算法
xiaoye-duck2 小时前
《算法题讲解指南:递归,搜索与回溯算法--二叉树中的深搜》--10.二叉搜索树中第k小的元素,11.二叉树的所有路径
c++·算法·深度优先·递归