当前位置: 首页 > 科技观察

C语言为什么不检查数组下标

时间:2023-03-12 03:17:33 科技观察

介绍最近在查一个bug,最后发现是数组越界。该数组只有30个字节,但代码用35个数据填充了该数组。这个bug还是偶尔会出现,找起来确实是白费力气。我突然想到:C语言为什么不检查数组下标???首先,让我们有一个演示来验证它。#include#includeintmain(){intdata[5]={0};for(inti=0;i<8;++i){printf("%d",data[i]);}printf("\n");return0;}结果表明C语言确实不检查数组的下标。不仅没有报错,而且运行正常。想到这里让我陷入了思考,为什么C语言不检查下标呢?想起来就这么简单,data数据组只有5个数据,编译器就知道了。为什么编译器知道何时访问第8个数据?没有错误?想到之前文章《??指针与数组??》中的如下示例代码:voidmain(){intdata[4]={0,1,2,3};诠释*p;p=数据+2;printf("p[-1]是%d\n",p[-1]);printf("*(p-1)is%d\n",*(p-1));}运行结果如下:不但可以编译通过,而且正确的输出结果是1。这说明C下标引用和间接访问表达式是相同的。这让我恍然大悟,数组的这些特性,比如数组名本质上是一个常量指针(不懂的可以看之前的推文《??指针与数组??》)C语言很难检查下标的有效性。如果C语言检查数组是否越界,因为当数组出现在表达式中时,它会立即被解释为指针。另外,使用其他指针变量也可以指向数组的任意元素,这个指针可以随意加减。引用数组元素时,虽然可以写成a[i],但它只是*(a+i)的表达式。C语言本身的语法无法检查,只能通过编译器来检查。然后编译器会添加额外的代码来检测数组是否越界,而C的下标检查涉及的开销比你最初想象的要多。编译器必须在程序中插入指令来验证下标结果引用的元素和指针表达式指向的元素属于同一个数组,这可能只是一个小函数,生成程序的数组校验占用大量的代码空间,这必然会影响程序的运行效率。这也让我明白了一件事:数组的标识(也就是数组名),它只包含了不包含数组长度的信息,它只是地址信息,也就是上面说的数组名本质上是一个常量指针。看完这篇,请好好想想。C语言有提供数组长度的底层函数吗???答案是不。一般我们获取一个数组的长度,我们可以得到这个数组占用的内存大小,然后除以单个元素的内存大小来计算数组长度。inta[8];printf("%d",sizeof(a)/sizeof(a[0]));为什么不修复“漏洞”既然发现了上面的问题,为什么不修复那些C语言高手修复这个“漏洞”呢?其他编程语言会吸取“教训”吗?学过JAVA的同学可以看看下面的代码:int[][]array={{1,2,3},{1,4}};System.out.println(数组[1][2]);这也是一个数组越界访问的例子,但是JAVA控制台会打印如下信息:Exceptioninthread"main"java.lang.ArrayIndexOutOfBoundsException:2atdemo.Array.main(Array.java:31)。它会很明确的告诉你数组下标越界了。是的,支持高级语言JAVA。然后说说C语言的设计目标:提供一种易于编译、处理低级内存、只生成少量机器码、无需任何运行环境支持即可运行的编程语言。如果C语言加入类似下标的检查来实现一个简单的数组数据写入,需要大量的指令来检查下标是否正确,那么是否还符合C语言的设计目标呢?如果C语言有大量这样的设计,操作系统内核还用C语言写吗?微控制器等实时系统还会使用C语言吗?所以C语言给了程序员更多的空间,C语言执行效率高,可以直接访问硬件,移植性非常好,所以世界上大部分的操作系统内核都是用C语言编写的。那么问题来了,JAVA查了数组下标,难道C语言一点改进都没有吗?其实不然,微软在这方面也做出了贡献。在早期的CRT函数中,没有对字符串指针或数组进行越界检查,需要程序员保证有足够的空间,所以在VS2005之后就有了微软提供的安全CRT函数版本。(CRT功能不是本文重点,不懂的同学请自行百度编程)。总结为什么C语言不检查数组下标???答案只有一个字:快。