调试技巧 与 编码注意事项

写程序检查

  • 编译时,加上-Wall,否则函数没有写return不会报错(windows c++14),inline 函数没有指定返回值类型也不会报错(但是过不了linux的编译)。必要时,加上 -Werror 把 warning 变成 error 以避免眼瞎。

  • 多测要清空!

  • 如果怀疑是程序在某条分支出现错误,尝试在分支处输出不可能的答案,然后fc样例输出,看看这一答案对应的原样例输出和输入,说不定能发现什么猫腻。

  • 我们离散化的时候不是会用到unique吗?他的格式是这样的unique(start,end,compare),其中compare参数是代替==的比较函数,可选择不写。返回值是去重后的end

  • 调试过程: 分步调试,优先确定前面部分是否正确;输出中间值,善用assertcerr还有abort如果是在找不出哪里错了,就重构可能出错的部分!

  • 不要忘记删除 assert 否则会报错 Runtime Error. Received signal 6: Aborted / IOT trap.

    #include <cassert>
    #include <iostream>
    assert(bool,[char*]);//如果bool为false,输出char*(没有则不输出)
    cerr<<"Error"<<endl;//输出"Error"到标准错误流,不会干扰标准输出流
    abort c;//抛出错误c,c可以是数字、字符串等任意类型(?
    
  • 精度问题: 如果数据很大远超long long而且题目没让你取模,那么大概率是输出浮点数。这时要注意所有可能爆long long的地方都要用浮点数(double)。另外,eps最好设置小一点(至少比要求的精度小 \(2\) 个数量级)。

  • 输出精度问题:double 输出时不一定会把所有位数都输全,所以要写 setprecision 才能保证输出的精度正确。

  • 数据类型存储范围: 记住int范围在 \(2\times 10^9\) 左右,long long范围在 \(9\times 10^{18}\) 左右,unsigned是两倍范围。切记,在 \(10^9\) 的要乘法要开long long,在 \(10^{18}\) 的要乘法的要开__int128。记住long long下溢出但没有溢出ull的转化为ull后会变为正确的数字。大部分二元运算会把两侧的强转为较高范围的一级。

  • 输出变量名: 如果你想输出变量但又分不清变量是哪个,可能会想要输出变量名。一个一个地敲实在是太慢了,试试这个!

    #define PT(name) cout<<(#name)<<"="<<name<<" ";
    

    此处(#name)会把替代name的事物的名称直接转换为字符串输出,因此这个“函数”可以输出a=1这样的效果。

  • 除以2问题: /2是向0取整,>>1是向下取整

  • 斜率优化之类的代码量小的题目还是别对拍了。对拍也看不出答案,不如瞪眼法。

编译器问题

  • DEV-C++ 编译器可能会允许没有指定返回值类型的函数通过编译(我因为这个问题写挂了两次BIT),但是某些平台如 HydroOJ 不行。
  • 注意保留 X 位小数的要求!编译器可能默认输出 \(4\) 位,但是别的编译器未必如此。

卡常

memset在赋值 \(0\) 或者 \(-1\) 的时候非常快,而赋值 0x3f 的时候就比较慢了。务必注意数组很大的时候memset常数带来的区别。

一些卡常的技巧:

  • 函数返回类型前加inline(据说O2会自动优化)
  • 修改枚举顺序,或者修改数组两个维度的顺序,利用内存连续访问的特性(例如矩阵乘法,或者把st表log维放在前面)
  • 非必要不开long long。特别是大量取模的情况。
  • 循环变量类型前加register
  • 函数传入的参数如果不需要修改,那么写成const int &a
  • 慎用memset,特别是多测
  • 慎用 vector,特别是扫描线和建图。有数倍常数。
  • 关闭输入输出同步流:
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
  • 注意此时不能用endl,而是要用"\n",否则刷新缓冲区会耗费很多时间。特别适用于大量换行的题目。

使用 GPROF

编译 g++ -pg -o test test.cpp
运行程序之后会生成 gprof.out
最后 gprof test.exe gmon.out 就会输出每个函数消耗的时间。

提交前检查

空间开没开够,开没开多,会不会MLE?

跑一遍手生成的大样例看看会不会TLE?

检查有没有编译器的逆天错误,例如非void函数没有return 或者 函数没有声明返回值类型

检查freopen

提交后检查

如果在本地和OJ评测结果不一样,可能是因为:

  • 有未定义行为:加入-Wall -fsanitize=leak,undefined,address编译选项;检查函数有没有返回值,有没有地址溢出,有没有变量未初始化
  • 栈空间不足:本地栈空间不足,可以使用-Wl,--stack=536870912命令开出 \(512\) MB空间(后面的数字是字节数)。
  • 评测机环境与本地不同:尤其注意windows环境下的换行字符有\r,而linux环境下没有。
  • 对于\r:如果使用getline等等输入方式读入一整行,会读入\r作为字符串的一部分,此时需要特判并删除。

基于 IOI 赛制的方法:在可能错误的地方加入assert或者abort,如果其他错误变成了 \(\color{purple}\text{RE}\) 说明你猜对了。

洛谷评测机不会递归爆栈,但是即使是数组越界也可能WA(特别是数组较小的时候)!

queue、deque 的 MLE:为了性能,queue 在 pop 时不会释放内存,所以把多个 queue 反复倒来倒去可能会导致 MLE。另外,建立一个queue初始就会自带约10倍内存常数,造成巨大负担。避免使用queue、deque!

Signal 7: Bus Error :总线错误。访问了非法的地址(如很小的负数下标),并开启 O2 优化后可能会出现,不过根据数组大小,可能也有 Signal 11: Segmentational Fault 出现。

posted @ 2024-10-31 20:43  Luke_li  阅读(62)  评论(0)    收藏  举报