17个C 编程常见错误及其解决方案
TOC
引言
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
1. 空指针解引用
错误示例:
代码语言:Cpp复制int* ptr = nullptr;
std::cout << *ptr; // 解引用空指针,可能导致段错误
解决方法: 在访问指针之前,务必检查其是否为空。
代码语言:Cpp复制if (ptr != nullptr) {
std::cout << *ptr;
}
2. 多线程竞争条件
错误示例: 多个线程同时读写同一数据,未加锁保护。
代码语言:Cpp复制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复制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复制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复制char str[10];
strcpy(str, "This is a very long string."); // 可能造成缓冲区溢出
解决方法: 使用安全的字符串处理函数,如strncpy或C 11之后的std::string。
5. 悬挂指针
错误示例: 指向动态分配内存的指针在释放内存后仍被继续使用。
代码语言:Cpp复制int* p = new int(5);
delete p;
*p = 10; // 悬挂指针,可能导致段错误
解决方法: 释放内存后将指针置为nullptr,表明它不再指向有效的内存。
6. 未捕获的异常
错误示例: 函数内部抛出异常但未被捕获。
代码语言:Cpp复制void mayThrowException() {
throw std::runtime_error("An error occurred.");
}
int main() {
mayThrowException(); // 如果没有捕获,程序会终止
return 0;
}
解决方法: 在可能抛出异常的地方添加try-catch
块,并妥善处理异常。
7. 浮点数精度丢失
错误示例: 依赖于精确的浮点数计算。
代码语言:Cpp复制double a = 0.1;
double b = 0.2;
if (a b == 0.3) { // 这里可能为假,因为浮点数运算存在精度误差
// ...
}
解决方法: 尽量避免直接比较浮点数相等,而是设定一个合理的误差范围。
8. 无符号整数溢出
错误示例: 对无符号整数执行减法,当结果小于零时可能会导致意外的大数值。
代码语言:Cpp复制unsigned int a = 0;
unsigned int b = 1;
std::cout << a - b; // 输出的结果将是UINT_MAX
解决方法: 理解并谨慎使用无符号整数,尤其是涉及负数操作时。
9. 隐式类型转换
错误示例: 不同类型的表达式混合运算导致隐式类型转换,产生非预期结果。
代码语言:Cpp复制long long num1 = LLONG_MAX;
int num2 = INT_MAX;
long long result = num1 num2; // num2提升为long long后导致溢出
解决方法: 尽量避免隐式类型转换,明确指定类型转换以防止潜在问题。
10. 未正确关闭文件
错误示例: 打开文件后在程序结束前忘记关闭,可能导致数据丢失或文件句柄耗尽。
代码语言:Cpp复制std::ofstream file("output.txt");
file << "Some content";
// 忘记调用file.close()
解决方法: 始终确保在适当的时间关闭文件,可以使用RAII(Resource Acquisition Is Initialization)技术,例如智能指针或C 11引入的std::ofstream的析构函数会自动关闭文件。
11. 无符号整数循环条件错误
错误示例: 在循环中使用无符号整数作为递减计数器,当期望循环结束时计数器为0,但由于无符号整数的特性导致无法正确终止循环。
代码语言:Cpp复制unsigned int counter = 5;
while (counter >= 0) { // 由于counter是无符号整数,当它递减至0时不会变为负数
// 循环体执行
--counter;
} // 本应在counter为0时退出循环,但实际上会进入死循环
解决方法: 确保正确设置循环条件,针对无符号整数的特性,应当避免在计数器达到其自然结束点时依赖于负数条件。可以使用固定的循环次数或另一个合适的终止条件来替代。
代码语言:Cpp复制unsigned int limit = 5;
for (unsigned int counter = 0; counter < limit; counter) {
// 循环体执行
} // 当counter到达limit时,循环自然结束
12. 错误的类型转换
错误示例: 强制类型转换可能掩盖潜在的逻辑错误,特别是在不同类型之间赋值或比较时。
代码语言:Cpp复制double d = 3.14;
int i = d; // 损失精度
if (d == 3) { // 可能永远不成立,因为浮点数与整数比较会有精度损失
// ...
}
解决方法: 除非必要,否则尽量避免强制类型转换,尤其是在比较和赋值操作中,确保正确处理类型之间的转换。
13. 循环体内的副作用
错误示例: 在循环体内修改迭代变量,导致意料之外的循环行为。
代码语言:Cpp复制for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); it) {
if (*it == target) {
it = vec.erase(it); // 直接删除当前元素可能导致未遍历完剩余元素
}
}
解决方法: 在循环体内避免对用于迭代的对象进行修改,若必须删除或移动元素,可选择复制迭代器或使用其它合适的数据结构操作方法。
14. 字符串字面量和字符数组混淆
错误示例: 初始化字符数组时,误用字符串字面量,导致未正确终止的字符串。
代码语言:Cpp复制char name[8] = "John Doe"; // 缺少终止符'