C语言中数组和内存管理,是安全性和性能之间矛盾关系的重要部分。
我曾提到要讨论性能和安全性之间的矛盾。这个矛盾的一个重要部分就是因为C语言中数组和内存管理的本质特征导致的。
理论上,数组是一个简单的数据结构:当你需要访问其中的一个元素时,只需要给出该元素的索引位置,就能对该元素进行读或者写操作。这句话中也隐含了
一个问题,那就是你需要访问一个元素时,都需要提供一个索引位置。使用索引位置来找元素通常是一个代价很高的计算,尤其是当元素的大小不是2的整数次幂
时: 在诸如表达式++a[i], 在地址递增的过程中,其计算地址的代价可以轻松超过5倍于a[i]的地址的代价。
在至少50年的时间里,编译器开发人员一直在努力让访问数组元素变得更快。其中很大一部分的工作都围绕想下面这种循环进行:
for (size_t i = 0; i < n; ++i)
c[i] = a[i] + b[i];
这段代码在每次循环迭代中,都需要通过计算将三个索引地址转换成对应的内存位置中,这种计算也带入了一些开销。 许多编译器都通过将循环重写为如下代码的方式来实现高效计算。在这段代码中,我们假设Pointer类型是可以指向a,b,c三个数组中某个元素的指针。
Pointer ap = &a[0], bp = &b[0], cp = &c[0], aend = ap + n;
while (ap < aend) {
*cp++ = *ap++ + *bp++;
}
这段转换后的代码将三个数组索引计算操作转换成了三个地址加操作,这样加速显着。不过,这个简单的转换操作看起来容易,做起来却很复杂,
因为编译器需要能够确认在这个for循环体中没有对i值本身的修改。 上面的例子可以很直观的看到i不会被改变,不过在实际的代码中,往往要困难很多。
C语言与在它之前的编程语言相比,一个非常重要的不同就是C能提供给程序员一些直接优化代码的机会,而不是简单的依赖编译器去做优化。C语言通过将数组和指针的概念统一化,使得程序员可以自己做大部分的数组索引计算,而在C语言之前,这些工作只能通过编译器去做。
用手动计算索引取代自动计算是一种进步,这个听起来有点怪怪的。但是在实际编程中,可能很多程序员都宁愿手工优化代码,而不是依赖编译器自动优化,因为无法确定编译器到底对代码做了什么。这也可能是吸引C程序员使用指针而不是索引来访问数组元素的原因之一。
除了在很多情况下会更快外,指针相比数组还有另外一个很大的优势:可以只用指向数组中特定元素的一个指针来识别数组中的元素。比如,假设我们想写一
个函数来对数组中某个区域内的元素做操作。如果没有指针,我们需要三个参数来确定这个区域:数组名称,区域开始索引,区域结束索引。而如果使用指针的话,
只要两个参数就足够了。
此外,不管是动态分配的内存,还是其他内存地址,都可以统一使用指针。例如,在malloc库函数返回一个指向动态内存的指针后,我们可以用这个指
针创建任何我们需要的数据结构。一旦我们在这块动态分配的内存中创建了这些数据结构之后,我们就能使用指向这些数据结构某个部分的指针来让其他函数可以直
接访问这一部分数据。 相应的,这些函数也无需知道他们将使用的内存到底是什么性质的。
使用指针是方便了很多,但是也要付出代价的。相比于使用索引变量引用数组元素的表示形式,使用指针的表示形式将会引入三种潜在危害。
第一,因为指向数组元素的指针和数组本身是完全独立的。
因此,在数组不存在或者内存释放之后,指针仍然有可能存在。比如,我们将数组元素的地址
&a[0]保存到指针ap中,我们同时也引入了在a不存在的时候,使用*ap的风险。这种风险在完全使用数组加索引的形式中是不存在的,因为一旦
数组a消失了,我们也无法引用他的元素。
第二,指针运算的可行性。
如果我们使用一对指针指向一个数组区间的两端,那么我们就一定能找到其中间元素的位置,因为可以直接使用数学运算得到。但
是这种指针的数学运算也同时引入了很多制造不可用地址的可能性,而且这种通过数学运算得到的不可用地址, 相比简单的一些针对整数的数学运算来说,
更难检测到。
最后,使用指针来表示范围,不仅仅需要指针本身存在且可用,还需要指针指向的内存是可用的内存
。上面代码中的aend变量就是一个典型例子。我们创
建了一个aend变量,并用它指向循环的上界。但是如果我们想试图对*aend取值,结果将是未定义的。这类指针被称为off-the-end指针。这类
指针的存在,也让验证C语言是否存在越界错误变得非常困难。
Ⅱ C语言的越界问题
C语言是不检查下表越界的,因此越界出来结果太正常了,你和书上一样是因为你从-1到6到赋值了,编译器的实现就是安数组首位坐标+-指针而已。如果你输出-3呢,应该就是随机了。
为什么arr【5】和arr【6】应该一样?
给你看看我用VC++6.0在Win32位上运行结果:
Ⅲ C语言指针问题
首先,你提到的所有p应该是一个类型,也就是
int *p
即,整型指针类型,该类型与一维整型数组等效,即int []
然后一个个来
第一个,只有用在定义的时候才是对的,
int *p=a;意思是定义int*类型的p,初始化为a。等效于int *p;p=a;
第二个,先p+5,然后取值,等效于p[5],在p=a下,也就是a[5],这里其实是越界的,知道是这个意思就好
第三个,先取值,在值加2,p=a时,*p为a[0],这个就是a[0]+2
第四个和第五个类似,如果p=a,那么四五完全等同,都是a[2]的地址
最后一个,对a[5]先取地址再取值,最终还是a[5],这个和第二个是等价的,当然,也一样是越界
Ⅳ C语言指针越界问题,大神指教!!!
*p是指向int类型的指针,a为数组首地址,这些理解都是对的,
但是,p没有被初始化啊,你能直接是用没有被初始化的变量嘛,那肯定是不行的啊.
所以正确的做法先给p绑定堆内存就可以这样写了.
#include<iostream>
usingnamespacestd;
intmain()
{
int**p=newint*;
intArray[10]={1,2,3,4,5,6};
*p=Array;
cout<<(*p)[1]<<endl;
system("pause");
return0;
}
编译完全通过.这里不贴出来了.点我头像进群.
Ⅳ c语言越界是什么意思
书中所说的越界是指如果z很大,大到int类型存不下(超过2^32-1), 就会发生越界溢出,(即内存不够存放z)此时z成为负数,导致计算出错。而模的乘积又等于乘积的模,所以每次乘法的结果都会小于1000,既满足了结果正确又不会发生int存不下
Ⅵ 什么是指针越界,有何后果
)应该特别注意程序的书写格式,让它的形式反映出其内在的意义结构。 程序是最复杂的东西(虽然你开始写的程序很简单,但它们会逐渐变得复杂起来),是需要用智力去把握的智力产品。良好的格式能使程序结构一目了然,帮助你和别人理解它,帮助你的思维,也帮助你发现程序中不正常的地方,使程序中的错误更容易被发现。 人们常用的格式形式是:逻辑上属于同一个层次的互相对齐;逻辑上属于内部层次的推到下一个对齐位置。请参考本课程的教科书或《C程序设计语言》(The C Programming Language,Brian W. Kernighan & Dennis M. Rirchie,清华大学出版社,大学计算机教育丛书(影印版,英文),1996。) 利用集成开发环境(IDE)或者其他程序编辑器的功能,可以很方便地维护好程序的良好格式。请注意下面这几个键,在写程序中应该经常用到它们:Enter键(换一行),Tab键(将输入光标移到下一个对齐位置——进入新的一个层次),Backspace键(回到前一个对齐位置——退到外面的一个层次)。 -------------------------------------------------------------------------------- 2)用最规范的、最清晰的、最容易理解的方式写程序。注意人们在用C语言写程序的习惯写法,例如教科书中解决类似问题时所使用的写法,《C程序设计语言》一书中有许多极好的程序实例。在这里有一个关于程序模式的相关网页,里面也列出了一些常用的模式。 C语言是一个非常灵活的语言,你可能在这里用许多非常隐晦的方式写程序,但这样写出的程序只能是作为一种玩意儿,就像谜语或者智力游戏。这些东西可以用于消磨时间,但通常与实际无缘。在我们的C语言讨论组里提到过这种东西。 -------------------------------------------------------------------------------- 3)在编程中,应仔细研究编译程序给出的错误信息和警告信息,弄清楚每条信息的确切根源并予以解决。特别是,不要忽略那些警告信息,许多警告信息源自隐含的严重错误。我们有许多办法去欺骗编译程序,使它不能发现我们程序中的错误,但这样做最终受到伤害的只能是自己。 -------------------------------------------------------------------------------- C语言的运算符很多,优先级定义也不尽合理,很难完全记清楚,因此要特别注意。需要时查一查(不要怕麻烦,相关网页有运算符表),或者直接按照自己的需要加上几个括号。 -------------------------------------------------------------------------------- scanf("%d %d", i++, a[i]); m = n * n++; -------------------------------------------------------------------------------- 6)总保证一个函数的定义点和它的所有使用点都能看到同一个完整的函数原型说明。 -------------------------------------------------------------------------------- 7)总注意检查数组的界限和字符串(也以数组的方式存放)的结束。C语言内部根本不检查数组下标表达式的取值是否在合法范围内,也不检查指向数组元素的指针是不是移出了数组的合法区域。写程序的人需要自己保证对数组使用的合法性。越界访问可能造成灾难性的后果。 例:在写处理数组的函数时一般应该有一个范围参数;处理字符串时总检查是否遇到空字符'\0'。
Ⅶ C语言指针越界问题。
strchr函数就在在字符串中查找指定字符,返回其地址。
这个函数本身就已经把结束符号0,计入遍历查找范围,所以没有把0作为结束位置,所以无所谓。
但字符串最好最后多留一位放0,不然代码长了,传参多了,就弄不清了。
Ⅷ C语言指针的问题:请问下C语言前辈们,是不是访问越界造成的calloc创建失败或者是指针访问违规
#define FILE_PATH_IN "C:\\DllTool.dll"
#define NEW_FILE_PATH_OUT "C:\\DllTool_new.dll"
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS32 pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
FILE *pFile = NULL;
LPVOID pFileBuffer = NULL;
LPVOID pImageBuffer = NULL;
LPVOID pNewBuffer = NULL;
FILE *pNewFile = NULL;
size_t dwFileSize = 0;
size_t dwSize = 0;
DWORD i = 0;
//读取文件
pFile = fopen(FILE_PATH_IN, "rb");
fseek(pFile, 0L, SEEK_END);
dwFileSize = ftell(pFile);
fseek(pFile, 0L, SEEK_SET);
pFileBuffer = calloc(dwFileSize, sizeof(char));
if (pFileBuffer == NULL)
{
printf("申请pFileBuffer空间失败!\n");
return 0;
}
dwSize = fread(pFileBuffer, sizeof(char), dwFileSize, pFile);
//拉伸
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
pNTHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + sizeof(pNTHeader->Signature));
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
pImageBuffer = calloc(pOptionHeader->SizeOfImage, sizeof(char));
if (pImageBuffer == NULL)
{
printf("申请pImageBuffer空间失败!\n");
return 0;
}
memcpy(pImageBuffer, pFileBuffer, pOptionHeader->SizeOfHeaders);
for (i=0; i<pPEHeader->NumberOfSections; i++)
{
PIMAGE_SECTION_HEADER pTempSec = (PIMAGE_SECTION_HEADER)((DWORD)pSectionHeader + i * IMAGE_SIZEOF_SECTION_HEADER);
memcpy((void*)((DWORD)pImageBuffer + pTempSec->VirtualAddress), \
(void*)((DWORD)pFileBuffer + pTempSec->PointerToRawData), pTempSec->SizeOfRawData);
}
//就是这里,下面这个pNewBuffer空间申请失败,指针为NULL。我调试到上面这个循环出了问题,但不知道怎么解决,原来就一句:pSectionHeader++ 就解决的事件,但都不行,请高手们帮帮忙!!!谢谢了!!!
//压缩
pNewBuffer = calloc(dwSize, sizeof(char));
if (pNewBuffer == NULL)
{
printf("申请pNewBuffer空间失败!\n");
return 0;
}
memcpy(pNewBuffer, pImageBuffer, pOptionHeader->SizeOfHeaders);
//就是上面,我已经标注了,本人C语言算不上精通,请高手们不吝赐教,帮帮忙!!!谢谢了!!!收起
已经找到问题所在了,我换成其它DLL也没问题,是我写的这个DLL文件导出函数名使用了def自定名,但我在声明中又使用了extern "C",生成的DLL文件本身存在一点问题,所以在复制内容时,出问题了,看到此问题的大侠们,谢谢了,给大家造成困扰了,这个指针没有问题,循环内虽然pSection++最后指向最后一个节区数据,但是i已经使其跳出循环,没有任何问题!给大家造成困扰,实在对不起,有时代码看不出问题,其它要从其它方面查询,换个其它同样的文件应该测试一下,唉!