Skip to content

常见的错误和警告

参考了来自 OI WIKI 常见错误和 Orz panda 常见错误等。 有时间后面再更。

编译错误的可能情况

  • 选择了不恰当的语言或编译器进行编译。
    • 在 XDOJ 中, 推荐使用 GNU C/C++, 进行编译。
  • 拼写错误, int main() 写为 int mian()
  • 写完 structclass 忘记写分号。
  • 数组开太大, (在 OJ 上)使用了不合法的函数(例如多线程), 或者函数声明但未定义, 会引起链接错误。
  • 函数参数类型不匹配。
    • 如使用 <algorithm> 头文件中的 max 函数时, 传入了一个 int 类型参数和一个 long long 类型参数。
  • 使用 gotoswitch-case 的时候跳过了一些局部变量的初始化。
  • 滥用 <bits/stdc++.h>

编译警告的可能情况

这类错误时写下的程序虽然能通过编译, 但大概率会得到错误的程序运行结果。这类错误会在使用 -W{warningtype}(比如说 -Wall ) 参数编译时被编译器指出。

  • 赋值运算符 = 和比较运算符 == 不分。

    • 如果确实想在原应使用 == 的语句里使用 =(比如 while (foo = bar)), 又不想收到 Warning, 可以使用 双括号while ((foo = bar))
  • 由于运算符优先级产生的错误。如果你不知道优先级, 加个括号总是没问题的。

  • 不正确地使用 static 修饰符。
  • 使用 scanf 读入的时候没加取地址符 &
  • 使用 scanfprintf 的时候参数类型与格式指定符不符。
    • 注意运算中可能带来的参数类型转换。
  • 同时使用位运算和逻辑运算符 == 并且未加括号。
    • 示例:(x >> j) & 3 == 2
  • int 字面量溢出。
    • 示例:long long x = 0x7f7f7f7f7f7f7f7f, 1<<62
  • 带符号整数的溢出。

    • 注意数据范围, 考虑使用 long long,但不要在任何情况下直接使用 long long, 可能会导致常数偏大或爆空间。
    • 带符号整数的溢出是未定义行为, 编译器输出的值 可能为任意值 , 参照CWE-190: Integer Overflow or Wraparound
    • 使用 -fsanitize=undefined -g 即可使得你的程序在触发带符号溢出时, 输出错误消息并显示对应的代码行号。但是你仍然需要先构造一组测试用例, 使得计算时使用的值大到可以产生溢出。
    • 由于编译器的优化, 可能会导致
  • 误加了 ; , 例如 while(a>0);

  • 未初始化局部变量/数组。

未初始化变量会发生什么

原文:https://loj.ac/d/3679 by @hly1204

例如我们在 C++ 中声明一个 int a; 但不初始化, 可能有时候会认为 a 是一个「随机」(其实可能不是真的随机)的值, 但是可能将其认为是一个固定的值, 但实际上并非如此。

我们在简单的测试代码中

https://wandbox.org/permlink/T2uiVe4n9Hg4EyWT

代码是:

#include <iostream>
int main() {
    int a;
    std::cout << std::boolalpha << (a < 0 || a == 0 || a > 0);
    return 0;
} 

在一些编译器和环境上开启优化后, 其输出为 false。

有兴趣的话可以看 https://www.ralfj.de/blog/2019/07/14/uninit.html, 尽管其是用 Rust 做的实验, 但是本质是一样的。

可能会导致答案错误的情况

  • 题交错了。
  • 题读假了。
  • 不测样例就交, 结果没过样例。
  • 只测样例就交, 对自己炒鸡自信。
  • 变量重名或混用。
    • 循环变量的混用。scd
    • 和输入输出格式中的声明不同, 理解不清导致混用。
    • 和标准库中函数或变量的重名。
  • 上一组数据处理完毕, 读入下一组数据前, 未清空数组。
    • 需要注意的是并不是所有的题都需要进行数组清空, 可能导致复杂度过大。
  • 读入优化未判断负数。
  • 所用数据类型位宽不足, 导致溢出。

    • 如习语「三年 OI 一场空, 不开 long long 见祖宗」所描述的场景。选手因为没有在正确的地方开 long long(将整数定义为 long long 类型), 导致得出错误的答案而失分。
  • 存图时, 节点编号 0 开始, 而题目给的边中两个端点的编号从 1 开始, 读入的时候忘记 -1。

  • 大/小于号打错或打反。
  • 在执行 ios::sync_with_stdio(false); 后混用 scanf/printfstd::cin/std::cout 两种 IO, 导致输入/输出错乱。

  • 在交互题内使用 cin.tie() 且不使用 endlflush 刷新缓冲区。

    • 不使用 cout.tie(), 该语句没有任何作用。
  • 由于宏的展开, 且未加括号导致的错误。

  • 没有删除或注释掉调试输出语句。
  • 哨兵值设置错误。例如, 平衡树的 0 节点。
  • 在类或结构体的构造函数中使用 : 初始化变量时, 变量声明顺序不符合初始化时候的依赖关系。
  • 并查集合并集合时没有把两个元素的祖先合并。

未定义行为

未定义行为一般会导致未知的结果。

  • 除以 0
  • 数组下标越界

    • 未正确设置循环的初值导致访问了下标为 -1 的值。

    • 无向图边表未开 2 倍。

    • 线段树未开 4 倍空间。

    • 看错数据范围, 少打一个零。

    • 错误预估了算法的空间复杂度。

    • 写线段树的时候, pushup 或 pushdown 叶节点。

  • 除 main 外有返回值函数执行至结尾未执行任何 return 语句

    • 即使有一个分支有返回值, 但是其他分支却没有, 结果也是未定义的。可以向编译选项中追加 -Wall, 检查编译器是否给出有关于函数未 return 的警告。

可能导致运行时错误

  • 没有删除或注释文件操作语句(在某些OJ上)。
  • 在 DOMJUDGE 中, MLE 标记为 RE 。

可能导致超时

  • 算法复杂度不对, 或出题人故意卡掉了你的算法。
  • 分治未判边界导致死递归。
  • BFS 时不标记某个状态是否已访问过。
  • 使用宏来展开编写 maxmin

可能导致超内存

  • 数组过大。
  • STL 容器中插入了过多的元素。