关于数组在内存中的存放形式

本文探讨了二维数组在内存中的存放形式,通过测试发现`char a[3][3]`的地址关系,深入理解了数组地址与指针的关系。尽管数组名不占用内存,但它是一个符号,用于表示数组的起始地址。编译器在处理数组寻址时,能直接转换为底层的指针运算,而人为创建的数组可能需要额外的头指针辅助寻址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事出缘由

char a[3][3];

sizeof(a);       //结果是9

9这个结果一直认为是“理所当然”,但是当自己实现一个二维数组时,最简单的方式就是:

char **test = (char**)malloc(sizeof(char*)*ROW_SIZE);

int i=0;

for(;i<ROW_SIZE;++i)

  test[i]=(char*)malloc(sizeof(char)*COL_SIZE);

其实这样获得数组大小是ROW_SIZE*COL_SIZE+ROW_SIZE*4字节没错吧。

上述内存并非连续,那连续的
这里写图片描述
可是还是得有前面的几个一维指针指向后面的一位数组,所以大小还是上面那个……

所以我的就有些惊奇,写了测试,结果打印出来的内容更加让我惊奇……char a[3][3]得到的&a==&a[0]==&a[0][0]。但是细细想来,这与sizeof(a)==9没有一点矛盾冲突。可是问题又来了,C语言在没有重载[]的情况下是如何实现的(代码中我是用c++写的)?是编译器的功劳么?

测试代码

#include <iostream>
using namespace std;

int main()
{
    char **x=(char**)malloc(sizeof(char*)*5);
    for(int i=0;i<5;++i)
        x[i]=(char*)malloc(sizeof(char)*3);
    char a[3][3];
    char b[9];

    /*
     * 无所谓的初始化
     */
    for(int i=0;i<3;++i)
        for(int j=0;j<3;++j)
            a[i][j] = '0'+i+j;

    cout<<hex<<"输出a的地址(&a):"<<endl
        <<(unsigned long)&a<<""<<endl
        <<"输出a+i的内容(应为a[i]的地址):"<<endl
        <<"a:"<<(unsigned long)a<<"\t\t"
        <<"a+1:"<<(unsigned long)(a+1)<<"\t"
        <<"a+2:"<<(unsigned long)(a+2)<<endl
        <<"输出a[i]的地址:"<<endl
        <<"&a[0]:"<<(unsigned long)&(a[0])<<'\t'
        <<"&a[1]:"<<(unsigned long)&(a[1])<<'\t'
        <<"&a[2]:"<<(unsigned long)&(a[2])<<endl
        <<"输出a[i]的内容(应为a[i][0]的地址):"<<endl
        <<"a[0]:"<<(unsigned long)a[0]<<'\t'
        <<"a[1]:"<<(unsigned long)a[1]<<'\t'
        <<"a[2]:"<<(unsigned long)a[2]<<endl
        <<"输出a[i][0]的地址:"<<endl
        <<"&a[0][0]:"<<(unsigned long)&a[0][0]<<'\t'
        <<"&a[1][1]:"<<(unsigned long)&a[1][1]<<'\t'
        <<"&a[2][2]:"<<(unsigned long)&a[2][2]<<endl;

    cout<<endl<<"所有元素(内容)的地址"<<endl;
    for(int i=0;i<3;++i){
        for(int j=0;j<3;++j)
            cout<<hex<<"&a["<<i<<"]["<<j<<"]:"<<(unsigned long)&a[i][j]<<' ';
        cout<<endl;
    }
    cout<<"Sizeof(a[3][3]):"<<sizeof(a)<<endl;

    cout<<endl<<"输出b的地址(&b):"<<endl<<(unsigned long)&b<<endl
        <<"输出b的内容(应为b[0]的地址):"<<(unsigned long)b<<endl
        <<"输出b[0]的地址:"<<(unsigned long)&b[0]<<endl;

    return 0;
}

测试结果

这里写图片描述

问题思考

其实这个问题就是数组在内存中的存放形式以及编译器是如何处理数组的初始化的。于是,我就取巧地在优快云发了贴询问,原帖内容。我决定在这里记一笔,整理一下比较好。

对于一维数组:
int a[3];

&a[i]=(int*)a+i;

对于二维数组:
int a[3][3];

&a[i]=(int(*)[5])a+i

归纳总结一下:

template <typename T, size_t N>
设有定义
T a[N];

那么对于任意int i有
a[i] == *((T*)a + i)
这是数组的寻址方式

特别内容

这里要特别强调一下,一直以来都把指针变量和数组名等价,但从&a==a这一点可以看出,数组名并不是指针变量,它不存在自己的内存空间,只是一个符号而已。见下:

#include <iostream>

using namespace std;

int main(){
    char a[3][3];
    typedef char Z[3][3];
    typedef char (*X)[3];

    Z &c = a;
    X d = a;

    cout<<hex<<(unsigned long)c<<endl
        <<(unsigned long)d<<endl
        <<(unsigned long)a<<endl
        <<(unsigned long)&c<<endl
        <<(unsigned long)&a<<endl
        <<(unsigned long)&d<<endl;

    return 0;
}

这里写图片描述
从上面的代码中可以看出来,引用的作用和数组名的作用是相同的,然而指针则是不一样的!!!!

结论

所以说当a[i][j]进行寻址时,编译器在编译时直接就将其转化为上述形式,无需其他额外地址进行辅助。然而人为地创造一个数组,并且对这一块连续区域进行操作,就必须自己设定一种方法——额外添加头指针用来寻址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值