常见错误
本页面主要列举一些竞赛中很多人经常会出现的错误。
会引起 CE 的错误
这类错误多为词法、语法和语义错误,引发的原因较为简单,修复难度较低。
例:
-
int main()
写为int mian()
之类的拼写错误。 -
写完
struct
或class
忘记写分号。 -
数组开太大,(在 OJ 上)使用了不合法的函数(例如多线程),或者函数声明但未定义,会引起链接错误。
-
函数参数类型不匹配。
-
示例:如使用
<algorithm>
头文件中的max
函数时,传入了一个int
类型参数和一个long long
类型参数。
-
-
使用
goto
和switch-case
的时候跳过了一些局部变量的初始化。
不会引起 CE 但会引起 Warning 的错误
犯这类错误时写下的程序虽然能通过编译,但大概率会得到错误的程序运行结果。这类错误会在使用 -W{warningtype}
参数编译时被编译器指出。
-
赋值运算符
=
和比较运算符==
不分。-
示例:
-
如果确实想在原应使用
==
的语句里使用=
(比如while (foo = bar)
),又不想收到 Warning,可以使用 双括号:while ((foo = bar))
。
-
-
由于运算符优先级产生的错误。
-
示例:
-
-
不正确地使用
static
修饰符。 -
使用
scanf
读入的时候没加取地址符&
。 -
使用
scanf
或printf
的时候参数类型与格式指定符不符。 -
同时使用位运算和逻辑运算符
==
并且未加括号。- 示例:
(x >> j) & 3 == 2
- 示例:
-
int
字面量溢出。- 示例:
long long x = 0x7f7f7f7f7f7f7f7f
,1<<62
。
- 示例:
-
未初始化局部变量。
未初始化变量会发生什么原文:https://loj.ac/d/3679 by @hly1204
例如我们在 C++ 中声明一个
int a;
但不初始化,可能有时候会认为a
是一个“随机”(其实可能不是真的随机)的值,但是可能将其认为是一个固定的值,但实际上并非如此。我们在简单的测试代码中
https://wandbox.org/permlink/T2uiVe4n9Hg4EyWT
代码是:
在一些编译器和环境上开启优化后,其输出为 false。
有兴趣的话可以看 https://www.ralfj.de/blog/2019/07/14/uninit.html,尽管其是用 Rust 做的实验,但是本质是一样的。
-
局部变量与全局变量重名,导致全局变量被意外覆盖。(开
-Wshadow
就可检查此类错误。) -
运算符重载后引发的输出错误。
- 示例:
既不会引起 CE 也不会引发 Warning 的错误
这类错误无法被编译器发现,仅能自行查明。
会导致 WA 的错误
-
上一组数据处理完毕,读入下一组数据前,未清空数组。
-
读入优化未判断负数。
-
所用数据类型位宽不足,导致溢出。
- 如习语“三年 OI 一场空,不开
long long
见祖宗”所描述的场景。选手因为没有在正确的地方开long long
(将整数定义为long long
类型),导致得出错误的答案而失分。
- 如习语“三年 OI 一场空,不开
-
存图时,节点编号 0 开始,而题目给的边中两个端点的编号从 1 开始,读入的时候忘记 -1。
-
大/小于号打错或打反。
-
在执行
ios::sync_with_stdio(false);
后混用scanf/printf
和std::cin/std::cout
两种 IO,导致输入/输出错乱。-
示例:
-
特别的,也不能在执行
ios::sync_with_stdio(false);
后使用freopen
。
-
-
由于宏的展开,且未加括号导致的错误。
-
示例:该宏返回的值并非
而是 。
-
-
哈希的时候没有使用
unsigned
导致的运算错误。- 对负数的右移运算会在最高位补 1。参见:位运算
-
没有删除或注释掉调试输出语句。
-
误加了
;
。-
示例:
-
-
哨兵值设置错误。例如,平衡树的
0
节点。 -
在类或结构体的构造函数中使用
:
初始化变量时,变量声明顺序不符合初始化时候的依赖关系。-
成员变量的初始化顺序与它们在类中声明的顺序有关,而与初始化列表中的顺序无关。参见:构造函数与成员初始化器列表 的“初始化顺序”
-
示例:
-
-
并查集合并集合时没有把两个元素的祖先合并。
-
示例:
-
换行符不同
在正式比赛中会尽量保证选手答题的环境和最终测试的环境相同。
本节内容仅适用于模拟赛等情况,而我们也建议出题人尽量让数据符合 数据格式。
不同的操作系统使用不同的符号来标记换行,以下为几种常用系统的换行符:
-
LF(用
\n
表示):Unix
或Unix
兼容系统 -
CR+LF(用
\r\n
表示):Windows
-
CR(用
\r
表示):Mac OS
至版本 9
而 C/C++ 利用转义序列 \n
来换行,这可能会导致我们认为输入中的换行符也一定是由 \n
来表示,而只读入了一个字符来代表换行符,这就会导致我们没有完全读入输入文件。
以下为解决方案:
-
多次
getchar()
,直到读到想要的字符为止。 -
使用
cin
读入,这可能会增大代码常数。 -
使用
scanf("%s",str)
读入一个字符串,然后取str[0]
作为读入的字符。 -
使用
scanf(" %c",&c)
过滤掉所有空白字符。
会导致未知的结果
未定义行为会导致未知的结果,可能是 WA,RE 等。编译器通常会假定你的程序不会出现未定义行为,因此出现开 O2 与不开 O2 代码行为不一致的情况。
-
除以 0(求 0 的逆元)
示例 -
数组(下标)越界
例如:
-
未正确设置循环的初值导致访问了下标为 -1 的值。
-
无向图边表未开 2 倍。
-
线段树未开 4 倍空间。
-
看错数据范围,少打一个零。
-
错误预估了算法的空间复杂度。
-
写线段树的时候,
pushup
或pushdown
叶节点。正确的做法:不要越界,记得检查自己的代码,使得下标访问数
x
,在定义的下标中。
-
-
除 main 外有返回值函数执行至结尾未执行任何 return 语句
即使有一个分支有返回值,但是其他分支却没有,结果也是未定义的。
可以向编译选项中追加
-Wall
,检查编译器是否给出有关于函数未 return 的警告。 -
尝试修改字符串字面量
示例这样试图修改字符串字面量会导致 未定义行为,应当使用其他 合适 的数据类型,例如
std::string
和char[]
。 -
多次释放/非法解引用一片内存
例如:
-
未初始化就解引用指针。
-
指针指向的内存区域已经释放。
使用
erase
或delete
或free
操作应注意不要对同一地址/对象多次使用。
-
-
解引用空指针/野指针
对于空指针:先应该判断空指针,可以用
p == nullptr
或!p
。对于野指针:可以释放指针的时候将其置为
nullptr
以规避。 -
有符号数溢出
例如我们有一个表达式
x+1 > x
。正常输出应当是
true
,但是在INT_MAX
作为x
时输出false
,这时称为signed integer overflow
。可以使用更大的数据类型(例如
long long
或__int128
),或判断溢出。若保证无负数,亦可使用无符号整型。有符号整数溢出可能影响编译优化,例如代码:
可能被编译器直接优化为:
因为编译器可以假定有符号整数永远不会溢出,因此
x > x + 1
恒成立。 -
使用未初始化的变量
示例
会导致 RE
-
没删文件操作(某些 OJ)。
-
排序时比较函数的错误
std::sort
要求比较函数是严格弱序:a<a
为false
;若a<b
为true
,则b<a
为false
;若a<b
为true
且b<c
为true
,则a<c
为true
。其中要特别注意第二点。 如果不满足上述要求,排序时很可能会 RE。 例如,编写莫队的奇偶性排序时,这样写是错误的:上述代码中
(block[a.l]&1)^(a.r<b.r)
不满足上述要求的第二点。 改成这样就正确了:
会导致 TLE
-
分治未判边界导致死递归。
-
死循环。
-
循环变量重名。
-
循环方向反了。
-
-
BFS 时不标记某个状态是否已访问过。
-
使用宏展开编写 min/max
这种错误会大大增加程序的运行时间,甚至直接影响代码的时间复杂度。在初学者写线段树时尤为多见。
常见的错误写法是这样的:
这样写虽然在正确性上没有问题,但是如果直接对函数的返回值取 max,如
a = Max(func1(), func2())
,而这个函数的运行时间较长,则会大大影响程序的性能,因为宏展开后是a = func1() > func2() ? func1() : func2()
的形式,调用了三次函数,比正常的 max 函数多调用了一次。注意,如果func1()
每次返回的答案不一样,还会导致这种max
的写法出现错误。例如func1()
为return ++a;
而a
为全局变量的情况。示例:如下代码会被卡到单次查询
导致 TLE。 -
没删文件操作(某些 OJ)。
-
在
for/while
循环中重复执行复杂度非 的函数。严格来说,这可能会引起时间复杂度的改变。
会导致 MLE
-
数组过大。
-
STL 容器中插入了过多的元素。
-
经常是在一个会向 STL 插入元素的循环中死循环了。
-
也有可能被卡了。
-
会导致常数过大
-
定义模数的时候,未定义为常量。
-
示例:
-
-
使用了不必要的递归(尾递归不在此列)。
-
将递归转化成迭代的时候,引入了大量额外运算。
只在程序在本地运行的时候造成影响的错误
-
文件操作有可能会发生的错误:
-
对拍时未关闭文件指针
fclose(fp)
就又令fp = fopen()
。这会使得进程出现大量的文件野指针。 -
freopen()
中的文件名未加.in
/.out
。
-
-
使用堆空间后忘记
delete
或free
。
贡献者:@MapMaths@Yingchi@Zirnc@66Leo66@CCXXXI@Shuzhou@Ir1dXD@Danni@谭九鼎@Yiming@mgt@StudyingFather@NachtgeistW
本页面最近更新:2/3/2023, 12:00:00 AM,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用