二维数组指针
int a[3][4] = { {0,1,2,3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
//0 1 2 3
//4 5 6 7
//8 9 10 11
定义一个指向a
的指针变量p
:
int (*p)[4] = a;
上面的*p
表明的是指向一个数组,指向的数组为int[4]
。
如何使用指针p
来访问二维数组中的每个元素,按照上面的定义:
p
指向数组a的开头,也即第0行,p+1
前进一行,指向第1行;*(p+1)
表示取地址上的数组,也就是整个第1行的数组,注意是一行数据,是多个数组,不是第1行中的第0个元素;
#include <stdio.h>
int main()
{
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a;
printf("%d\n", sizeof(*(p+1)));
return 0;
}
*(p+1)+1
表示第1行第1个元素的地址,*(p+1)
单独使用时表示的是第1行数据,放在表达式中会被转换为第1行数据的首地址,也就是第1行第0个元素的地址,因为使用整行数组没有实际的含义,编辑器遇到这种情况都会转换为指向该行第0个元素的指针,就像一维数组的名字,在定义时或者和sizeof
,&
一起使用时才会表示整个数组,出现在表达式中就会被转换伪指向数组第0个元素的指针。*(*(p+1) + 1)
表示第1行第1个元素的值,增加一个*
表示取地址上的数据。
a + i == p + i;
a[i] == p[i] == *(a + i) == *(p + i);
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j);
使用指针遍历二维数组
#include <stdio.h>
int main(){
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0; i<3; i++){
for(j=0; j<4; j++) printf("%2d ",*(*(p+i)+j));
printf("\n");
}
return 0;
}
数组越界问题
对于数组越界问题,不同编译器有不同的处理方案,C/C++标准仅仅对界内访问做了明确的规定,而访问界外数据时却没有说怎么处理:
- 不检查下标是否越界可以有效提高程序运行的效率,因为如果设置了检查是否越界,那么必须在生成的目标代码中加入额外的代码用于程序运行时检测下标是否越界,这会导致程序的运行速度下降,为了程序的运行效率,C/C++不检查下标是否越界;
- 如果数组下标越界,那么会自动接着那块内存往后写,以前说不允许数组下标越界,并不是因为界外没有存储空间,而是因为界外的内容是未知的,也就是说如果界外的空间暂时没有被利用,那么我们可以占用那块内存,但是如果之前界外的内存已经存放了东西,那么越界访问就会覆盖那块内存,则有可能导致错误的结果。
访问越界指针就是访问非法内存地址,
当一个进程需要某块内存时,如果这块内存地址没有被分配实际的物理内存,那么就会产生缺页异常,这个异常会触发操作系统的缺页异常处理,开始为这段内存分配实际的物理内存,如果当前物理内存够用,分配成功后返回到程序产生缺页异常的那步继续执行,如果物理内存不够用,则考虑内存的换出操作,根据算法,将一部分内存换出以腾出空闲的物理内存分给当前这个产生缺页异常的程序,让程序继续执行。如果既没有空闲物理内存,也没有内存可换出,那就只好返回ENOMEM错误,一直传回用户进程,让用户进程来处理。
一个进程要想使用内存,必须先申请分配内存给自己,不管是栈空间还是堆空间等,当一个程序访问了一段还没有合法分给它的内存地址时,就叫做非法地址访问,发生段错误,引起SIGSEGV信号。