【C语言-全局变量】

news2025/4/13 11:32:35

【C语言-全局变量】

  • 1.能局部就局部,别啥都往全局塞
  • 2.尽量用结构体对零散变量封装
  • 3.函数传参
  • 4.静态变量+模块化
  • 5 单例模式, 限制全局实例数量
  • 6. 配置化全局参数——集中管理可调参数
  • 7. 事件驱动架构:消息队列通信
  • 策略选择建议

参考https://mp.weixin.qq.com/s/Ke134-r6dzz-WPYrdP8G7A并用deepseek扩展

1.能局部就局部,别啥都往全局塞

烂代码(全局变量版本):

uint16_t adc_raw;       // 全局变量:整个程序都可以访问和修改  
uint16_t adc_processed; // 全局变量:同样可以被任何地方修改  

void get_adc_data(void) {  
    adc_raw = ADC_Read();      // 直接存储到全局变量  
    adc_processed = adc_raw * 2; // 处理后存储到全局变量  
}  

好代码(局部变量版本):

uint16_t get_adc_data(void) {  
    uint16_t raw = ADC_Read();     // 局部变量:只在函数内部存在  
    uint16_t processed = raw * 2;  // 局部变量:处理数据  
    return processed;               // 用完即走  
}  

我来举一个具体的例子来说明这两种方式的区别:
想象你是一个银行出纳员。全局变量就像是把所有客户的现金都放在一个开放的柜台上,任何人都可以接触和修改。而局部变量就像是每个客户都有自己的私密交易窗口,交易完成后窗口立即关闭。
全局变量的潜在问题:
意外修改风险

uint16_t adc_raw;  // 全局变量  

void process_adc() {  
    adc_raw = ADC_Read();  
}  

void debug_print() {  
    // 突然有人在这里修改了adc_raw!  
    adc_raw = 0;   
}  

void main() {  
    process_adc();  
    // 你可能期望adc_raw还是原来的值,但实际上已经被意外修改  
}  

局部变量的优势:
作用域受限
内存管理更高效
代码可读性更好
减少意外修改的可能性

uint16_t safe_adc_processing() {  
    uint16_t raw = ADC_Read();     // 只在这个函数内存在  
    uint16_t processed = raw * 2;  // 处理数据  
    return processed;               // 函数结束,变量立即销毁  
}  

void main() {  
    uint16_t result = safe_adc_processing();   
    // 变量生命周期非常清晰和可控  
}  

实际应用场景
假设你在开发一个嵌入式温度监测系统:

// 不好的做法:全局变量到处乱飞  
uint16_t global_temperature;  
uint16_t global_humidity;  

void read_sensors() {  
    global_temperature = read_temperature_sensor();  
    global_humidity = read_humidity_sensor();  
}  

// 好的做法:局部变量,职责清晰

typedef struct {  
    uint16_t temperature;  
    uint16_t humidity;  
} SensorData;  

SensorData collect_sensor_data() {  
    uint16_t temp = read_temperature_sensor();  
    uint16_t humid = read_humidity_sensor();  
    
    return (SensorData){   
        .temperature = temp,   
        .humidity = humid   
    };  
}  

关键收获
局部变量就像"用完即走"的快餐盒
全局变量像是一个公共食堂,容易被意外"污染"
局部变量提供了更好的封装和安全性
内存管理更加高效和可预测
建议:
尽可能使用局部变量
如果必须使用全局变量,要非常谨慎
使用const关键字进一步限制变量修改
考虑使用结构体和函数返回值来管理数据

2.尽量用结构体对零散变量封装

原始代码(散乱的全局变量):

// 全局变量分散,难以维护
uint8_t led_brightness;  // LED亮度
uint8_t led_mode;        // LED模式(0-常亮,1-呼吸)
bool led_enabled;        // LED开关状态

void update_led() {
    if (led_enabled) {
        if (led_mode == 0) {
            led_brightness = 100;  // 常亮模式
        } else {
            led_brightness = 50;   // 呼吸模式(示例简化)
        }
    }
}

优化后的代码(结构体封装):

// 将LED相关变量封装成结构体
typedef struct {
    uint8_t brightness;  // 亮度
    uint8_t mode;        // 模式(0-常亮,1-呼吸)
    bool enabled;        // 开关状态
} LedController;

// 初始化一个LED控制器实例
LedController led = {0, 0, false};

void update_led() {
    if (led.enabled) {
        if (led.mode == 0) {
            led.brightness = 100;  // 常亮模式
        } else {
            led.brightness = 50;   // 呼吸模式
        }
    }
}

关键好处解释:
变量集中管理
所有LED相关的变量(亮度、模式、开关状态)被归拢到 LedController 结构体中,代码一目了然,不再需要全局搜索 led_ 开头的变量。

命名更简洁明确
原始代码中变量名需要加 led_ 前缀避免冲突(如 led_brightness),而结构体成员名无需冗余前缀,直接通过 led.brightness 访问,语义更清晰。

便于扩展和维护
若新增功能(例如增加颜色参数),只需在结构体中添加成员:

typedef struct {
    uint8_t brightness;
    uint8_t mode;
    bool enabled;
    uint32_t color;  // 新增颜色参数
} LedController;

无需新增全局变量,改动集中且安全。

支持多实例化
如果需要控制多个LED,只需创建多个结构体实例:

LedController led1 = {0, 0, false};
LedController led2 = {0, 1, true};

而用全局变量实现多实例会导致变量名爆炸(如 led1_brightness, led2_mode)。

函数参数传递更高效
若需将LED状态传递给函数,只需传递结构体指针,而非多个单独变量:

void set_led_mode(LedController *led, uint8_t mode) {
    led->mode = mode;
}

总结
通过结构体封装,代码从“散乱的全局变量”变为“模块化的数据集合”,显著提升了可读性、可维护性和扩展性,尤其适合嵌入式系统中管理硬件外设(如定时器、LED、传感器)的状态。

3.函数传参

原始代码(依赖全局变量)

// 全局变量控制电机状态
uint8_t motor_speed = 0;   // 电机转速
uint8_t motor_enabled = 0; // 电机开关

// 全局函数直接操作全局变量
void set_motor() {
    if (motor_enabled) {
        PWM_SetDutyCycle(motor_speed);  // 假设是PWM控制电机
    } else {
        PWM_Stop();                     // 关闭电机
    }
}

优化后的代码(通过参数传递状态)

// 函数通过参数接收状态,不依赖全局变量
void set_motor(uint8_t enabled, uint8_t speed) {
    if (enabled) {
        PWM_SetDutyCycle(speed);
    } else {
        PWM_Stop();
    }
}
// 调用示例:开启电机,转速50%
set_motor(1, 50);

关键优势解释

  1. 消除隐式依赖,明确函数职责
    烂代码问题:
    原函数 set_motor() 隐式依赖全局变量 motor_enabled 和 motor_speed。
    调用函数时,需先确保全局变量已正确赋值,函数行为与外部状态强耦合。

优化后:
函数 set_motor(enabled, speed) 明确要求传入 enabled 和 speed 参数。
函数仅对输入参数负责,行为完全由调用者控制,职责清晰。

  1. 避免副作用,提升代码可预测性
    烂代码问题:
    若其他函数意外修改了 motor_speed,set_motor() 的行为会悄然改变,引发难以追踪的 Bug。

优化后:
函数内部不依赖外部状态,相同的参数输入必然产生相同的行为,调试时只需关注参数来源,无需排查全局变量。

  1. 支持并发和复用
    烂代码问题:
    全局变量导致函数无法在多任务或中断环境中安全调用(例如:motor_speed 可能在函数执行中被其他线程修改)。

优化后:
参数传递的是瞬时值,函数执行期间状态不会变化。
还可轻松扩展为控制多个电机(例如通过额外参数指定电机ID)。

  1. 便于测试和维护
    烂代码问题:
    单元测试时需先初始化全局变量,测试用例互相干扰。

优化后:
直接测试函数逻辑:

// 测试用例:当 enabled=1, speed=50 时,是否调用了 PWM_SetDutyCycle(50)
set_motor(1, 50);

无需关心全局状态,测试更简单可靠。

4.静态变量+模块化

场景:温度传感器数据管理
假设我们需要管理一个温度传感器的采样值和报警标志。

原始代码(全局变量污染)

// 全局变量在头文件中暴露,所有文件都可随意修改
float temperature = 0.0f;    // 当前温度
bool overheat_alarm = false; // 过热报警标志

void read_temperature() {
    temperature = read_sensor();  // 读取传感器
    if (temperature > 100.0f) {
        overheat_alarm = true;    // 触发报警
    }
}

问题:任何文件都可以直接修改 temperature 或 overheat_alarm,导致数据被意外篡改,调试困难。

优化代码(static变量 + 模块化)

// 文件:temperature.c
#include "sensor.h"

static float temperature = 0.0f;     // 仅本文件可见
static bool overheat_alarm = false;  // 仅本文件可见

// 内部温度更新逻辑
void read_temperature() {
    temperature = sensor_read();
    if (temperature > 100.0f) {
        overheat_alarm = true;
    } else {
        overheat_alarm = false;
    }
}

// 对外接口:只读访问
float get_temperature() { return temperature; }
bool is_overheating()  { return overheat_alarm; }
// 文件:main.c
#include "temperature.h"

void main() {
    read_temperature();  // 更新温度
    if (is_overheating()) {
        printf("报警!当前温度:%.1f°C\n", get_temperature());
    }
}

关键优势分析
变量访问控制
通过 static 关键字将 temperature 和 overheat_alarm 限制在 temperature.c 文件内,外部文件无法直接修改,只能通过 get_temperature() 和 is_overheating() 接口读取。

强制数据封装
所有温度相关的操作(如传感器读取、报警判断)被集中到 temperature.c,修改温度值的唯一途径是通过 read_temperature() 函数,避免了数据被意外篡改。

接口与实现分离
外部只需关心 get_temperature() 和 is_overheating() 接口,无需了解内部如何存储温度值或判断报警逻辑,降低代码耦合度。

为什么不能直接在 get_counter() 中修改 counter?
假设有如下代码:

// 危险设计:在获取函数中修改值
uint32_t get_counter() {
    counter++;    // 修改静态变量
    return counter;
}

问题分析
违反单一职责原则
get_counter() 的命名暗示它只是「获取」计数器值,但实际行为却包含「修改」计数器,这会导致以下问题:

调用者预期只是读取值,却意外触发计数器变化

调试时多次调用 get_counter() 会导致计数器异常增加

线程安全问题
如果在中断或多线程环境中调用 get_counter(),自增操作 counter++ 可能被打断,导致数据竞争(需原子操作保护)。

破坏封装性
计数器修改逻辑应该由明确的接口控制(如 count_task()),如果在获取函数中隐式修改,相当于开放了「后门」,违背模块化设计初衷。

正确设计模式

// 文件:counter.c
static uint32_t counter = 0;

// 明确修改接口
void increment_counter() {
    counter++;
}

// 明确读取接口
uint32_t get_counter() {
    return counter;
}

// 可选:重置接口
void reset_counter() {
    counter = 0;
}

调用示例

// 正常使用
increment_counter();        // 明确的修改操作
printf("%u\n", get_counter());  // 明确的读取操作

// 错误用法会被编译器拦截
counter = 100;          // 编译错误:counter不可见
get_counter() = 50;     // 编译错误:返回值不可修改

总结
通过 static + 模块化设计:

实现「模块内全局,模块外不可见」的受控访问

通过明确的接口函数(如 get_xxx()/set_xxx())管理数据

避免在查询函数中隐藏修改操作,保持代码可预测性

这种模式在嵌入式开发中广泛应用,尤其适合管理硬件状态(如传感器数据、设备标志位),既能保留全局变量的便利性,又确保系统可靠性。

5 单例模式, 限制全局实例数量

type struct{
	uint32_t speed;
	uint8_t  mode;
	bool initialized;
}SPIController;

static SPIController spi_instance = {0};  //唯一实例
SPIController* get_spi_instance(void){
	if(!spi_instance.initialized){
		spi_instance.speed = 1000 000;
		spi_instance.mode  = 0;
		spi_instance.initialized = true;
	}
	return &spi_instance;
}

void spi_set_speed)uint32_t speed)
{
	SPIController* spi = get_spi_instance();
	spi->speed = speed;
	//...
}

好处:

确保系统中只有一个 SPI 控制器实例

初始化延迟加载(第一次使用时初始化)

通过接口函数控制访问,避免直接操作结构体

6. 配置化全局参数——集中管理可调参数

场景:系统有多个可配置参数(如阈值、超时时间),需集中管理。
示例:温度控制系统参数

// 文件:config.h
typedef struct {
    float temp_high_threshold;   // 高温阈值
    float temp_low_threshold;    // 低温阈值
    uint16_t sampling_interval;  // 采样间隔(ms)
} SystemConfig;

// 默认配置(const 防止误修改)
extern const SystemConfig DEFAULT_CONFIG;

// 文件:config.c
const SystemConfig DEFAULT_CONFIG = {
    .temp_high_threshold = 85.0f,
    .temp_low_threshold = -10.0f,
    .sampling_interval = 1000
};

// 使用示例:
SystemConfig current_config = DEFAULT_CONFIG;

void check_temperature(float temp) {
    if (temp > current_config.temp_high_threshold) {
        trigger_alarm();
    }
}

好处:

参数集中管理,修改时无需搜索分散的全局变量

通过 const 实现默认配置保护

支持运行时动态加载配置(如从EEPROM读取)

7. 事件驱动架构:消息队列通信

原理
核心思想:通过消息队列传递数据,取代共享全局变量,任务间解耦。

适用场景:多任务系统(如FreeRTOS)、中断与主循环通信、模块化系统设计。

示例:传感器数据采集与处理

// 文件:main.c(FreeRTOS示例)
#include "FreeRTOS.h"
#include "queue.h"

// 定义传感器数据结构
typedef struct {
    float temperature;
    float humidity;
    uint32_t timestamp;
} SensorData;

// 创建消息队列(容量10条数据)
QueueHandle_t sensor_queue = xQueueCreate(10, sizeof(SensorData));

// 传感器任务:采集数据并发送到队列
void sensor_task(void *pvParams) {
    SensorData data;
    while (1) {
        data.temperature = read_temperature_sensor();
        data.humidity = read_humidity_sensor();
        data.timestamp = xTaskGetTickCount();
        xQueueSend(sensor_queue, &data, portMAX_DELAY); // 阻塞直到发送成功
        vTaskDelay(pdMS_TO_TICKS(1000)); // 每1秒采集一次
    }
}

// 处理任务:从队列接收数据并处理
void process_task(void *pvParams) {
    SensorData data;
    while (1) {
        if (xQueueReceive(sensor_queue, &data, portMAX_DELAY)) {
            if (data.temperature > 50.0f) {
                trigger_alarm(); // 触发高温报警
            }
            log_to_sd_card(&data); // 记录到SD卡
        }
    }
}

// 主函数中启动任务
int main() {
    xTaskCreate(sensor_task, "Sensor", 128, NULL, 2, NULL);
    xTaskCreate(process_task, "Process", 128, NULL, 1, NULL);
    vTaskStartScheduler();
    return 0;
}

策略选择建议

场景 推荐策略 示例
单一函数内部状态维护 状态局部化(static) 传感器滤波、错误计数器
多实例硬件控制 面向对象设计(结构体) 管理多个ADC、PWM控制器
多任务/中断间数据传递 事件驱动(消息队列) 传感器数据采集与处理分离
系统级配置参数 结构体封装 + 单例模式 存储网络配置、系统参数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2333786.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蔚来汽车智能座舱接入通义大模型,并使用通义灵码全面提效

为加速AI应用在企业市场落地,4月9日,阿里云在北京召开AI势能大会。阿里云智能集团资深副总裁、公共云事业部总裁刘伟光发表主题演讲,大模型的社会价值正在企业市场释放,阿里云将坚定投入,打造全栈领先的技术&#xff0…

VMWare Workstation Pro17.6最新版虚拟机详细安装教程(附安装包教程)

目录 前言 一、VMWare虚拟机下载 二、VMWare虚拟机安装 三、运行虚拟机 前言 VMware 是全球领先的虚拟化技术与云计算解决方案提供商,通过软件模拟计算机硬件环境,允许用户在一台物理设备上运行多个独立的虚拟操作系统或应用。其核心技术可提升硬件…

【数据结构】红黑树超详解 ---一篇通关红黑树原理(含源码解析+动态构建红黑树)

一.什么是红黑树 红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。1972年出现,最初被称为平衡二叉B树。1978年更名为“红黑树”。是一种特殊的二叉查找树,红黑树的每一个节点上都有存储表示节点的颜色。每一个节点可以是…

uni-app初学

文章目录 1. pages.json 页面路由2. 图标3. 全局 CSS4. 首页4.1 整体框架4.2 完整代码4.3 轮播图 swiper4.3.1 image 4.4 公告4.4.1 uni-icons 4.5 分类 uni-row、uni-col4.6 商品列表 uni-row、uni-col 小程序开发网址: 注册小程序账号 微信开发者工具下载 uniapp …

PHP多维数组

在 PHP 中&#xff0c;多维数组是数组的数组&#xff0c;允许你存储和处理更复杂的数据结构。多维数组可以有任意数量的维度&#xff0c;但通常我们最常用的是二维数组&#xff08;数组中的数组&#xff09;。 首先来介绍一下一维数组&#xff0c; <?php//一维数组 $strAr…

数学建模:针对汽车行驶工况构建思路的延伸应用

前言&#xff1a; 汽车行驶工况构建的思简单理解为将采集的大量数据进行“去除干扰、数据处理&#xff0c;缩减至1800S的数据”&#xff0c;并可达到等效替换的目的&#xff0c;可以使在试验室快速复现&#xff1b;相应的解决思路、办法可应用在 “通过能量流采集设备大量采集…

go语言内存泄漏的常见形式

go语言内存泄漏 子字符串导致的内存泄漏 使用自动垃圾回收的语言进行编程时&#xff0c;通常我们无需担心内存泄漏的问题&#xff0c;因为运行时会定期回收未使用的内存。但是如果你以为这样就完事大吉了&#xff0c;哪里就大错特措了。 因为&#xff0c;虽然go中并未对字符串…

当DRAM邂逅SSD:新型“DRAM+”存储技术来了!

在当今快速发展的科技领域&#xff0c;数据存储的需求日益增长&#xff0c;对存储设备的性能和可靠性提出了更高的要求。传统DRAM以其高速度著称&#xff0c;但其易失性限制了应用范围&#xff1b;而固态硬盘SSD虽然提供非易失性存储&#xff0c;但在速度上远不及DRAM。 为了解…

JS实现文件点击或者拖拽上传

B站看到了渡一大师课的切片&#xff0c;自己实现了一下&#xff0c;做下记录 效果展示 分为上传前、上传中和上传后 实现 分为两步 界面交互网络请求 源码如下 upload.html <!DOCTYPE html> <html lang"zh-CN"><head><meta charset&q…

Centos7.9 升级内核,安装RTX5880驱动

系统镜像下载 https://vault.centos.org/7.9.2009/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso 系统安装步骤省略 开始安装显卡驱动 远程登录查看内核 [root192 ~]# uname -a Linux 192.168.119.166 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x8…

Xdocreport实现根据模板导出word

只使用freemaker生成简单的word文档很容易&#xff0c;但是当word文档需要插入动态图片&#xff0c;带循环数据&#xff0c;且含有富文本时解决起来相对比较复杂&#xff0c;但是使用Xdocreport可以轻易解决。 Xdocreport既可以实现文档填充也可以实现文档转换&#xff0c;此处…

当当平台商品详情接口设计与调用指南

当当平台商品详情接口设计与调用指南 接口名称 GET /api/product/detail 图书商品核心信息查询接口 请求参数说明 参数名称 类型 是否必填 说明 isbn string 是 国际标准书号(支持13位/10位) product_id string 否 平台内部商品编号&#xff08;与…

sql server分析表大小

使用自动存储过程查询 EXEC sp_spaceused YourTableName; rows&#xff1a;表中的行数。reserved&#xff1a;表占用的总空间&#xff08;包括数据和索引&#xff09;。data&#xff1a;表数据占用的空间。index_size&#xff1a;索引占用的空间。unused&#xff1a;未使用的空…

《AI大模型应知应会100篇》第13篇:大模型评测标准:如何判断一个模型的优劣

第13篇&#xff1a;大模型评测标准&#xff1a;如何判断一个模型的优劣 摘要 近年来&#xff0c;大语言模型&#xff08;LLMs&#xff09;在自然语言处理、代码生成、多模态任务等领域取得了显著进展。然而&#xff0c;随着模型数量和规模的增长&#xff0c;如何科学评估这些模…

【区块链安全 | 第三十七篇】合约审计之获取私有数据(一)

文章目录 私有数据访问私有数据实例存储槽Solidity 中的数据存储方式1. storage(持久化存储)定长数组变长数组2. memory(临时内存)3. calldata可见性关键字私有数据存储风险安全措施私有数据 私有数据(Private Data)通常指的是只对特定主体可见或可访问的数据,在区块链…

项目管理(高软56)

系列文章目录 项目管理 文章目录 系列文章目录前言一、进度管理二、配置管理三、质量四、风险管理五、真题总结 前言 本节主要讲项目管理知识&#xff0c;这些知识听的有点意思啊。对于技术人想创业&#xff0c;单干的都很有必要听听。 一、进度管理 二、配置管理 三、质量 四…

OpenCV边缘检测方法详解

文章目录 引言一、边缘检测基础概念边缘类型 二、OpenCV中的边缘检测方法1. Sobel算子2. Scharr算子3. Laplacian算子4. Canny边缘检测 三、性能比较与选择建议四、总结 引言 边缘检测是计算机视觉和图像处理中的基础技术&#xff0c;它能有效识别图像中物体的边界&#xff0c…

Linux:shell运行原理+权限

1.shell的运行原理 如果我们打开了命令终端或者是xshell进行远程登录服务器&#xff0c;就会看到命令行&#xff0c;如下图所示&#xff1a; 这个命令行本身也是系统中一个运行起来的程序&#xff0c;它用来接收用户的输入&#xff0c;帮用户来执行指令&#xff0c;将运行结果展…

【LeetCode Solutions】LeetCode 160 ~ 165 题解

CONTENTS LeetCode 160. 相交链表&#xff08;简单&#xff09;LeetCode 162. 寻找峰值&#xff08;中等&#xff09;LeetCode 164. 最大间距&#xff08;中等&#xff09;LeetCode 165. 比较版本号&#xff08;中等&#xff09; LeetCode 160. 相交链表&#xff08;简单&#…

Openssl升级至openssl9.8p1含全部踩坑内容

1、安装依赖包基础条件 yum install gcc yum install gcc-c yum install perl yum install perl-IPC-Cmd yum install pam yum install pam-devel sudo yum install perl-Data-Dumper 问题一&#xff1a;提示yum不可用 镜像源问题更换阿里源即可 wget -O /etc/yum.repos.d/…