当前位置 : 主页 > 编程语言 > c语言 >

调试技巧(2)

来源:互联网 收集:自由互联 发布时间:2023-09-03
所爱隔山海,山海皆可平,所念皆星河,星河不可及。 上课! 接着上节课讲的调试(1),本节课进一步讲解调试(2).@TOC1.调试实例讲解(2) 校招笔试题 2.如何写出好的(易于调试)代

调试技巧(2)_空指针

所爱隔山海,山海皆可平,所念皆星河,星河不可及。

调试技巧(2)_指针变量_02


调试技巧(2)_空指针_03


调试技巧(2)_c++_04

上课!

接着上节课讲的调试(1),本节课进一步讲解调试(2). @TOC 1.调试实例讲解(2)

校招笔试题

2.如何写出好的(易于调试)代码?

3.编程常见的错误

代码改错

int main()
{
	int i = 0;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (i = 0; i <=12; i++)
	{
		printf("hehe\n");
		arr[i] = 0;
	}
	return 0;
}
请复制这段代码到本地编译器运行,看看为什么会出现问题。运行起来后会发现,死循环打印hehe,为什么呢? 我们调试起来会发现一个非常奇怪的现象:i的值和arr[12]的值居然是同步改变的,这到底是为什么呢?规定: 1.局部变量的存储是存储于栈区上的 2.栈区的使用习惯是: 先使用高地址的空间 再使用低地址的空间在内存中大致有下面这三块空间分布,吧栈区放大后,假设顶部是高地址,则底部就是低地址,如图:先创建局部变量i,i就使用高地址处的一块空间,再创建数组arr,arr[0]开始创建低地址处的一块空间。然后再创建到arr[9]时,i和arr[9]之间恰好有两块整型大小的空间,也就是绿色的空间随后,arr[12]的空间创建自然而然地就和 i 在同一块空间了你一定会问,为什么在 i 和arr[9]之间恰好有两块整型大小的空间?不能是三块空间吗?不能是一块空间吗? 声明:以上现象只针对于vs2019 X86 Debug调试环境下产生的现象说那么多东西,只为了说明一件事: 调试的重要性!!调试可以帮助你找到自己的错误!学会调试,你的能力将达到一个质的飞跃。 上节课讲过,Debug和Relese版本的不同 在这个例子中,在不同版本下所出现的情况也不同:

在Release版本底下,i和 arr[12]所在的地址也不一样了。到这里,有同学会问到:既然 局部变量i 先创建内存空间,arr后创建那为什么不先创建arr的内存空间,后创建局部变量i的空间呢?这样i的内存空间不就在arr下面了吗,这样不就不会死循环了吗?来演示一下结果:

现在把 i 放在arr之后,也就是 i 的创建是在arr之后的,出现上面的结果

意思就是,数组越界了。 那为什么第一次死循环的时候 , 又没有报这个错误呢? 因为程序在忙着打印hehe,没空给你报错 就是这个道理 程序在忙着做自己的事情打印hehe 根本没空搭理你,给你报错 下面来一道题目

  • 公司校招笔试题: 在Linux x86_64 gcc环境下,下面的程序会出现什么问题?运行的结果是什么?int main(int argc,char*argv[]) { long i; long a[16]; for (i = 0; i <= 17; i++) { a[i] = 0; printf("%d", i); } return 0; }其实这道题的本质是与上面的例题相同的只要你写出上面的分析过程,就能拿下这道题该题运行的现象仍然是会出现死循环

2.如何写出好的代码?

2.1代码运行正常bug很少效率高可读性高可维护性高注释清晰文档齐全 常见的coding技巧使用assert尽量使用const养成良好的编码风格5添加必要的注释避免编码的陷阱下面以一个例子说明上面所有的要点:模拟实现my_strcpy的功能strcpy函数,简单来说就是把一个字符串拷贝到另一个字符串里面//先观察strcpy的功能 #include int main() { char arr[20] = "xxxxxxxxxxxxxxx"; char str[] = "hello world"; strcpy(arr, str); printf("%s\n", arr); }运行结果如下: 在c库中,可以搜索到strcpy函数的相关信息: 现在来模拟my_strcpy函数void mystrcpy(char* dst, char* src) { while(*dst++=*src++) //等价于 //*dst = *src; //dst++,src++; { ; } } int main() { char arr1[20] = { 0 }; char arr2[] = "hello"; my_strcpy(arr1, arr2); printf("%s\n", arr1); }我们一般是这么写的但是这样写还有什么隐患呢?1.假如传过去的指针是空指针,就会出现问题,所以我们需要在while前面判断是否为空指针if(*dst ==NULL || *stc == NULL) { return ; }但是这样写,还是会有问题,为什么呢? 1.每次进入my_strcpy函数内部,都要执行if语句,不管它是不是空指针。 2.它不会暴露错误出来,就算是空指针,也会悄悄规避掉,程序员无法知道自己穿的是空指针。 所以,引用 “断言”-----assert assert内部可以放一个表达式,表达式如果为假,就报错,为真,啥事没有。仍然用上面的代码,来举例:void my_strcpy(char* dst, char* src) { assert(dst !=NULL && src != NULL);//断言 while (*dst++ = *src++) { ; } } int main() { char arr1[20] = { 0 }; char* p = NULL;//p指向的常量字符串无法更改 my_strcpy(arr1, p); //如果是反着来,是无法更改的 printf("%s\n", arr1); }运行结果如下: 它不仅能报错,还指出是哪个文件目录下的错误这就是(断言) assert 的好处假设有一个程序员,将void my_strcpy(char* dst, char* src) { assert(dst && src); while(*dst++ = *src++ ) { ; } } int main() { char arr1[20] = { 0 }; char* p = "hello";//p指向的常量字符串无法更改 my_strcpy(arr1, p); printf("%s\n", arr1); }写成了while(*src++ =*dst++ ) { ; }也就是在while循环里面,将两个指针位置互换了这样写一定会有问题,那么会是什么问题? 可以知道,arr里面只有\0,把\0 复制到src的第一个位置后 循环马上停下了,输出的也就是arr1里面的内容,即\0如何避免呢?-----const 先来看一个例子, 可以通过指针修改 n 内部的值。但是当我在指针变量前面加上了 const后 , 结果如下: 它说,左值是必须可修改的,这就意味着,加上了const 后,指针指向的内容不可更改再来看一个例子: 看看有什么不同? 在这个例子中,我把const 放在了 * 的后面,这时候 p不能更改了。总结: const修饰指针变量的时候 1.const放在的左边,修饰的是指针指向的内容 表示指针指向的内容,不能通过指针来改变*2.也可以放在的右边,const修饰的是指针变量本身 表示指针变量本身的内容不能被修改,但是指针指向的内容, 可以通过指针来改变回到上面的例子,所以当我写成 const char* src的时候,就算写反了位置,也没关系 因为这样写的时候void my_strcpy(char* dst, const char* src) { assert(dst && src); while(*src++ = *dst++ ) { ; } } int main() { char arr1[20] = { 0 }; char* p = hello; my_strcpy(arr1, p); printf("%s\n", arr1); }编译器都报错了,更别说运行起来了看到这里,细心的朋友还会发现, 这里是char *类型的返回值呀,为什么上面写的都是void类型呢? 别着急,这就马上讲char* my_strcpy(char* dst, const char* src) { assert(dst && src); char* ret = dst;//存储dst的起始位置 while(*src++ = *dst++ ) { ; } return ret ; //这里不能返回dst,因为dst指向的空间不再是起始位置的地址了 } int main() { char arr1[20] = { 0 }; char* p = hello; my_strcpy(arr1, p); printf("%s\n", arr1); }大功告成! 总结:使用assert尽量使用const养成良好的编码风格5添加必要的注释避免编码的陷阱

3.编程常见的错误 3.1编译型错误直接看错误信息提示(双击错误行),或者根据经验直接判断,相对比较简单下面这个例子就是把英文的小括号写成了中文的小括号 3.2链接型错误 下面是一个链接型错误,链接型错误中,双击错误行是没有什么反应的,看行数也没什么效果,解决办法就是 "搜索" 按Ctrl +F5 打开搜索框进行搜索 下面是最后一个,也是最难的一个 3.3运行时错误运行时错误,也是最难解决的一个错误,就需要用到调试,上面讲的第一个例子就是运行时错误产生的,调试可以说是专门为了解决这个错误而产生的。今天的内容就到这里。总结:调试是重中之重,万事开头难,当你勇敢地迈出第一步,就成功了一半。 下课————— END —————

【文章原创作者:高防cdn http://www.558idc.com/gfcdn.html提供,感恩】
网友评论