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

在这里插入图片描述

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

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浮点数的标准,里面有关于浮点数的存储过程。

最后来一只可爱的猫猫,创作不易,求关注,求收藏!

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


首先GDB是类unix系统下一个优秀的调试工具, 当然作为debug代码的工具, 肯定没有IDE来的直观了. 不过, 命令行自然有命令行的有点, 当你无法是使用IDE时, gdb有时会帮上很大的忙. 下面用1个例子来说明用法吧: 建立1个目录testgdb2 1. 编写c文件test.c可以见到在2
1. C语言定义1个数组的时候, 必须同时指定它的长度.例如:int a[5]={1,2,3,4,5}; //合法int b[6]; //合法int c[]; //错误 因为没有指定长度但是下面语句是正确, 它隐形定义了数组的长度, 就是赋值元素的个数int d[] ={4,5,6,7,8,9} /
C++的auto关键字在C+⬑新标准出来之前基本上处于弃用的状态,到C+⬑新标准发布之后,auto关键字被赋予了新的意义,并且功能也变得很强大,此篇文章重点介绍auto关键字的新功能、新用法,以及在C+⬔、C+⬗、C+⬠各版本中对它的使用缺陷和限制不断地修正和增强,当然auto关
const关键字是用于定义一个不该被改变的对象,它的作用是告诉编译器和其他程序员不允许修改这个对象的值或者状态。当程序员看到使用const修饰的代码时就知道不应该修改对应对象的值,而编译器则会强制实施这个约束,任何违反这个规定的代码会在编译期间报错。它可以用于任何函数或者类之后的全局或namespa
文章浏览阅读315次。之前用C语言编过链表,这几天突然想用C++编一下链表,搞了大半天才搞出来,所以就赶紧整理一下记录下来,省的万一时间长了找不到代码哈哈。一、链表代码1、Node.h文件代码#pragma onceclass Node{public: int ID; char alph; Node* next; Node(int ID,char alph); ~Node();private:..._if(current->id==id)
文章浏览阅读219次。碰到问题就要记录下来,防止遗忘吧。文章目录一、VS中的命令行参数二、内联函数和宏三、初始化和赋值一、VS中的命令行参数今天在运行代码的时候,碰都了下面的情况: // 解析命令行参数 if (pcl::console::find_argument (argc, argv, "-h") >= 0) { printUsage (argv[0]); return 0; }..._"if (pcl::console::find_argument(argc, argv, "-f") >= 0)怎么输入参数"
文章浏览阅读1.8k次,点赞11次,收藏37次。因为自己对决策树的机制非常的好奇,所以就研究了一下决策树的ID3算法,在这也做一篇笔记记录一下过程。文章目录一、什么是决策树?二、信息增益2.1信息熵2.1.1定义2.1.2演变2.2信息增益三、ID3算法实现四、小结一、什么是决策树?这个问题是我从一开始就有的疑问,什么是决策树?在看了一些资料之后,因为没有看到书上给出具体定义,所以按照我自己的理解决策树就是通过一个个“决策”而构建的一种树状结构,而且决策树的整个处理机制非常类似于我们人类在面临决策问题时的处理机制,这也可能就是其名字的由来。决_c++id3
文章浏览阅读492次。C++ 设计模式之策略模式
文章浏览阅读683次。我也算是个C++的小白,对于C++中的谓语我第一时间就想到了C#中的委托,但两者又不尽相同,所以想写一篇笔记记录一下。文章目录一、什么是谓语?二、使用谓语一、什么是谓语?谓语是一个可调用的表达式,其返回的结果可以作为条件的值,在C++中其实就是向算法传递函数。这和C#中的委托的概念其实是一样的,都是将函数作为参数进行传递。C++标准库中的谓语主要有两类:一元谓语和二元谓语,也就是有的算法只能..._谓语句 c++
文章浏览阅读225次。又看了一遍操作符的东西,感觉之前对操作符的理解还停留在很浅的认知上(仅仅会用哈哈),所以做一下笔记来加深一下印象。文章目录一、为什么会有操作符重载?二、操作符重载作用的对象一、为什么会有操作符重载?如果要回答这个问题,我们其实应该仔细想一下如果没有操作符重载会怎样呢?这其实很容易就联想到了C语言,因为他就没有操作符重载这一说。虽然C语言中没有类class这一概念,但是他有着和类及其相似的结构..._6-6 我的朋友 - c/c++ 操作符重载分数 15作者 海洋饼干叔叔单位 重庆大学实现frie
文章浏览阅读216次。因为之前碰到了很多关于C++上的问题,现在整理并记录一下。文章目录一、引用一、引用在C++中,引用就是给对象起了另一个名字,也就是“对象别名”。感觉和什么东西很相似,仔细一想不就是类型别名“typedef”吗哈哈。它其实是和原对象形成了一种绑定的一种关系,..._vc++6.0报错:returning address of local
文章浏览阅读565次。因为一直好奇预处理器的工作机制,所以就查了查书,做一下自己看完书之后的笔记。文章目录一、预处理器的作用一、预处理器的作用_c语言预处理器作用
文章浏览阅读1.8k次,点赞3次,收藏10次。最近特别查阅了一下关于C++文件的输入/输出的资料,整理了一下就写一下笔记。文章目录一、什么是流二、什么是缓冲区三、代码实现文件IO3.1 使用文件流对象读取数据3.2重定向一、什么是流当前的计算机具有很多种设备,但是无论是哪种设备都要与数据和信息进行打交道,所以这就牵扯到设备与数据之间的I/O操作。而每种设备又有着不同的特性和操作协议,由于过于复杂,所以我们一般是不会和这些通信细节打交道的..._c++ inpath
文章浏览阅读4.8k次,点赞6次,收藏29次。因为要使用到C++的动态链接库,所以就特意网上找了一下资料实现了一下。文章目录一、lib与dll文件二、创建dll文件三、dll隐式链接四、显式链接五、小结一、lib与dll文件之前我一直以为动态链接库就是指dll文件,这也是C#给我造成的一种印象,因为在C#中建立的类库文件都是dll文件,而且只要简单引用就可以了,但是C++却并不是这样的,这可能是因为C#隐藏了一些细节的缘故吧。在C++中共有两种库模式,一种是包含lib和dll两种文件,这种情况下其中的lib文件包含了函数所在的dll文件和dl_c++调用动态链接库
文章浏览阅读973次。因为遇到了一这个操作符的问题,所以记录一下出现的问题*~*。一、问题描述二、产生原因因为也是第一次出现这个问题,所以就到网上查了一些资料和书籍,现在倒也大概理解这个错误出现的原因了。有时候举个例子可能更容易理解为啥会出现这个错误,就拿一本书中的例子来说一下,如下所示:template<class T> class NamedObject { public: NamedObject(std::string& nameVal, const T objectVal) __copy_assign报错
C语言中的单向链表可以解决数组和结构体在使用时的内存连续性问题,同时还能动态地调整长度。本文介绍了单向链表的结构和基本操作,并给出了一个简单的示例代码。
文章浏览阅读2.3k次。区分'0'、"0"、0、''_0和
文章浏览阅读5.8k次,点赞4次,收藏8次。C语言函数指针详解,微剖本质_c语言指针函数
数组指针和指针数组是代码中常见的定义形式。虽然它们的语法类似,但含义完全不同。对于一维数组而言,数组名即为首元素的地址,不需要取址即可赋值给指针。而对于二维数组,数组名代表首行元素的地址,可以看作是一个指针数组,需要使用取址操作。
文章浏览阅读297次。总结刚入门的新同学C语言编程常见的低级错误