当前位置:编程学习 > C/C++ >>

从内存角度理解指针和数组(一)

对C中的指针和数组的一点个人见解,希望各位斧正一下。
 
1,指针和数组的内存布局
同样环境下(编译和运行环境),不管定义什么类型的指针,指针变量所占用的内存大小始终是相等的。即:
 
[cpp]  
int main(void){  
    int *p0 = NULL;  
    long int *p1 = NULL;  
    printf("%d %d\n",sizeof(p0),sizeof(p1));  
    return 0;  
}  
[plain]  
simgrp100:/home/gni-170 >./test  
8 8  
如运行结果所示,在我的机器上,指针是占8个字节的。
 
不管数组是多少维度的,它在内存中的布局通常是一块连续的区域,数组名就是这块连续区域的首地址。在我的系统里long int 类型和指针类型所占的字节数是一样的,为了简要说明问题,故使用long int作为数组元素类型。
[cpp]  
long int a[3] = {1,2,3};  
long int b[2][3] = {1,2,3,4,5,6};  
数组a和b的内存布局就会类似如下(8字节对齐),
 
。黑色表示变量,变量可以理解为一块内存的别名,它实际上不占空间,仅仅为了程序的可读性和编译器的使用。红色表示实际的内存地址,淡蓝色表示内存中所存储的值(不管是不是地址值,对内存来说都一样,都是存的数字,所以就统一标为淡蓝色,值的类型只是对编译器有用,谁叫C 是强类型语言呢---变量在编译阶段就必须确定所占用的空间):
 
  p
0x00002000 0x00000000
  a
0x00001000 0x0000A000 0x0000A000 1
  0x0000A008 2
  0x0000A010 3
  0x0000A018 4
  b
0x00001008 0x0000B000 0x0000B000 1
  0x0000B008 2
  0x0000B010 3
  0x0000B018 4
  0x0000B020 5
  0x0000B028 6 p 是一个指针变量,先不管是什么类型,几级指针,它就在那里,总是占8个字节(我的环境)。
2,用指针操作数组
从上面可知,指针变量和普通变量一样,它也是一块内存的别名。只不过这块内存的大小是根据环境固定的,不像普通的char, int , long , float变量那样,类型不同,内存块大小也不同。在接下来,得始终记住,不管何种类型以及何种维度,数组都是一块连续的内存区。
 
既然指针和数组别名的值都是内存的地址值(对编译器说的),那就用指针来操作数组吧,只要指针的最小偏移操作(自加 ++)等于数组元素的大小,那理论上就不会出错。
 
2.1,使用一级指针操作二维数组
[cpp] 
void func1(void){  
    long int i;  
    long int *p;  
    long int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};  
    p = (long int *)b; //别管类型转换(long int *), 那是给编译器检查用的,我们只看真正的逻辑部分,即p = b,将b的值赋给p。  
    for(i=0;i<12;i++){  
        printf("%d ",*p++);  
    }  
    printf("\n");  
}  
int main(void){  
    printf("invoke func1:\n");  
    func1();  
    return 0;  
}  
[cpp]  
<pre name="code" class="cpp">invoke func1:  
1 2 3 4 5 6 7 8 9 10 11 12 </pre>  
发现没有,用一级指针照样也能完整地读取二维数组,三维数组也有一样的效果。现分析一下内存的变化
当执行:
 
[cpp]  
p = (long int *)b; //别管类型转换(long int *), 那是给编译器检查用的,我们只看真正的逻辑部分,即p = b,将b的值赋给p。  
内存变化如下,可见p与b的值确实相等了。
  b
0x00001000 0x0000B000 0x0000B000 1
  0x0000B008 2
  0x0000B010 3
  0x0000B018 4
  p 0x0000B020 5
0x00002000 0x0000B000 0x0000B028 6
   执行:
[cpp]  
*p++; //分成两步 *p; p++;  
*p 永远表示:将p的值作为内存地址后,读取存在该内存中的值。但是p只是一块内存的首地址,并不知道要读取的值占了多少空间啊,这就要归功于(long int *)了,编译器看到这句话后去掉一个*后就可以得知:以该地址起,存的是一种long int的数据。那么就从以p值作为首地址一次性读取sizeof(long int)个字节。
[cpp] 
p++ // 等效p = 0x0000B000+1*sizeof(long int)  
如果p是long int **类型的呢,那么p++后是改如何变化呢?接下来看看使用二级指针操作二维数组。
 
2.2,用二级指针操作二维数组
[cpp] 
void func2(void){  
    long int i;  
    long int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};  
    long int **p;  
    p = (long int **)b;  
    for(i=0;i<12;i++){  
        printf("%d ",*p++);  
    }  
    printf("\n");  
}  
[cpp] 
<pre name="code" class="cpp">int main(void){  
    printf("invoke func2:\n");  
    func2();  
    return 0;  
}</pre>  
[cpp]  
invoke func2:  
1 2 3 4 5 6 7 8 9 10 11 12   
 
同一级指针一样,二级指针也能操作二维数组,而且它们的内存布局也是一样的。
当执行:
 
[cpp]  
p = (long int **)b; //别管前面的类型转换(long int **), 那是给编译器检查用的,我们只看真正的逻辑部分,即p = b,将b的值赋给p。  
内存变化如下,可见p与a的值确实相等了。
  b
0x00001000 0x0000B000 0x0000B000 1
  0x0000B008 2
  0x0000B010 3
  0x0000B018 4
  p 0x0000B020 5
0x00002000 0x0000B000 0x0000B028 6
 
执行:
[cpp] 
*p++; //分成两步 *p; p++;  
[cpp]  
<pre name="code" class="cpp" style="background-color:rgb(255,255,255)"><pre name="code" class="cpp">p++ // 等效p = 0x0000B000+1*sizeof(long int *)</pre></pre>  
*p 永远表示:将p的值作为内存地址后,读取存在该内存中的值。但是p只是一块内存的首地址,并不知道要读取的值占了多少空间啊,这就要归功于(long int **)了,编译器看到这句话后去掉一个*后就可以得知:以该地址起,存的是一种long int *的数据。那么就从以p值作为首地址一次性读取sizeof(long int *)个字节。
为什么二级指针还是能顺利读取数组的值呢,因为(long int **)p, *p还是表示的是(long int *)类型的指针并不是一个long int的值啊?
 
要理解这个问题的根本在于:不管p是几级指针,*p永远表示以p所指内存地址为首地址的值。至于该值占多少字节,就需要看去掉一个*后的类型。所以不管什么类型,多少级指针,那些只是编译器编译所需的指导信息,到了程序运行期间,所有的事情在内存中就表现为读写,而这种读写行为的两个必须条件:从哪里开始读,读多少个字节。这里需注意到我的机器:sizeof(p)=  sizeof( long int ) = 8。
 
 
 
总结:
 
1,不管什么类型的变量,类型是用来给编译器确定变量需要的内存空间,变量是一块内存的别名,数组的变量是存储数组元素的一块内存的首地址;
 
2,不管用几级指针去操作几维数组都是可以的,只要指针的基本移步操作的步长等于数组元素的大小;
 
3,数组名可以赋值给指针(
补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,