前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >17个C++编程常见错误及其解决方案

17个C++编程常见错误及其解决方案

原创
作者头像
开源519
发布于 2024-04-30 12:44:48
发布于 2024-04-30 12:44:48
1.3K0
举报
文章被收录于专栏:开源519开源519

17个C++编程常见错误及其解决方案

TOC

引言

  想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。

1. 空指针解引用

错误示例:

代码语言:Cpp
AI代码解释
复制
int* ptr = nullptr;
std::cout << *ptr;  // 解引用空指针,可能导致段错误

解决方法: 在访问指针之前,务必检查其是否为空。

代码语言:Cpp
AI代码解释
复制
if (ptr != nullptr) {
    std::cout << *ptr;
}

2. 多线程竞争条件

错误示例: 多个线程同时读写同一数据,未加锁保护。

代码语言:Cpp
AI代码解释
复制
int shared_var = 0;

void thread_func() {
    for (int i = 0; i < 1000000; ++i) {
        shared_var++;  // 多线程并发执行此操作可能导致结果不准确
    }
}

int main() {
    std::thread t1(thread_func);
    std::thread t2(thread_func);
    t1.join();
    t2.join();
    std::cout << shared_var;  // 预期输出2000000,但实际上可能不是
}

解决方法: 使用互斥量(mutex)或其他同步机制保护共享资源。

代码语言:Cpp
AI代码解释
复制
std::mutex mtx;
int shared_var = 0;

void thread_func() {
    for (int i = 0; i < 1000000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        shared_var++;
    }
}

3. 死锁

错误示例: 两个线程分别持有对方需要的锁,互相等待导致死锁。

代码语言:Cpp
AI代码解释
复制
std::mutex m1, m2;
bool flag1 = false, flag2 = false;

void func1() {
    std::unique_lock<std::mutex> lck1(m1);
    std::unique_lock<std::mutex> lck2(m2, std::defer_lock);
    while (!flag2) {
        lck2.lock();  // 若func2已获得m1,这里将导致死锁
        // ...
    }
}

void func2() {
    std::unique_lock<std::mutex> lck2(m2);
    std::unique_lock<std::mutex> lck1(m1, std::defer_lock);
    while (!flag1) {
        lck1.lock();  // 若func1已获得m2,这里同样导致死锁
        // ...
    }
}

解决方法: 遵循锁的获取顺序一致性原则,或者使用更高级的并发原语避免死锁。

4. 缓冲区溢出

错误示例: 数组越界写入。

代码语言:Cpp
AI代码解释
复制
char str[10];
strcpy(str, "This is a very long string.");  // 可能造成缓冲区溢出

解决方法: 使用安全的字符串处理函数,如strncpy或C++11之后的std::string。

5. 悬挂指针

错误示例: 指向动态分配内存的指针在释放内存后仍被继续使用。

代码语言:Cpp
AI代码解释
复制
int* p = new int(5);
delete p;
*p = 10;  // 悬挂指针,可能导致段错误

解决方法: 释放内存后将指针置为nullptr,表明它不再指向有效的内存。

6. 未捕获的异常

错误示例: 函数内部抛出异常但未被捕获。

代码语言:Cpp
AI代码解释
复制
void mayThrowException() {
    throw std::runtime_error("An error occurred.");
}

int main() {
    mayThrowException();  // 如果没有捕获,程序会终止
    return 0;
}

解决方法: 在可能抛出异常的地方添加try-catch块,并妥善处理异常。

7. 浮点数精度丢失

错误示例: 依赖于精确的浮点数计算。

代码语言:Cpp
AI代码解释
复制
double a = 0.1;
double b = 0.2;
if (a + b == 0.3) {  // 这里可能为假,因为浮点数运算存在精度误差
    // ...
}

解决方法: 尽量避免直接比较浮点数相等,而是设定一个合理的误差范围。

8. 无符号整数溢出

错误示例: 对无符号整数执行减法,当结果小于零时可能会导致意外的大数值。

代码语言:Cpp
AI代码解释
复制
unsigned int a = 0;
unsigned int b = 1;
std::cout << a - b;  // 输出的结果将是UINT_MAX

解决方法: 理解并谨慎使用无符号整数,尤其是涉及负数操作时。

9. 隐式类型转换

错误示例: 不同类型的表达式混合运算导致隐式类型转换,产生非预期结果。

代码语言:Cpp
AI代码解释
复制
long long num1 = LLONG_MAX;
int num2 = INT_MAX;
long long result = num1 + num2;  // num2提升为long long后导致溢出

解决方法: 尽量避免隐式类型转换,明确指定类型转换以防止潜在问题。

10. 未正确关闭文件

错误示例: 打开文件后在程序结束前忘记关闭,可能导致数据丢失或文件句柄耗尽。

代码语言:Cpp
AI代码解释
复制
std::ofstream file("output.txt");
file << "Some content";
// 忘记调用file.close()

解决方法: 始终确保在适当的时间关闭文件,可以使用RAII(Resource Acquisition Is Initialization)技术,例如智能指针或C++11引入的std::ofstream的析构函数会自动关闭文件。

11. 无符号整数循环条件错误

错误示例: 在循环中使用无符号整数作为递减计数器,当期望循环结束时计数器为0,但由于无符号整数的特性导致无法正确终止循环。

代码语言:Cpp
AI代码解释
复制
unsigned int counter = 5;
while (counter >= 0) {  // 由于counter是无符号整数,当它递减至0时不会变为负数
    // 循环体执行
    --counter;
}  // 本应在counter为0时退出循环,但实际上会进入死循环

解决方法: 确保正确设置循环条件,针对无符号整数的特性,应当避免在计数器达到其自然结束点时依赖于负数条件。可以使用固定的循环次数或另一个合适的终止条件来替代。

代码语言:Cpp
AI代码解释
复制
unsigned int limit = 5;
for (unsigned int counter = 0; counter < limit; ++counter) {
    // 循环体执行
}  // 当counter到达limit时,循环自然结束

12. 错误的类型转换

错误示例: 强制类型转换可能掩盖潜在的逻辑错误,特别是在不同类型之间赋值或比较时。

代码语言:Cpp
AI代码解释
复制
double d = 3.14;
int i = d;  // 损失精度
if (d == 3) {  // 可能永远不成立,因为浮点数与整数比较会有精度损失
    // ...
}

解决方法: 除非必要,否则尽量避免强制类型转换,尤其是在比较和赋值操作中,确保正确处理类型之间的转换。

13. 循环体内的副作用

错误示例: 在循环体内修改迭代变量,导致意料之外的循环行为。

代码语言:Cpp
AI代码解释
复制
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {
    if (*it == target) {
        it = vec.erase(it);  // 直接删除当前元素可能导致未遍历完剩余元素
    }
}

解决方法: 在循环体内避免对用于迭代的对象进行修改,若必须删除或移动元素,可选择复制迭代器或使用其它合适的数据结构操作方法。

14. 字符串字面量和字符数组混淆

错误示例: 初始化字符数组时,误用字符串字面量,导致未正确终止的字符串。

代码语言:Cpp
AI代码解释
复制
char name[8] = "John Doe";  // 缺少终止符'\0',可能会导致读取额外的内存数据

解决方法: 确保字符数组的大小足够容纳字符串字面量加上终止符'\0',或者使用C++的std::string类以避免此类问题。

代码语言:Cpp
AI代码解释
复制
char name[9] = "John Doe";  // 确保有足够的空间存放'\0'
// 或者
std::string nameStr = "John Doe";  // 使用std::string类,无需手动管理终止符

15. 不恰当的数组边界检查

错误示例: 访问数组时未检查索引有效性,可能导致数组越界。

代码语言:Cpp
AI代码解释
复制
int arr[5] = {1, 2, 3, 4, 5};
std::cout << arr[5];  // 数组越界,可能导致未定义行为

解决方法: 在访问数组之前,始终确保索引的有效性,防止数组越界。

代码语言:Cpp
AI代码解释
复制
int arr[5] = {1, 2, 3, 4, 5};
int index = 5;
if (index >= 0 && index < sizeof(arr) / sizeof(arr[0])) {
    std::cout << arr[index];  // 正确进行边界检查
} else {
    std::cout << "Index out of bounds.\n";
}

16. 动态内存分配和释放不匹配

错误示例: 使用不同的分配和释放函数,导致内存泄漏或程序崩溃。

代码语言:Cpp
AI代码解释
复制
void* memory = malloc(sizeof(int)*10);
free(memory);  // 在C++代码中混用了malloc和free

解决方法: 在C++中,建议使用new和delete操作符进行动态内存分配和释放,以确保匹配:

代码语言:Cpp
AI代码解释
复制
int* memory = new int[10];
delete[] memory;  // 使用delete[]释放动态分配的数组

并且,遵循RAII原则,优先考虑使用智能指针(如std::unique_ptrstd::shared_ptr)来自动管理内存,避免手动分配和释放内存带来的问题。

代码语言:Cpp
AI代码解释
复制
std::unique_ptr<int[]> memory(new int[10]);  // 自动释放内存

另一个需要注意的是,对于单个对象的动态内存分配,应当使用newdelete而非new[]delete[]

代码语言:Cpp
AI代码解释
复制
int* singleMemory = new int;
delete singleMemory;  // 正确释放单个对象的内存

17. 全局对象的时序和作用域问题

错误示例: 在C/C++程序中,全局对象的初始化顺序由编译器界定,非显式指定,可能会导致依赖全局对象的组件遭遇初始化时序问题,影响对象状态一致性及程序稳定性。

代码语言:C++
AI代码解释
复制
// cpp1
class Database {};  // 数据库类
Database globalDb;  // 全局数据库实例

// cpp2
class Service {
public:
    Service(Database& db) { std::cout << "Service initialized." << std::endl; }
};

Service globalService(globalDb);  // 依赖全局数据库的服务实例

Service类依赖于Database实例。尽管直觉上globalDb应先于globalService初始化。但依据C++标准,全局对象的初始化顺序未严格规定,尤其在不同编译器或复杂项目中,可能导致Service使用未完全初始化的Database对象,引发未预期行为。

解决方法:

  • 避免全局依赖:尽量设计成局部或通过参数传递依赖,减少系统范围的耦合。
  • 利用单例模式:确保依赖以可控顺序初始化,尤其适用于需全局访问但需管理初始化时机的场景。
  • 静态局部变量:在函数内部使用静态局部变量初始化依赖,这样可以在首次使用时按需初始化,且顺序更为确定。
  • 显式初始化函数:编写一个启动或配置函数来手动控制所有组件的初始化顺序。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
17个C++编程常见错误及其解决方案
  想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
开源519
2025/02/27
2190
17个C++编程常见错误及其解决方案
C/C++开发基础——原子操作与多线程编程
因为,thread类的构造函数是一个可变参数模板,可接收任意数目的参数,其中第一个参数是线程对应的函数名称。
Coder-ZZ
2023/11/13
5910
C/C++开发基础——原子操作与多线程编程
C++线程
C和C++的线程用法区别可以从多个角度进行比较,包括线程创建与管理、线程同步、传递对象、异常处理等方面。以下是C与C++线程用法的全面总结:
ljw695
2024/11/15
1080
C++线程
【C++】基础:常见错误与异常处理
在C++中,异常处理是一种用于捕获和处理程序运行期间产生的错误情况的机制。异常处理允许我们在程序中指定可能会引发异常的代码块,并定义相应的处理逻辑。
DevFrank
2024/07/24
2500
C++中线程同步与互斥的4种方式介绍、对比、场景举例
在C++中,当两个或更多的线程需要访问共享数据时,就会出现线程安全问题。这是因为,如果没有适当的同步机制,一个线程可能在另一个线程还没有完成对数据的修改就开始访问数据,这将导致数据的不一致性和程序的不可预测性。为了解决这个问题,C++提供了多种线程同步和互斥的机制。
码事漫谈
2025/01/12
5320
C++中线程同步与互斥的4种方式介绍、对比、场景举例
C++11 thread_local的 用法
这里有一个很重要的信息,就是 static thread_local 和 thread_local 声明是等价的,都是指定变量的周期是在线程内部,并且是静态的。这是什么意思呢?举个代码的例子。
zayyo
2023/11/30
5590
[C++] 智能指针
在现代C++开发中,资源管理(包括内存、文件句柄、锁等)是一个至关重要的问题。特别是在异常安全性设计中,如何避免资源泄漏是开发者必须面对的挑战。
DevKevin
2024/11/17
3520
[C++] 智能指针
C++智能指针
C++智能指针 零、前言 一、为什么需要智能指针 二、内存泄漏 三、智能指针 1、RAII 2、智能指针的原理 3、std::auto_ptr 4、std::unique_ptr 5、std::shared_ptr 6、std::weak_ptr 7、删除器 8、C++11和boost中智能指针的关系 零、前言 本章主要讲解学习C++中智能指针的概念及使用 一、为什么需要智能指针 示例: double Division(int a, int b) { // 当b == 0时抛出异常 if (b =
用户9645905
2022/11/15
6440
C++智能指针
来聊聊C++中头疼的线程、并发
在一个应用程序(进程)中同时执行多个小的部分(线程),这就是多线程。多个线程虽然共享一样的数据,但是却执行不同的任务。
AI算法修炼营
2020/05/08
5.1K0
【C++】C++11线程库 和 C++IO流
1. C++11的线程库实际封装了windows和linux底层的原生线程库接口,在不同的操作系统下运行时,C++11线程库可以通过条件编译的方式来适配的使用不同的接口,比如在linux下,就用封装POSIX线程库的接口来进行多线程编程,在windows下,就用封装WinAPI线程库的接口来进行多线程编程。所以C++11线程库为我们带来了可移植性编程。
举杯邀明月
2023/10/17
3650
【C++】C++11线程库 和 C++IO流
Boost C++ 库 | 多线程
Qt历险记
2024/10/24
2370
Boost C++ 库 | 多线程
C++:thread | condition_variable|mutex
相信大家在Linux系统编程中都接触过线程创建和退出的相关系统调用,这些系统调用是Linux环境下的一套线程设计方案。但是这种设计方案仅限于Linux环境下使用,其缺点就是可移植性差。所以C++设计了thread库,该库可以适用于任何平台下,从根本上解决了可移植性差的问题。
破晓的历程
2024/10/10
1330
C++编程经验(12):C++11新特性
没有系统学过,所以这篇写的基本都是我接触过的,接触过多少就整理多少吧。 有些特性也不知道是不是新的,反正都是我新接触的,用的还挺顺手。
看、未来
2021/10/09
1.2K0
C++编程经验(12):C++11新特性
【c++】智能指针详解&&c++特殊类设计&&c++的类型转换
下面我们先分析一下下面这段程序有没有什么内存方面的问题?提示一下:注意分析MergeSort函数中的问题
用户10925563
2024/08/06
2190
【c++】智能指针详解&&c++特殊类设计&&c++的类型转换
美团一面——为什么会有虚假唤醒?
本文为 C++ 一面的面试真题——为什么会有虚假唤醒?,主要考察了条件变量(std::condition_variable)的使用以及虚假唤醒的概念。本文将详细探讨虚假唤醒的定义、触发原因以及如何避免它。
程序员的园
2025/02/04
1400
美团一面——为什么会有虚假唤醒?
C++锁(万字长文):概念、不同锁实现、死锁现象+代码实例+预防+避免、加锁性能降低8种有效策略
锁是一种同步原语,用于保证多个线程在访问共享资源时的互斥性。通过加锁机制,可以确保在某一时刻,只有一个线程能够访问共享资源。
码事漫谈
2024/12/26
1.6K4
C++锁(万字长文):概念、不同锁实现、死锁现象+代码实例+预防+避免、加锁性能降低8种有效策略
C++并发编程的同步介绍
上面的互斥锁只是在共享数据处执行保护操作,但是数据的同步,即线程对数据的操作的先后次序并不确定,当我们还想对线程同步时,必须采取一定的同步操作。条件变量是达到这个目的方法。
zayyo
2023/11/30
2760
【C++】智能指针的使用及其原理
下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到 执⾏,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛 出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起 来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了。
用户11375356
2025/02/14
1600
【C++】智能指针的使用及其原理
【C++】C++11的新特性 — 线程库 ,原子操作 , 条件变量
在Linux中我们了解了什么是线程: 【Linux】从零开始认识多线程 — 线程概念与底层实现 【Linux】从零开始认识多线程 — 线程控制 【Linux】从零开始认识多线程 — 线程ID 【Linux】从零开始认识多线程 — 线程互斥
叫我龙翔
2024/08/13
3510
【C++】C++11的新特性 — 线程库 ,原子操作 , 条件变量
C++ 实现多线程生产者消费者模式
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。
为为为什么
2023/03/24
2.7K0
C++ 实现多线程生产者消费者模式
相关推荐
17个C++编程常见错误及其解决方案
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档