嵌入式面经-C语言:智能指针,`#define` 和 `const`,`typedef`,头文件中定义静态变量

文章目录

    • C++中的智能指针
      • 普通指针的问题
      • 智能指针的概念
      • 智能指针的使用
        • 1. `std::unique_ptr`(独占所有权)
        • 2. `std::shared_ptr`(共享所有权)
      • 智能指针的内存泄漏
        • 循环引用示例
        • 3. `std::weak_ptr`(弱引用,解决循环引用)
      • 总结
    • `#define` 和 `const` 定义常量的选择
      • 1. `#define` 是简单的文本替换
      • 2. `const` 有类型检查
      • 3. 调试支持
      • 4. 作用域管理
      • 5. 节省内存
    • `typedef` 和 `#define` 的区别
      • 1. 类型安全
      • 2. 指针类型区别
      • 3. 调试和可读性
      • 总结
      • **`#define` vs `const`**
      • **`#define` vs `typedef`**
    • 头文件中定义静态变量的问题
      • 1. 每个包含该头文件的源文件都会创建一个独立的静态变量
      • 2. 可能导致调试困难
      • 3. 头文件的正确做法
        • 推荐:在头文件中使用 `extern` 声明,而在 `.c` 文件中定义
      • 4. `static` 变量的正确使用场景
      • 结论

C++中的智能指针

普通指针的问题

在C++传统的手动内存管理中,动态分配的对象需要手动释放,否则会产生内存泄露

智能指针的概念

智能指针(Smart Pointer)是一个类模板,主要用于管理动态分配的堆内存,避免手动管理带来的内存泄露、二次释放等问题。智能指针通过RAII(Resource Acquisition Is Initialization) 机制,在对象生命周期结束时自动释放资源,从而提高程序的安全性和可维护性。

C++标准库提供了三种主要的智能指针:

  • std::unique_ptr(独占所有权)
  • std::shared_ptr(共享所有权)
  • std::weak_ptr(弱引用)

智能指针的使用

智能指针通过自动管理资源来解决普通指针的管理问题。

1. std::unique_ptr(独占所有权)
  • unique_ptr 只能有一个指针拥有某块堆内存,不能被复制,只能被移动
  • 适用于独占资源,如文件句柄、互斥锁等。
#include <iostream>
#include <memory> // 需要包含头文件void func() {std::unique_ptr<int> ptr = std::make_unique<int>(10);std::cout << *ptr << std::endl; // 输出 10
} // ptr 离开作用域,自动释放内存int main() {func();return 0;
}

特点

  • 不能被复制:std::unique_ptr<int> p2 = p1; // 错误
  • 只能转移所有权:std::unique_ptr<int> p2 = std::move(p1);
2. std::shared_ptr(共享所有权)
  • shared_ptr 允许多个 shared_ptr 实例共享同一块堆内存
  • 通过引用计数(Reference Count)管理资源,只有最后一个 shared_ptr 被销毁时,资源才会被释放。
#include <iostream>
#include <memory>void func() {std::shared_ptr<int> p1 = std::make_shared<int>(20);std::shared_ptr<int> p2 = p1; // p2 也共享这块内存std::cout << *p1 << ", " << *p2 << std::endl; // 输出 20, 20
} // p1, p2 离开作用域,引用计数变为 0,自动释放内存int main() {func();return 0;
}

特点

  • 适用于多个对象共享资源的场景,如缓存、线程池等
  • 通过 use_count() 方法可以查询当前引用计数。

智能指针的内存泄漏

虽然 shared_ptr 自动管理资源,但如果两个 shared_ptr 互相持有对方,会导致循环引用(Cyclic Reference),导致资源无法释放。

循环引用示例
#include <iostream>
#include <memory>class B; // 先声明class A {
public:std::shared_ptr<B> ptrB;~A() { std::cout << "A 被销毁\n"; }
};class B {
public:std::shared_ptr<A> ptrA;~B() { std::cout << "B 被销毁\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;b->ptrA = a;// 离开 main() 作用域时,a 和 b 的引用计数都不会变为 0,导致内存泄露return 0;
}

问题:

  • ab 互相持有 shared_ptr,导致引用计数始终不为 0,资源不会释放。
  • 解决方案:使用 std::weak_ptr 破坏循环引用。
3. std::weak_ptr(弱引用,解决循环引用)

weak_ptr 不会增加引用计数,它只是一个观察者,不会影响 shared_ptr 管理的对象生命周期。

#include <iostream>
#include <memory>class B; // 先声明class A {
public:std::weak_ptr<B> ptrB; // 改为 weak_ptr~A() { std::cout << "A 被销毁\n"; }
};class B {
public:std::weak_ptr<A> ptrA; // 改为 weak_ptr~B() { std::cout << "B 被销毁\n"; }
};int main() {std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->ptrB = b;b->ptrA = a;// 现在没有循环引用,资源会被正常释放return 0;
}

为什么 weak_ptr 解决了问题?

  • weak_ptr 不增加 shared_ptr 的引用计数,因此不会影响对象的生命周期。
  • weak_ptr 只是一种观察 shared_ptr 是否已经失效,可以通过 lock() 方法获取有效 shared_ptr

总结

智能指针类型主要特点适用场景
unique_ptr独占所有权,不可复制独占资源,如文件、互斥锁
shared_ptr共享所有权,引用计数管理适用于多个对象共享资源
weak_ptr不增加引用计数,防止循环引用适用于 shared_ptr 互相引用的情况

智能指针是现代 C++ 的重要工具,能有效减少手动管理内存带来的问题,提高程序的稳定性和安全性。在实际开发中,合理选择合适的智能指针能帮助编写高效、安全、可维护的代码。

#defineconst 定义常量的选择

两者都可以用于定义常量,但 const 通常是更好的选择,原因如下:

1. #define 是简单的文本替换

  • 预处理器会直接将 #define 定义的内容替换到代码中,不进行类型检查。
  • 这可能导致难以发现的错误,例如:
    #define PI 3.1415926
    float radius = 2;
    float area = PI * radius * radius;  // 预处理器替换成 3.1415926 * radius * radius
    
  • 但是如果写成 #define PI 3,1415926(错误的逗号),编译器不会报错,而是会产生奇怪的行为。

2. const 有类型检查

  • const 变量具有类型,编译器会进行类型检查:
    const double PI = 3.1415926;
    
  • 这样可以避免 #define 的文本替换错误,提高代码的可读性和安全性。

3. 调试支持

  • #define 定义的常量在编译时被替换,调试器无法查看它们的值。
  • const 变量存储在内存中,可以在调试时查看其值。

4. 作用域管理

  • #define 没有作用域的概念,定义后在整个代码中都可用,容易引起冲突。
  • const 变量可以限制在特定的作用域,减少潜在错误。

5. 节省内存

  • #define 定义的值会在代码中多次重复,而 const 变量只存储一次,提高内存利用率。
  • 例如:
    #define MAX_SIZE 100
    const int MAX_SIZE = 100;
    
  • #define 版本中,每次使用 MAX_SIZE,编译器都会插入 100,而 const 版本只会存储一份 100,程序会引用该变量。

typedef#define 的区别

两者都可以创建别名,但 typedef 更推荐用于定义数据类型的别名,原因如下:

1. 类型安全

  • #define 只是简单的文本替换,不做类型检查:
    #define INT_PTR int *
    INT_PTR a, b; // 实际等价于 `int *a, b;`,b 只是 int 变量
    
  • typedef 具有类型安全:
    typedef int* IntPtr;
    IntPtr a, b;  // `a` 和 `b` 都是 `int*`
    

2. 指针类型区别

  • typedef 的含义更加清晰:
    typedef char* String;
    String s1, s2; // s1 和 s2 都是 `char*`
    
  • 但是 #define
    #define String char*
    String s1, s2; // 实际等价于 `char* s1, s2;`,其中 s2 是 `char` 而不是指针
    

3. 调试和可读性

  • typedef 允许更好的调试支持,因为它是一个真正的类型定义,而 #define 只是替换文本。

总结

  • 定义常量时,优先使用 const 而不是 #define
  • 定义类型别名时,使用 typedef 而不是 #define,尤其是在指针和复杂结构时。

#define vs const

对比项#defineconst
基本原理预处理器宏替换,不分配存储空间变量存储在内存中,有类型信息
类型检查无类型,不进行类型检查有类型,编译器进行类型检查
作用域全局作用域,无作用域概念受作用域限制(局部或全局)
存储位置代码段,不占用存储空间数据段,可能存储在栈、全局区或常量区
调试支持不能在调试器中查看值可在调试器中查看
编译方式预处理阶段替换,无法优化编译时优化
代码可读性低,容易引入难以发现的错误高,更安全,避免潜在错误
示例#define PI 3.1415926const double PI = 3.1415926;
推荐使用场景适用于条件编译、函数宏适用于定义常量,推荐使用

#define vs typedef

对比项#definetypedef
基本原理预处理器宏替换,字符串替换类型定义,创建类型别名
类型检查无类型检查,可能引入错误受编译器检查,类型安全
使用方式只替换文本,可能导致意外错误定义新的类型,更加清晰
调试支持无法调试,替换后看不到原始定义受调试器支持,可查看变量类型
指针定义区别#define INT_PTR int* 定义的 INT_PTR p1, p2; 只让 p1 成为指针,而 p2 仍然是 inttypedef int* IntPtr;IntPtr p1, p2; 都是 int*
结构体定义需要 #define 结构体名,并手动写 struct 关键字typedef struct 可直接使用别名
示例#define INT_PTR int*typedef int* IntPtr;
推荐使用场景仅适用于简单文本替换适用于创建类型别名,推荐使用

头文件中定义静态变量的问题

在头文件中定义静态变量static 变量)通常是不推荐的,主要有以下几个原因:

1. 每个包含该头文件的源文件都会创建一个独立的静态变量

  • static 变量的作用域限定在当前编译单元(即当前 .c 文件),如果在头文件中定义 static 变量,那么每个包含该头文件的 .c 文件都会有各自独立的 static 变量副本,而不是共享同一个变量。
  • 这不仅导致 资源浪费(因为每个 .c 文件都有自己的一份拷贝),还可能导致逻辑错误(多个文件中存在相同名称但不同的变量)。

示例(错误示范):

// file.h
static int count = 0;  // 头文件中定义静态变量(错误示范)
// file1.c
#include "file.h"
void func1() {count++;  // file1.c 有自己独立的 count 变量
}
// file2.c
#include "file.h"
void func2() {count++;  // file2.c 也有自己独立的 count 变量
}

问题:
file1.cfile2.c 各自有自己的 count 变量,它们不会共享同一个 count,这可能不是我们想要的行为。

2. 可能导致调试困难

由于 static 变量的作用域仅限于当前编译单元,多个 .c 文件中可能会存在多个同名但不同的变量,这会使得代码的可维护性变差,调试时容易造成混淆。

3. 头文件的正确做法

推荐:在头文件中使用 extern 声明,而在 .c 文件中定义

如果变量需要在多个 .c 文件中共享,推荐使用 extern 关键字在头文件中声明,而在某个 .c 文件中定义变量。

示例(正确做法):

// file.h(仅声明,不定义)
#ifndef FILE_H
#define FILE_Hextern int count;  // 使用 extern 声明全局变量#endif // FILE_H
// file.c(定义变量)
#include "file.h"int count = 0;  // 仅在一个 .c 文件中定义void increment() {count++;
}
// main.c(使用变量)
#include "file.h"
#include <stdio.h>int main() {printf("count = %d\n", count);return 0;
}

这种做法可以确保 count 变量在整个程序中只有一个实例,避免了 static 变量在多个文件中重复定义的问题。

4. static 变量的正确使用场景

尽管不推荐在头文件中定义 static 变量,但它在 .c 文件内部 还是有实际用途的:

  • 限制变量作用域:避免变量在其他文件中被错误访问和修改。
  • 防止命名冲突:局部 static 变量不会污染全局命名空间。

示例(正确使用 static):

// file.c
#include <stdio.h>static int count = 0;  // 仅 file.c 内部可见void increment() {count++;printf("count = %d\n", count);
}
// main.c
#include "file.h"int main() {increment();  // 访问 file.c 内部的静态变量return 0;
}

这种 static 变量不会被其他 .c 文件访问,因此是 static 的正确用法。

结论

方案是否推荐原因
在头文件中定义 static 变量❌ 不推荐每个包含头文件的 .c 文件都会有独立的变量,浪费资源且容易出错
在头文件中使用 extern 声明,全局变量放在 .c 文件✅ 推荐确保变量在多个 .c 文件中共享,并且只有一个实例
.c 文件中定义 static 变量✅ 推荐限制变量作用域,避免污染全局命名空间

最佳实践:

  • 如果变量需要在多个 .c 文件中共享 → 用 extern 声明,在 .c 文件中定义。
  • 如果变量仅在当前 .c 文件中使用 → 用 static 限制作用域,防止其他文件访问。
  • 避免在头文件中定义 static 变量,否则会导致多个 .c 文件各自拥有独立的变量实例,增加内存占用,且可能产生意想不到的错误。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

预处理指令中#if 和 #endif的用法

在 C 语言中&#xff0c;#if 和 #endif 是预处理指令&#xff0c;用于条件编译。它们的核心作用是&#xff1a;根据预处理器能够识别的条件&#xff08;通常是宏定义或常量表达式&#xff09;&#xff0c;决定某段代码是否参与编译。 — 基本功能 #if 用于开启一个条件编译块…

【统计学相关知识】极小充分统计量

定义、判别&#xff0c;计算上的定义 极小充分统计量不具有唯一性&#xff0c;具有不变性&#xff0c;线性映射仍然是极小充分统计量 一般来说&#xff0c;使用因子分解定义找到的充分统计量&#xff0c;直观地找到的&#xff0c;一般是极小充分统计量&#xff0c;但还是要遵…

winx64 安装对应版本火狐浏览器驱动

#本人需要学习使用selenium 选择对应的浏览器是firefox 为什么 因为喜欢 首先需要确定你的浏览器版本 在火狐的设置常规中你就能发现 Supported platforms — Firefox Source Docs documentation (mozilla.org) 以上链接参照Supported platforms 找到对应版本 &#xff08;注…

【最后203篇系列】016 Q201架构思考

前言 Q200已经达到了我既定的目标&#xff0c;在最近的3个月&#xff0c;我需要进一步完善&#xff0c;达到可以试产的程度。 在这个过程当中&#xff0c;许多知识和体会一直在变。 qtv200到目前&#xff0c;虽然通过习惯(每晚运行离线策略和比对)方式维持了注意力的集中&…

埃森哲中捷石化proposalv04(64页PPT)(文末有下载方式)

资料解读&#xff1a;埃森哲中捷石化proposalv04 详细资料请看本解读文章的最后内容。 埃森哲公司为中捷石化提供的ERP和MES系统实施项目提案&#xff0c;旨在通过信息化手段提升中捷石化的精细化管理水平。该提案详细阐述了埃森哲对中捷石化现状的理解、建议的解决方案、实施…

【2025新版本】【谷粒商城版】Kubernetes

本文作者&#xff1a; slience_me 文章目录 【2025】Kubernetes1. docker安装2. kubernetes安装前3. kubeadm,kubelet,kubectl3.1 简介kubeadmkubeletkubectl常用指令 3.2 安装3.3 kubeadm初始化3.4 加入从节点(工作节点)3.5 安装Pod网络插件&#xff08;CNI&#xff09;3.6 Ku…

Unity 运行报错:InvalidOperationException: Insecure connection not allowed 的原因

当你在 Unity 中运行项目时&#xff0c;如果遇到 InvalidOperationException: Insecure connection not allowed 这个错误&#xff0c;通常是由于以下原因导致的&#xff1a; 1. UnityWebRequest 的安全限制 UnityWebRequest 是 Unity 用于发送 HTTP 请求的核心组件。从 Unit…

ubuntu下TFTP服务器搭建

tftp 命令的作用和 nfs 命令一样&#xff0c;都是用于通过网络下载东西到 DRAM 中&#xff0c;只是 tftp 命令 使用的 TFTP 协议&#xff0c; Ubuntu 主机作为 TFTP 服务器。因此需要在 Ubuntu 上搭建 TFTP 服务器&#xff0c; 需要安装 tftp-hpa 和 tftpd-hpa&#xff0c;命令…

Python+Django网页前后端rsp云端摄像头人数监控系统

程序示例精选 PythonDjango网页前后端rsp云端摄像头人数监控系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonDjango网页前后端rsp云端摄像头人数监控系统》编写代码&#xff0c;…

糊涂人寄信

1.糊涂人寄信 - 蓝桥云课 糊涂人寄信 题目描述 有一个糊涂人&#xff0c;写了 n 封信和 n 个信封&#xff0c;到了邮寄的时候&#xff0c;把所有的信都装错了信封。求装错信封可能的种类数。 输入描述 有多行读入&#xff0c;每行输入一个正整数 n&#xff0c;表示一种情况…

华为ISC+战略规划项目数字化转型驱动的智慧供应链革新(169页PPT)(文末有下载方式)

资料解读&#xff1a;华为ISC战略规划项目数字化转型驱动的智慧供应链革新 详细资料请看本解读文章的最后内容。 华为的ISC战略规划项目是其供应链数字化转型的核心&#xff0c;旨在通过智慧供应链的革新&#xff0c;提升企业的竞争力和运营效率。本文将从多个维度详细解读这…

深度学习框架PyTorch——从入门到精通(5)自动微分

使用torch.autograd自动微分 张量、函数和计算图计算梯度禁用梯度追踪关于计算图的更多信息张量梯度和雅可比乘积 在训练神经网络时&#xff0c;最常用的算法是反向传播。在该算法中&#xff0c;参数&#xff08;模型权重&#xff09;根据损失函数的梯度相对于给定参数进行调整…

Mobile-Agent-V:通过视频引导的多智体协作学习移动设备操作

25年2月来自北京交大和阿里巴巴公司的论文“Mobile-Agent-V: Learning Mobile Device Operation Through Video-Guided Multi-Agent Collaboration”。 移动设备使用量的快速增长&#xff0c;迫切需要改进自动化以实现无缝任务管理。然而&#xff0c;因缺乏操作知识&#xff0…

单片机开发资源分析的实战——以STM32F103C8T6为例子的单片机资源分析

目录 第一点&#xff1a;为什么叫STM32F103C8T6 从资源手册拿到我们的对STM32F103C8T6的资源描述 第二件事情&#xff0c;关心我们的GPIO引脚输出 第三件事情&#xff1a;去找对应外设的说明部分 前言 本文章隶属于项目&#xff1a; Charliechen114514/BetterATK: This is…

《基于Spring Boot+Vue的智慧养老系统的设计与实现》开题报告

个人主页:@大数据蟒行探索者 一、研究背景及国内外研究现状 1.研究背景 根据1982年老龄问题世界大会联合国制定的标准,如果一个国家中超过65岁的老人占全国总人口的7%以上,或者超过60岁的老人占全国总人口的10%以上,那么这个国家将被定义为“老龄化社会”[1]。 随着国…

微软OneNote无法同步解决方案

目录 前言原因UWP特性 解决方案C***h注册表 参考链接 前言 假设有多台Windows电脑&#xff0c;最方便且免费的多设备笔记同步方案就是微软自家的OneNote&#xff0c;使用OneDrive自带的5G云存储。 但是在国内大陆的OneNote&#xff0c;经常会出现无法同步、同步失败&#xff1…

硬件设计抽象级别详解:门级、RTL级、行为级与HLS

硬件设计抽象级别详解&#xff1a;门级、RTL级、行为级与HLS 引言 在数字系统设计领域&#xff0c;硬件描述语言(HDL)提供了多种抽象级别来描述电路功能和结构。从最底层的门级描述到高层的行为级描述&#xff0c;每一种抽象级别都有其特定的用途和优势。理解这些不同级别以及…

WPF程序使用AutoUpdate实现自动更新

AutoUpdate.NET使用 一、AutoUpdater.NET 简介 AutoUpdater.NET 是一个开源库&#xff0c;支持从各种源&#xff08;如GitHub、FTP、HTTP服务器等&#xff09;下载并安装更新。它提供了灵活的配置选项&#xff0c;允许开发者根据需求定制更新检查逻辑和用户体验。 二、安装 …

Qwen2-Audio:通义千问音频大模型技术解读

引言:从llm到mlm(audio) 大型语言模型(LLM)的发展日新月异,它们在文本理解、生成、推理等方面展现出惊人的能力。然而,交互模态不仅仅依赖于文字,语音、语调、环境音等听觉信息同样承载着丰富的内容。阿里巴巴通义千问团队,推出了 Qwen-Audio 系列模型,这里我们一起…

问题 | ACOS(X) 与 ACOSD(X)的区别

github&#xff1a;https://github.com/MichaelBeechan CSDN&#xff1a;https://blog.csdn.net/u011344545 [TOC](ACOS(X) 与 ACOSD(X)的区别) ACOSD(X) 是反余弦函数&#xff0c;结果以角度形式表示。ACOS(X) 用于计算 X 中每个元素的反余弦值。当 X 为复数时&#xff0c;结…