前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于C/C++ 一些自己遇到的问题以及解惑

关于C/C++ 一些自己遇到的问题以及解惑

作者头像
花狗Fdog
发布2020-10-28 11:41:57
6630
发布2020-10-28 11:41:57
举报
文章被收录于专栏:花狗在Qt

有些自己遇到的,有一些是通过群友的提问应发的,问题本身的价值可能并不高,但其背后的原因才是我们应该学习的,下面我们来看看。

1.数组越界造成的死循环

       有一位朋友在群里发了该代码,并说该代码导致了死循环???

在这里插入图片描述
在这里插入图片描述

       废话少说,上工具,我们来分析分析。

在这里插入图片描述
在这里插入图片描述

       Dev下的程序并无异常???????我们来看看vs2015的表现,虽然是正常输出内容,并没有造成死循环,但是弹出了一个异常~ 。这个异常是由于我们数组越界造成的,而数组越界又是一种未决行为,编译器不会做任何处理,但是vs2015还是义务的帮我提示了异常,所以Dev和vs该用哪一个编译器,心里有数了吧?        回到问题本身,我询问了这位群友,在他的电脑上下确确实实是造成了死循环,用的是CodeBlocks,所以得出一个结论就是循环里发生数组越界在某些IDE编译运行,会导致死循环。再往下看,通过搜索,我了解到==导致死循环与编译器的内存分配有关,若内存递减分配会出现死循环,递增分配则不会,==并通过在不同IDE输出内存地址,确实验证这个结论成立.

在这里插入图片描述
在这里插入图片描述

       那么为什么会产生这样的效果呢,揭秘如下.

若是内存递减分配,对于数组和i的内存分配如下:

在这里插入图片描述
在这里插入图片描述

若是内存递增分配,对于数组和i的内存分配如下:

在这里插入图片描述
在这里插入图片描述

       现在可能就有人问了,为什么递减分配 i和iarray[2]挨着,而递增i就和iarray[0]挨着,其实这个不难理解,*(iarray+1)一定比*(iarray)的地址高不是吗,对于递减分配,必须倒着来分配,对于递减这种分配模式,iarray[3]的地址就是i的地址,iarray[3]=0便是i=0,这样一来便导致了i的值又重新被赋值为0,导致了死循环,然后,注意,没有完,之所以i会跟在数组后面,是因为字节对齐,对于32位来说是4字节,对于64位来说是8字节,当数组内容不足以字节对齐,i就会分配在其旁边,或者说是后面,当数组正好有8个元素,i就不会跟在数组后面,也就不会造成死循环,所以造成死循环一是编译器分配内存方式,二是字节对齐的问题.        经测试,博主所使用的dev和vs2015,以及一些编译器会在数组和i的地址之间,用一小块内存,用来避免两者,从而一定程度上解决死循环问题,但当越界过大,还是会造成死循环.所以在使用对内存的操作上,应格外小心…

如何查看内存?如果是C,我们可以用%p来输出变量地址,若是C++,我们可以用static_cast<void *>(&a)来输出变量地址,大家若是使用vs,教大家一个小技巧,在调试模式中(F5)下依次单击调试,窗口,即使窗口,打开这个窗口后,我们可以用&变量名来获取地址

在这里插入图片描述
在这里插入图片描述

2.int main(int argc,char* argv[])里面的参数有什么作用?

       首先可以告诉大家的是对于单纯的C语言,main里面的参数对于我们学习C来说,并不重要,标准形式有两种int main(int argc,char* argv[])和int main(void),在实际的学习使用中,我们使用int main(void)这种形式就可以了,当然,你要是感觉酷一点可以用int main(int argc,char* argv[]),如果你还想知道int argc,char* argv[]这种参数的作用,那么请往下看。        由于我们的main函数不被其他函数调用(注意:不是不可调用,是一般情况下不调用,如果你想挨骂的话…),所以就不能像其他函数一样,在程序运行中获取参数数据,那为什么还要有这个参数呢,实际上,这个参数是程序运行时,利用操作系统传进来的,argc代表着指针数组的元素个数,argv[0]是程序所在计算机的完整路径,例如: C:\Users\fdog\Desktop\hello.exe。而argv[0]之后的元素就是我们要利用操作系统传给的字符串类型的数据。        如果大家还是体会不到这个参数的作用,我可以举几个例子:

       1.大多数人应该都写过XXXX管理系统,有管理,就有数据,有数据就需要我们保存,我们可以用一个文本来保存用户输入数据,但是这个文本应该保存在什么地方呢?总不能在代码中固定一个路径吧,大家计算机名字都不一样,这样肯定行不通,于是我们在代码中开始写到cout<<“请输入数据保存的路径”; 然后开始读取用户输入的路径,那么有没有进一步提升用户体验的写法?这时我们就可以用到main的参数了,利用argv[0]获取该程序的路径,并通过算法解析,即可得到用户把exe放在哪里,那么我们在exe所在的路径下保存数据文本即可,这样就会提示用户体验。

       2.当你编写的程序需要根据提供的数据执行不同从操作,但是每次执行所需要的数据又未知,这个时候我们就可以用到main的参数,我们可以写一个脚本程序,然后让程序读取脚本中提供的参数,这样就会事半功倍。

其实相当于是调用了exe,exe里面的函数利用参数工作,而exe也同样可以利用参数工作,那么如何输入参数呢,告诉大家几张方法:

1.直接在命令行输入 start 路径 参数1 参数2 参数3

2.我们将编译好的程序,快捷方式放到桌面,右键选择属性,在其目标的文本框 exe文件后面 加入参数

在这里插入图片描述
在这里插入图片描述

3.最后一个也就是直接在我们的IDE进行设置,例如我所使用的vs2015,右键项目->属性,在调试页面可以看到一个命令参数。

在这里插入图片描述
在这里插入图片描述

好了,main的参数就说到这里,悄悄告诉大家,其实main还有第三个参数:char *envp[],如果大家有兴趣,可自行研究研究。

3.程序代码区、文字常量区、静态区(全局区)、堆区、栈区

       为什么说这个,原因在图中:

在这里插入图片描述
在这里插入图片描述

       群里在讨论链表,一位名叫C语言信赖代考的网友讲了一句清除链表只需要释放头节点就行了,不用一个一个删,我看到了,于是好意提醒了一句,结果这位网友告诉我头节点后面连着所有节点,只需要释放头节点就行了,一看此现状,我就没再与他讲理了。刚奇葩的是这个哥们私聊我说有单子给他,这我敢给啊,再顺便讲一句,不要随随便便叫人代考啊。。。。我们进入正题。        这位网友之所以会怎么说,应该是没有理解malloc/new,也就是malloc的内存申请在哪,就是栈区和堆区问题,但是因为程序代码区、文字常量区、静态区(全局区)、堆区、栈区这些东西常出现在一起,索性也就放在一起说了。        我查找了大量的有关博文,大多数有关博文都有怎么一张图,如果说以前,我可能会同意,但是现在我对图中栈区的向下增长有一些疑惑,就拿我们刚开始数组死循环的内存分配来说,内存两种分配模式,递增,递减,所以我觉得这个图还有待考证。

在这里插入图片描述
在这里插入图片描述

  • 程序代码区: 无疑问,存放程序代码的地方,不过这这里的代码被处理成二进制进行保存。

  • 文字常量区: 存放常量(程序在运行的期间不能够被改变的量)。

  • 静态区(全局区): 又分为        未初始化的全局变量和静态变量。        已初始化的全局变量和静态变量。 静态变量和全局变量的存储区域是一起的,一旦静态区的内存被分配, 静态区的内存直到程序全部结束之后才会被释放。

  • 堆区: 调用malloc()函数来主动申请的,需使用free()函数来释放内存,或者是C++中对应的new()函数,若申请了堆区内存,之后忘记释放内存,很容易造成内存泄漏。

  • 栈区(堆栈区): 存放函数内的局部变量,形参和函数返回值。栈区之中的数据的作用范围过了之后,系统就会回收自动管理栈区的内存(分配内存 , 回收内存),不需要开发人员来手动管理。栈区就像是一家客栈,里面有很多房间,客人来了之后自动分配房间,房间里的客人可以变动,是一种动态的数据变动。

举个例子:
举个例子:

同时我们可以右键项目,链接器,系统,可以看到堆保留大小和堆栈保留大小。

在这里插入图片描述
在这里插入图片描述

然后再说一下malloc对应free,new对应delete,new,delete涉及构造函数和析构函数,不可混用,若是C++,应使用new。

4.函数指针 指针函数 指针数组 数组指针 傻傻分不清

int fun();                     函数 int * fun();                   指针(样式的)函数 本质是一个函数,只不过返回的类型是指针

int(*fun)();                   函数(样式的)指针 本质上是一个指针 fun =& fun_2               获取函数地址,fun_2 是一个函数名, 调用的话 使用(*fun)() 和fun() 效果是一样的

char * p[];              指针(样式的)数组 本质是数组 ,将指针进行集合,元素为指针

int (*p)[];              数组(样式的)指针 本质是指针

上面出现的括号都是必要的,不可省略,说其是一种格式也不为过,指针XX和XX指针分不清主次,可以像我一样在两者之间加上(样式的),应该会方便理解。

5.return continue break return 0 exit

break:跳出所在的当前整个循环,到外层代码继续执行,break不仅可以结束其所在的循环,还可结束其外层循环,但一次只能结束一种循环。

continue:跳出本次循环,从下一个迭代继续运行循环,内层循环执行完毕,外层代码继续运行,continue结束的是本次循环,将接着开始下一次循环。

其实这两个没什么说的,return 和 exit可能在书中不常见。

return:直接返回函数,所有该函数体内的代码(包括循环体)都不会再执行,同时结束其所在的循环和其外层循环。当自定义函数中无返回值时,可以使用该写法。相当于使用了break。

return 0; 当函数有返回值时,使用该写法。

exit(1); 程序/进程立即结束(正常退出) exit(0); 程序/进程立即结束(异常退出)

6.最大值加1等于最小值?(一图看懂)

我们可以把变量的取值范围当作是汽车的里程表,一来为了好理解,二来确实是这样的,拿char来说:

在这里插入图片描述
在这里插入图片描述

7.精度问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这位网友的问题很有意思,这个案例也是很好的图示了下面我要说的话,这是众多初学者的一个理解错误,每一本语言书都会告诉你单精度类型有效范围是7位,双精度类型有效范围15位,这就给大家造成一种错觉,认为只能存15位,其实是错了,它所指的15位指的是精度问题,就是图中的样子,它可以存储30位甚至更多,但是它的精度只有前15位,就是超过了15位,一起数字都化作了0。之所以可以保存到30多位,和浮点数的存储有关,浮点数是用科学记数法存储的,有关浮点数的定义,这个就涉及到计算机组成原理了,还是比较难的,大家有兴趣可以搜索IEEE754浮点数的标准,里面有关于浮点数的存储过程。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/08/03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.数组越界造成的死循环
  • 2.int main(int argc,char* argv[])里面的参数有什么作用?
  • 3.程序代码区、文字常量区、静态区(全局区)、堆区、栈区
  • 4.函数指针 指针函数 指针数组 数组指针 傻傻分不清
  • 5.return continue break return 0 exit
  • 6.最大值加1等于最小值?(一图看懂)
  • 7.精度问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档