C++常见问题与思考

TLS(线程本地存储)原理

线程本地存储(Thread Local Storage,TLS)是一种机制,它允许每个线程拥有自己独立的变量实例,这些变量的生命周期与线程相同。也就是说,不同线程对同一个 TLS 变量的访问,实际上是在访问各自独立的副本,彼此之间互不干扰。

实现方式
  • 静态 TLS:在编译时就为每个线程分配 TLS 变量的存储空间。编译器会在可执行文件中预留相应的空间,当线程启动时,操作系统会为每个线程初始化这些 TLS 变量。在 C++ 中,可以使用 __declspec(thread)(Windows)或 __thread(GCC、Clang)关键字来声明静态 TLS 变量。例如:
// 使用 __thread 声明静态 TLS 变量
__thread int tls_variable = 0;
  • 动态 TLS:在运行时动态地为线程分配和管理 TLS 变量。操作系统提供了一系列的 API 来创建、访问和销毁动态 TLS 变量。在 C++ 中,可以使用 std::thread_local 关键字来声明动态 TLS 变量。例如:
// 使用 std::thread_local 声明动态 TLS 变量
thread_local int dynamic_tls_variable = 0;
工作原理
  • 数据结构:操作系统会为每个线程维护一个 TLS 数据结构,这个数据结构通常是一个数组或链表,用于存储该线程的所有 TLS 变量。
  • 索引机制:每个 TLS 变量都有一个唯一的索引,线程通过这个索引来访问自己的 TLS 变量。当线程访问一个 TLS 变量时,操作系统会根据线程 ID 和变量索引,从该线程的 TLS 数据结构中找到对应的变量副本。
  • 线程创建和销毁:当一个新线程创建时,操作系统会为该线程分配一个新的 TLS 数据结构,并将所有 TLS 变量初始化为默认值。当线程销毁时,操作系统会释放该线程的 TLS 数据结构。

如何实现一个无锁队列?

无锁队列是一种在多线程环境下不使用锁(如互斥锁)来实现线程安全的队列数据结构。无锁队列通常使用原子操作和内存屏障来保证多线程操作的正确性和一致性。下面将介绍如何使用 C++ 实现一个简单的无锁队列,这里采用单生产者单消费者(SPSC)的无锁队列作为示例。

实现思路

  • 使用原子操作来更新队列的头指针和尾指针,避免使用锁带来的性能开销。
  • 采用循环数组作为队列的底层存储结构。
  • 通过比较和交换(CAS)操作来确保在多线程环境下对头指针和尾指针的更新是原子的。

仿函数的优势

#include <iostream>
#include <vector>
#include <algorithm>// 定义一个比较仿函数
class Greater {
public:bool operator()(int a, int b) const {return a > b;}
};int main() {std::vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};// 使用仿函数作为排序规则std::sort(numbers.begin(), numbers.end(), Greater());for (int num : numbers) {std::cout << num << " ";}std::cout << std::endl;return 0;
}
  • 可状态化:和普通函数不同,仿函数可以拥有自己的状态。也就是说,仿函数类可以有成员变量,这些成员变量能记录仿函数的状态。
  • 可作为模板参数:仿函数可以作为模板参数,在模板编程里能发挥很大作用。例如,std::sort函数就可以使用仿函数来定义排序规则。

野指针(Wild Pointer)

  • 定义:指向无效内存地址的指针,访问会导致未定义行为(崩溃、数据损坏)。
  • 常见场景
    • 未初始化的指针:未明确指向有效内存。
    • 指向已释放的内存(即悬垂指针)。
    • 指向超出作用域的局部变量

深浅拷贝

c++中引用有没有深浅拷贝的问题

  • 浅拷贝是指在拷贝对象时,仅复制对象的成员变量的值,而不复制成员变量所指向的资源。如果成员变量是指针、引用或其他对象的引用,那么浅拷贝后,源对象和目标对象的成员变量将指向相同的资源。
  • 深拷贝是指在拷贝对象时,不仅复制对象的成员变量的值,还会复制成员变量所指向的资源。深拷贝后,源对象和目标对象的成员变量将指向不同的资源,互不干扰。

string模拟实现拷贝构造和移动构造(手撕)

#include <iostream>
#include <cstring> // 用于std::strlen和std::strcpyclass MyString {
private:char* data; // 指向动态分配的字符数组public:// 构造函数MyString(const char* str = "") {if (str) {data = new char[std::strlen(str) + 1]; // 分配内存,+1用于存储'\0'std::strcpy(data, str); // 复制字符串} else {data = new char[1];*data = '\0'; // 空字符串}}// 拷贝构造函数MyString(const MyString& other) {data = new char[std::strlen(other.data) + 1];std::strcpy(data, other.data);std::cout << "Copy constructor called" << std::endl;}// 移动构造函数MyString(MyString&& other) noexcept {data = other.data; // 直接接管other的资源other.data = nullptr; // 将other的指针置为空,避免析构时重复释放std::cout << "Move constructor called" << std::endl;}// 赋值运算符(拷贝赋值)MyString& operator=(const MyString& other) {if (this != &other) { // 防止自赋值delete[] data; // 释放当前对象的资源data = new char[std::strlen(other.data) + 1];std::strcpy(data, other.data);}return *this;}// 赋值运算符(移动赋值)MyString& operator=(MyString&& other) noexcept {if (this != &other) { // 防止自赋值delete[] data; // 释放当前对象的资源data = other.data; // 接管other的资源other.data = nullptr; // 将other的指针置为空}return *this;}// 析构函数~MyString() {delete[] data; // 释放动态分配的内存}// 获取字符串内容const char* c_str() const {return data;}// 打印字符串内容void print() const {std::cout << data << std::endl;}
};int main() {MyString str1("Hello, World!");MyString str2(str1); // 调用拷贝构造函数MyString str3(std::move(str1)); // 调用移动构造函数std::cout << "str1: ";str1.print();std::cout << "str2: ";str2.print();std::cout << "str3: ";str3.print();return 0;
}

内存泄露的问题、定位内存泄露的问题、处理内存泄露的问题

内存泄露的定义内存泄露(Memory Leak)是指程序在动态分配内存后,由于某种原因未能正确释放,导致这部分内存无法被重新使用。随着时间推移,程序占用的内存不断增加,最终可能导致系统内存耗尽,程序运行缓慢甚至崩溃。2\. 定位内存泄露问题定位内存泄露问题通常需要借助一些工具和方法,以下是一些常见的方法:(1)使用内存分析工具• Valgrind:一款开源的内存调试和性能分析工具,适用于Linux平台。它可以检测内存泄漏、越界访问、非法指针等问题。• 使用方法:在终端运行  valgrind ./your_program  ,它会生成详细的报告,指出内存泄露的位置。• AddressSanitizer:Google开源的内存错误检测工具,可以检测内存泄漏、缓冲区溢出等问题。• 使用方法:在编译时添加  -fsanitize=address  选项,运行程序时会自动检测内存问题。• LeakSanitizer:专门用于检测内存泄漏的工具。• mtrace:GNU Glibc自带的内存问题检测工具,通过记录  malloc  和  free  的调用来检测内存泄漏。• 使用方法:在代码中调用  mtrace()  和  muntrace()  ,并设置环境变量  MALLOC_TRACE  ,运行程序后会生成日志文件,通过分析日志可以定位内存泄漏问题。(2)代码审查定期审查代码,查找未释放的内存分配。重点关注以下几点:• 确保每个  malloc  、  calloc  、  realloc  等动态分配内存的函数都有对应的  free  调用。• 检查指针重新赋值、错误的内存释放以及返回值的不正确处理等情况。(3)日志和调试在程序中添加日志记录内存分配和释放的操作。通过分析日志,可以确定哪些内存分配没有被释放。此外,使用调试器(如GDB)也可以追踪内存泄漏。(4)自定义内存分配函数创建自定义的内存分配和释放函数,记录每次分配和释放的内存信息。通过这种方式可以更直观地检测内存泄漏。3\. 处理内存泄露问题一旦发现内存泄露问题,需要采取以下措施进行处理:(1)手动释放内存在程序中进行内存分配时,确保及时释放不再需要的内存。如果忘记释放内存,就会导致内存泄漏。(2)使用智能指针在C++中,使用智能指针(如  std::unique_ptr  、  std::shared_ptr  )可以自动管理内存,避免手动释放内存的繁琐操作,从而减少内存泄漏的风险。(3)修复代码中的错误根据内存分析工具的报告或日志信息,修复代码中的错误。例如:• 确保在指针重新赋值前释放原有内存。• 修复错误的内存释放逻辑。• 正确处理函数返回值,避免内存泄漏。(4)定期进行垃圾回收对于一些支持垃圾回收的语言(如Java、Python),可以定期进行垃圾回收,以释放不再使用的内存。4\. 预防内存泄露预防内存泄露比修复内存泄露更为重要,以下是一些预防内存泄露的方法:• 明确内存所有权:每次分配内存时,明确哪个部分负责释放该内存。• 使用RAII(资源获取即初始化):在C++中,通过RAII机制确保资源在对象生命周期结束时自动释放。• 避免使用裸指针:尽量使用智能指针或容器来管理动态内存。• 编写单元测试:通过单元测试检查内存分配和释放的正确性。

迭代器失效

序列式容器(如vectordeque

插入元素导致迭代器失效
  • vector:当向vector中插入元素时,如果当前容量不足,vector会重新分配一块更大的内存,并将原有元素复制到新内存中,然后释放旧内存。这会导致所有指向原vector的迭代器、指针和引用都失效。
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3};auto it = vec.begin();vec.insert(vec.begin(), 0); // 插入元素,可能导致迭代器失效// *it = 10; // 错误,此时迭代器it可能已失效return 0;
}
  • deque:在deque的中间插入元素会使所有迭代器、指针和引用失效;在deque的两端插入元素,只有指向插入点的迭代器会失效。

 std::deque  内部实现是一个块序列,每个块存储固定数量的元素。当删除中间元素时,为了保持块的连续性和完整性,可能会导致以下操作:

  • 元素移动:删除中间元素后,后面的元素需要向前移动以填补空缺。
  • 块调整:可能需要重新分配块,或者调整块的边界。

最终导致迭代器失效:由于元素的物理位置发生了变化,所有迭代器的指向都会变得不确定。

删除元素导致迭代器失效
  • vector:删除vector中的元素时,被删除元素之后的所有迭代器、指针和引用都会失效。
#include <iostream>
#include <vector>int main() {std::vector<int> vec = {1, 2, 3};auto it = vec.begin() + 1;vec.erase(vec.begin()); // 删除元素,后续迭代器失效// *it = 10; // 错误,此时迭代器it已失效return 0;
}
  • deque:删除deque中的元素时,除了被删除元素的迭代器失效外,若删除的不是两端元素,所有迭代器、指针和引用都会失效。

关联式容器(如setmap

插入元素导致迭代器失效

对于setmap等关联式容器,插入元素不会使任何迭代器、指针和引用失效。因为关联式容器使用红黑树等数据结构实现,插入操作只是在树中添加新节点,不会影响原有节点的位置。

#include <iostream>
#include <set>int main() {std::set<int> mySet = {1, 2, 3};auto it = mySet.begin();mySet.insert(4); // 插入元素,迭代器不会失效std::cout << *it << std::endl; // 可以正常使用迭代器return 0;
}
删除元素导致迭代器失效

删除关联式容器中的元素时,被删除元素的迭代器会失效,但其他迭代器不受影响。

#include <iostream>
#include <set>int main() {std::set<int> mySet = {1, 2, 3};auto it = mySet.begin();auto nextIt = std::next(it);mySet.erase(it); // 删除元素,当前迭代器失效// *it = 10; // 错误,此时迭代器it已失效std::cout << *nextIt << std::endl; // 可以正常使用其他迭代器return 0;
}

解决迭代器失效问题的方法

  • 重新获取迭代器:在进行插入或删除操作后,重新获取迭代器,确保其指向有效的元素。
  • 使用返回值更新迭代器:一些容器的插入和删除操作会返回有效的迭代器,可以使用这些返回值更新迭代器。

const的使用

  • 1\. 声明常量变量  const  可以用来声明常量变量,确保其值在初始化后不能被修改。cppconst int MAX_SIZE = 100; // MAX_SIZE是一个常量,不能被修改
  • 2\. 常量成员函数在类中,  const  可以修饰成员函数,表示该成员函数不会修改对象的任何成员变量。这对于保证对象的不可变性非常有用。
  • 3\. 常量对象可以使用  const  修饰整个对象,表示该对象的所有成员变量都不能被修改。对于常量对象,只能调用其常量成员函数。
  • 4\. 常量引用  const  可以用于声明引用,表示引用所指向的对象不能被修改。常量引用常用于函数参数,以避免不必要的拷贝。
  • 5\. 常量指针和指针常量• 常量指针:指针所指向的内容不能被修改。
  • 常量指针常量:指针本身和指针所指向的内容都不能被修改。cppconst int* const ptr = &value; // 
  • 6\. 常量表达式C++11引入了  constexpr  ,用于声明编译时常量。  constexpr  比  const  更严格,它要求表达式在编译时必须能够求值。cppconstexpr int MAX_SIZE = 100; // 编译时常量
  • 7\. 常量成员变量类中的成员变量可以被声明为  const  ,表示该成员变量在对象初始化后不能被修改。常量成员变量必须在构造函数的初始化列表中初始化。
  • 8\. 常量与模板  const  可以用于模板参数,表示模板参数是一个常量。
  • 9\. 常量与函数返回值函数可以返回  const  类型的值,但通常不推荐,因为返回  const  值会限制函数的使用。
  • 10\. 常量与类型别名

dynamic_cast

功能

dynamic_cast 主要用于在继承体系中进行安全的向下转型(从基类指针或引用转换为派生类指针或引用),并且会在运行时检查转型的有效性。如果转型失败,对于指针类型会返回 nullptr,对于引用类型会抛出 std::bad_cast 异常。

使用场景

常用于多态环境下,当你持有一个基类指针或引用,但需要访问派生类特有的成员时,可以使用 dynamic_cast 进行安全的类型转换。

示例代码
#include <iostream>class Base {
public:virtual void print() { std::cout << "Base" << std::endl; }virtual ~Base() {}
};class Derived : public Base {
public:void print() override { std::cout << "Derived" << std::endl; }void derivedFunction() { std::cout << "Derived function" << std::endl; }
};int main() {Base* basePtr = new Derived();Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);if (derivedPtr) {derivedPtr->derivedFunction();}delete basePtr;return 0;
}
注意事项
  • dynamic_cast 只能用于含有虚函数的类层次结构,因为它依赖于虚函数表来进行运行时类型检查。
  • 运行时类型检查会带来一定的性能开销。

static_cast

功能

static_cast 是一种编译时的类型转换,它可以用于各种基本类型之间的转换,以及在继承体系中进行向上转型(从派生类指针或引用转换为基类指针或引用)和向下转型(但不进行运行时检查)。

使用场景
  • 基本类型的转换,如 int 转 double
  • 继承体系中的向上转型,这是安全的,因为派生类对象包含基类对象的所有成员。
  • 显式调用类的转换构造函数或转换运算符。
示例代码
#include <iostream>class Base {};
class Derived : public Base {};int main() {int num = 10;double d = static_cast<double>(num);Derived derived;Base* basePtr = static_cast<Base*>(&derived); // 向上转型return 0;
}
注意事项
  • static_cast 不进行运行时类型检查,因此向下转型时如果类型不匹配,可能会导致未定义行为。

const_cast

功能

const_cast 主要用于去除或添加 const 或 volatile 修饰符。它只能用于改变对象的常量性或易变性,不能改变对象的类型。

使用场景

当你需要在某些情况下修改一个原本被声明为 const 的对象时,可以使用 const_cast 去除 const 修饰符,但要确保这种修改是安全的。

示例代码
#include <iostream>void printNonConst(int& num) {std::cout << num << std::endl;
}int main() {const int num = 10;int& nonConstNum = const_cast<int&>(num);// nonConstNum = 20; // 不建议修改,可能导致未定义行为printNonConst(nonConstNum);return 0;
}
注意事项
  • 去除 const 修饰符后修改原本为 const 的对象可能会导致未定义行为,应谨慎使用。

reinterpret_cast

功能

reinterpret_cast 是一种最危险的类型转换,它可以将任意指针类型转换为其他指针类型,甚至可以将指针转换为整数类型或反之。它不进行任何类型检查,只是简单地重新解释二进制位。

使用场景
  • 底层编程,如在某些系统编程中需要将指针转换为整数进行地址计算。
  • 处理一些特殊的硬件相关操作。

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

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

相关文章

如何快速下载并安装 Postman?

从下载、安装、启动 Postman 这三个方面为大家详细讲解下载安装 Postman 每一步操作&#xff0c;帮助初学者快速上手。 Postman 下载及安装教程(2025最新)

使用Gitee Go流水线部署个人项目到服务器指南

使用Gitee Go流水线部署个人项目到服务器指南 前言&#xff01;&#xff01;&#xff01; 本文解决的问题&#xff1a; 你有一台ECS服务器&#xff0c;你在上面部署了一个Java服务也就是一个jar&#xff0c;你觉着你每次手动本地打包&#xff0c;上传&#xff0c;在通过命令去…

Linux第一节:Linux系统编程入门指南

摘要 本文面向Linux初学者&#xff0c;系统讲解操作系统核心概念、Shell命令实战、权限管理精髓及目录结构解析。通过思维导图命令示例原理解析的方法&#xff0c;帮助开发者快速构建Linux知识体系&#xff0c;掌握生产环境必备技能。 一、Linux的前世今生&#xff1a;从实验室…

【Linux 维测专栏 5 -- linux pstore 使用介绍】

文章目录 Linux pstore 功能简介1. pstore 概述2. pstore 的核心功能3. pstore 的工作原理4. pstore 的使用示例5. pstore 的优势6. 典型应用场景配置示例1)DTS配置2)config配置运行测试及log问题小结Linux pstore 功能简介 1. pstore 概述 pstore(Persistent Storage)是…

在 ASP .NET Core 9.0 中使用 Scalar 创建漂亮的 API 文档

示例代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/90407900 Scalar 是一款可帮助我们为 API 创建精美文档的工具。与感觉有些过时的默认 Swagger 文档不同&#xff0c;Scalar 为 API 文档提供了全新而现代的 UI。其简洁的设计让开发人员可以轻松找到测试…

Rabbitmq消息被消费时抛异常,进入Unacked 状态,进而导致消费者不断尝试消费(下)

一、消费流程图 消息在消费出现异常的时候&#xff0c;将一直保留在消息队列&#xff0c;所以你会看到以下奇怪的现象&#xff1a; 消息队列仅有5个消息&#xff0c; 投递速度也非常快&#xff0c;结果却一直无法消费掉。 二、重试策略 重试机制的使用场景&#xff1a;重试机制…

【STM32】知识点介绍二:GPIO引脚介绍

文章目录 一、概述二、GPIO的工作模式三、寄存器编程 一、概述 GPIO&#xff08;英语&#xff1a;General-purpose input/output&#xff09;,即通用I/O(输入/输出)端口&#xff0c;是STM32可控制的引脚。STM32芯片的GPIO引脚与外部设备连接起来&#xff0c;可实现与外部通讯、…

JavaScript流程控制精讲(二)运算符与循环实战

JavaScript流程控制精讲&#xff08;二&#xff09;运算符与循环实战 学习目标&#xff1a;掌握条件判断与循环控制&#xff0c;实现基础业务逻辑 核心要点&#xff1a;运算符优先级 | 短路运算 | 循环优化 | 项目实战 一、运算符进阶技巧 1.1 算术运算符 console.log(5 % 3)…

如何在IPhone 16Pro上运行python文件?

在 iPhone 16 Pro 上运行 Python 文件需要借助第三方工具或远程服务&#xff0c;以下是具体实现方法和步骤&#xff1a; 一、本地运行方案&#xff08;无需越狱&#xff09; 使用 Python 编程类 App 以下应用可在 App Store 下载&#xff0c;支持直接在 iPhone 上编写并运行 …

【赵渝强老师】达梦数据库的数据库对象

达梦数据库中包含各种数据库对象&#xff0c;主要分为两大类型&#xff1a;基本数据库对象和复杂数据库对象。下面分别进行介绍。 视频讲解如下 【赵渝强老师】达梦数据库的数据库对象 一、 基本数据库对象 常见的基本数据库对象有&#xff1a;表、索引、视图、序列、同义词等…

【每日算法】Day 6-1:哈希表从入门到实战——高频算法题(C++实现)

摘要 &#xff1a;掌握高频数据结构&#xff01;今日深入解析哈希表的核心原理与设计实现&#xff0c;结合冲突解决策略与大厂高频真题&#xff0c;彻底掌握O(1)时间复杂度的数据访问技术。 一、哈希表核心思想 哈希表&#xff08;Hash Table&#xff09; 是一种基于键值对的…

LeetCode 第29题、30题

LeetCode 第29题&#xff1a;两数相除 题目描述 给你两个整数&#xff0c;被除数dividend和除数divisor。将两数相除&#xff0c;要求不使用乘法、除法和取余运算。整数除法应该向零截断&#xff0c;也就是截去其小数部分。例如&#xff0c;8.345将被截断为8&#xff0c;-2.733…

26考研——树与二叉树_树、森林(5)

408答疑 文章目录 二、树、森林树的基本概念树的定义和特性树的定义树的特性 基本术语树的基本术语和概念祖先、子孙、双亲、孩子、兄弟和堂兄弟结点的层次、度、深度和高度树的度和高度分支结点和叶结点有序树和无序树路径和路径长度 森林的基本术语和概念森林的定义森林与树的…

【HarmonyOS Next之旅】DevEco Studio使用指南(六)

目录 1 -> 在模块中添加Ability 1.1 -> Stage模型添加UIAbility 1.1.1 -> 在模块中添加UIAbility 1.1.2 -> 在模块中添加Extension Ability 2 -> 创建服务卡片 2.1 -> 概述 2.2 -> 使用约束 2.3 -> 创建服务卡片 2.4 -> 创建动态/静态卡片…

Langchain 多模态输入和格式化输出

多模态输入 图片处理&#xff08;最高频&#xff09; 1.1 URL形式&#xff08;推荐大文件&#xff09; from langchain.schema import HumanMessage from langchain.chat_models import ChatOpenAIchat ChatOpenAI(model"gpt-4-vision-preview")message HumanMes…

Excel多级联动下拉菜单的自动化设置(使用Python中的openpyxl模块)

1 主要目的 在Excel中&#xff0c;经常会遇到需要制作多级联动下拉菜单的情况&#xff0c;要求单元格内填写的内容只能从指定的多个选项中进行选择&#xff0c;并且需要设置多级目录&#xff0c;其中下级目录的选项内容要根据上级目录的填写内容确定&#xff0c;如下图所示&am…

3.25-1 postman执行+弱网测试

1.导出json脚本 2.打包json文件 3.下载的文件 二 .导入脚本 选择文件 点击导入 导入的接口 三.多接口运行 &#xff08;1&#xff09;集合右键&#xff0c;点击run &#xff0c;运行多个接口 2.编辑环境&#xff0c;集合&#xff0c;执行次数等 运行多个接口 四.运行多个接口…

Pear Admin Flask 开发问题

下载代码请复制以下命令到终端执行 git clone https://gitee.com/pear-admin/pear-admin-flask 于是我下载git 完成安装后&#xff1a; 安装 Git 后出现的页面是 “Git for Windows 的版本发布说明&#xff08;Release Notes&#xff09;”&#xff0c;通常会在安装完成后自动弹…

12-scala样例类(Case Classes)

例类&#xff08;Case classes&#xff09;和普通类差不多&#xff0c;只有几点关键差别&#xff0c;接下来的介绍将会涵盖这些差别。样例类非常适合用于不可变的数据。 定义一个样例类 一个最简单的样例类定义由关键字case class&#xff0c;类名&#xff0c;参数列表&#…

cmakelist中添加opencv

版本选择 qt的msvc&#xff0c;版本2019 opencv版本 4.5.3 配置了环境变量 x64下的v14中的bin 配置头文件 {"configurations": [{"name": "Win32","includePath": ["${workspaceFolder}","d:\\QT\\6.5.3\\msvc20…