分类
Level0

常见错误与调试方法

以Google工作中最有效率的一个月为例,使用Google的一款内部工具,即可以查看每天的代码增量(包括添加、删除、更改的代码行数),根据以往的调查证明,一名高效的Google工程师每天能写100-150行代码。

每天刷1-2道题,基本上就能到100行代码。但学习的价值在于思考,不能沉迷于堆积低质量的代码,不能沉迷于刷水题。

一、常见编译错误

  1. 链接错误: [Error] ld returned 1 exit status:链接失败- 大多因为执行窗口没有关闭
  2. 头文件错误: [Error] cstdio: No such file or directory 没有这个文件或者目录
  3. 定义错误; [Error] ‘ans’ does not name a type
    int n, sum; ans;
  4. 中文字符错误: [Error] stray ‘\243’ in program ; ; 正确的英文操作符devc++中符号的关键字会显示红色
  5. 变量未声明,直接使用: [Error] ‘c’ was not declared in this scope
  6. 漏写;号: [Error] expected ‘;’ before ‘}’ token 
    for(;) for(,,)
  7. 漏写判断条件 或 括号() {}不匹配  [Error] expected ‘(‘ before ‘{‘ token 
    while {}
  8. 函数声明和定义时类型不一致
  9. 子函数没有定义  ‘xxx’ was not declared in this scope

二、编译时不报错,但是运行时会错误:

  1. 超出数据类型的数据范围
无符号字符型 unsigned char 0~255

短整型 short [int] -32768~32767
无符号短整型unsigned short[int] 0~65535

整型 [signed]int -2147483648~+2147483647   ---  2*10^9
无符号整型unsigned[int] 0~4294967295 ---  4* 10^9

(64bit 8Bytes)
long long的最大值:9223372036854775807   ---  10^18
long long的最小值:-9223372036854775808
unsigned long long的最大值:18446744073709551616

int = 4字节
char = 1字节
long long = 8字节
128M = 131072KB = 134217728字节

定义int型的一维数组最多是3千万, long long型1千5百万, char型1亿左右
  1. scanf()读取了错误类型,比如整数类型读成了字符串
float f;
scanf("%d", &f); //定义float,读成了整型 %d,错误类型
  1. if判断中 == 写成了 =
  2. 多层条件语句中的 if 和 else 不配对
  3. 数组越界
  4. 循环语句中,循环控制变量没有修改,造成了死循环
  5. switch 语句漏掉 break
  6. 不小心除0,不能将0作为除数

三、避免错误的方法:被虐多了,也就记住了,多做题勤总结

  1. 数组多定义几个元素,不怕浪费空间
  2. 变量 ijkmn常用做整形,float变量可以f开头,变量命名形成习惯
  3. 所有判断语句中if() while() for(;;)不要使用=,赋值可以放在前面; 这样容易检查出==写成=号的问题
  4. 除了临时使用的变量,其他变量都定义成全局变量(这一条只适用于竞赛),可以避免栈不够用和忘记初始化的问题
  5. 程序复杂了,可以分出来子函数
  6. 避免局部变量覆盖全局变量

四、常用的调试方法,在竞赛中打印更有效、直观

  1. 关键路径上加上printf用于调试(万能的打印: printf或cout (强调无数次,还是要强调)
  2. 检查输入读取是否正确
  3. 检查判断条件是否有遗漏
  4. 检查循环次数是否正确 0-n 还是1-n 是 小于 还是 小于等于
  5. 检查输出格式是否正确
  6. 总而言之,言而总之,不要怕加打印,你的眼力一定比不过打印输出来得犀利。最后,提交代码前一定要将调试代码注释掉,不然爆零。
#include <cstdio>
using namespace std;
int k, ans=0, num, num_days;
int main(){
    scanf("%d", &k);
    num = 1;
    num_days = 0;
    for(int i = 1; i<=k; i++){
	num_days++;
	if(num_days <= num) {	
	    ans += num; 
	   //打印出天数 和 当天发的金币数 进行验证		
	   //printf("%d   %d\n", i, num);
	}else{
	    num_days=1;
	    num++;
	    ans += num;
	    //printf("%d   %d\n", i, num);
	    }		
	}
	printf("%d", ans);
	return 0;
}

常用调试输出语句:

printf("%d %d\n", a,b)
if(a>b){
printf("enter a>b");
}

int a[100]
for(int i =0; i < 100; i++) {
printf("a[%d]=%d\n", i a[i]);
}

int f(int n) {
printf("calling f(%d)\n", n);
}

函数递归调用栈的最大空间分析

// 函数递归调用栈的最大空间分析.cpp 
#include <iostream>
using namespace std;
int i=1;
void fun(){
    // (1)变量总空间在16字节及以下
    char a,b,c;
    printf("No.%d %p\n",i,&a);
    i++;
    fun();
	
    // (2)三个小数组的情况
    //char a[16],b[16],c[16];
    //printf("No.%d %ld字节 %p\n",i,sizeof(a),a);
    //i++;
    //fun();
		
    // (3)一个大数组的情况
    //char a[10240];
    //printf("No.%d %ld字节 %p\n",i,sizeof(a),a);
    //i++;
    //fun();
}
int main(){
    fun();
    return 0;
}


(1)变量总空间在16字节及以下
要按16字节计算,说明栈元素的空间动态扩展是每次增加16字节
……
No.32458 0000000000034bdf
No.32459 0000000000034b9f
No.32460 0000000000034b5f
No.32461
计算:
9fH-5fH=40H=64=48+16
32461*64=2077504=1.9812MB

(2)三个小数组的情况:
……
No.21638 16字节 0000000000034c30
No.21639 16字节 0000000000034bd0
No.21640 16字节 0000000000034b70
No.
计算:
d0H-70H=60H=96=48+316 2164096=2077440=1.9812MB

(3)一个大数组的情况:
……
No.198 10240字节 000000000003e930
No.199 10240字节 000000000003c100
No.200 10240字节 00000000000398d0
No.201 10240字节 00000000000370a0
计算:
98d0H-70a0H=2830H=10288=48+10240
201*10288=2067888=1.9720MB

从两种情况下的数据地址跟踪的结果分析可以看出:
1、48字节应是函数递归堆栈结构体的固定空间。
2、函数递归调用的堆栈的最大存储空间约为2MB,
这也是局部数组的最大空间,所以函数递归的栈数据是用局部数组来实现的。

每个操作系统的调试结果可能有差异,自行调试分析即可。