C动态扩容多维数组

在C语言中创建动态的多维数组,以字符串数组为例,下面的程序是创建二维字符串数组,其实准确来说是3维字符数组哈~

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
#define INIT_SIZE 5
#define INFOQUANTITY 7
#define LEN 128

    char ***res = (char ***)malloc(sizeof(char **) * INIT_SIZE);
    for (int i = 0; i < INIT_SIZE; i++) {
        res[i] = (char **)calloc(INFOQUANTITY, sizeof(char *));
        for (int j = 0; j < INFOQUANTITY; j++)
            res[i][j] = (char *)calloc(LEN, sizeof(char));
    }
	// 赋值
    for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            strcpy(res[i][j], "world");
    }
    printf("--------打印指针数组值----------\n");
    for (int i = 0; i < INIT_SIZE; i++) {
        printf("%p\n", res[i]);
        for (int j = 0; j < INFOQUANTITY; j++)
            printf("&: %p ", &res[i][j]);
        printf("\n");
    }
	// 打印
    for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            printf("%s ", res[i][j]);
        printf("\n");
    }
	// free释放多维数组
    for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            free(res[i][j]);
        free(res[i]);
    }
    free(res);
    return 0;
}

编译:

PS D:\codeCopyTest\C> make src=.\3arr.c           
gcc .\3arr.c -W -fexec-charset=GBK  -o .\3arr.exe

运行:

PS D:\codeCopyTest\C> .\3arr
--------打印指针数组值----------
0000000000972440
&: 0000000000972440 0000000000972480
&: 0000000000972448 00000000009763C0
&: 0000000000972450 00000000009764D0
&: 0000000000972458 00000000009765E0
&: 0000000000972460 00000000009766F0
&: 0000000000972468 0000000000976800
&: 0000000000972470 0000000000976910

0000000000972590
&: 0000000000972590 0000000000976A20
&: 0000000000972598 0000000000976B30
&: 00000000009725A0 0000000000976C40
&: 00000000009725A8 0000000000976D50
&: 00000000009725B0 0000000000976E60
&: 00000000009725B8 0000000000976F70
&: 00000000009725C0 0000000000977080
...... 

00000000009787C0
&: 00000000009787C0 0000000000977D90
&: 00000000009787C8 0000000000977EA0
&: 00000000009787D0 000000000097A610
&: 00000000009787D8 0000000000979840
&: 00000000009787E0 0000000000978850
&: 00000000009787E8 0000000000979EA0
&: 00000000009787F0 0000000000979950

world world world world world world world
world world world world world world world
world world world world world world world
world world world world world world world
world world world world world world world
PS D:\codeCopyTest\C>

从打印结果可以看出来,这个数组形如:

[
  ["world","world","world","world","world","world","world"],
  ["world","world","world","world","world","world","world"],
  ...  
]

说明

  • 最里面的都是一个个的字符串"world" ,就是char* 类型;
  • 在向上一层就是一个个的字符串"world"组成的数组[“world”,“world”,“world”,…],也就是char** 类型;
  • 开头define了的常量INIT_SIZE就是向内一层字符串数组的个数,而INFOQUANTITY就是每个字符串数组里面有多少个字符串,LEN就是给每个字符串所申请的空间,也就意味着每个字符串(带上末尾’\0’)不能超过长度LEN;
  • 在向外一层就是res了,res是包含了字符串数组char** 的数组,也就是res的类型是char***,res中有INIT_SIZE个元素也就是有INIT_SIZE个字符串数组;

分析一下malloc的过程

  • 先malloc出res指针数组,数组元素类型是char**类型的指针,所以元素为char**的数组就是char***类型了,也就是char ***res = (char ***)malloc(sizeof(char **) * INIT_SIZE);
  • 有了res数组后,res数组的每个元素就是char**指针,也就是res数组包含有INIT_SIZE个字符串数组,所以循环for(ini i=0;i<INIT_SIZE;i++) 给每个字符串数组申请空间,每个字符串数组内部有INFOQUANTITY个字符串,也就是每个res[i]包含INFOQUANTITY个char*类型的指针,所以有res[i] = (char **)calloc(INFOQUANTITY, sizeof(char *));
  • 再然后给每个字符串数组内部的字符串元素申请空间,因为有INFOQUANTITY个字符串,所以循环for (int j = 0; j < INFOQUANTITY; j++);每个字符串开辟长度LEN的空间,也就是res[i][j] = (char *)calloc(LEN, sizeof(char));这里有一个细节,那就是res[i][j]的内容并不是具体的字符或者字符串,它仍然是一个指针,res[i][j]中记录的指针指向一片堆中的内存区,这些内存区中才是真正存放具体字符串数据的

这个过程可能对于指针不熟悉的小伙伴容易迷糊,其实就是对于低一阶的指针类型,其组成的数组就是高一阶的指针,比如如果我有3个int类型的指针int*,形如[int*,int*,int*],用变量来承接它,就意味着我要一个指针变量prt,它指向一个数组,数组的每个元素是int* 类型的,所以prt的类型就是int **prt,也就是int **prt = [int*,int*,int*];
而在我们的这个程序中,其内存示意图如下:
在这里插入图片描述

而我们也可以得出一个结论,这样动态的申请的数组,其本质上是一个多维的指针数组,数组中的元素其实都是指针类型,只有在落实到最内层的指针元素的时候,这些最内层元素指向的堆空间才是真正存放具体数据的地方
这一点也可以从打印的元素值上看出来

--------打印指针数组值----------
0000000000972440
&: 0000000000972440 0000000000972480
&: 0000000000972448 00000000009763C0
&: 0000000000972450 00000000009764D0
&: 0000000000972458 00000000009765E0
&: 0000000000972460 00000000009766F0
&: 0000000000972468 0000000000976800
&: 0000000000972470 0000000000976910

对res[i][j]取地址以及打印res[i][j]的内容,第一列是元素res[i][j]的地址,基本是连续的,而且我们申请的每个字符串空间是LEN=128个字节,但是res[i][j]之间相隔最多只有8个字节宽度,可以看出res的最内层元素res[i][j]分布和具体的字符串数组并不在同一片内存区;第二列是res[i][j]的内容,其内容是指针,指向的其他的堆空间才是最终字符串存放的位置;

free的过程

释放内存的过程和申请内存的过程相反;

	// free释放多维数组
    for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            free(res[i][j]);
        free(res[i]);
    }
    free(res);
  • 先从最内层的申请开始循环做释放,也就是释放最内层的申请的字符串空间,指针保存在res[i][j]中,也就是free(res[i][j]),此处注意res[i][j]是char*类型的指针;
  • 然后再循环释放res的每个元素也就是把保存了一个个字符串数组的指针的数组释放, free(res[i]);res[i]是char** 类型
  • 最后释放res,就是free(res);

数组的动态扩容

采用realloc函数对res进行扩容,realloc的函数用法如下:

    size_t beforeSize = INIT_SIZE;
    size_t extenedSize = beforeSize * 2;
    res = realloc(res, sizeof(char **) * extenedSize);
  • 这里先用变量beforeSize复制一份扩容前数组的大小;
  • 然后设置扩容后的res数组尺寸为原来的2倍,用extendSize来保存;
  • 然后realloc进行扩容,realloc会把原来的数组的内容先拷贝一份,然后选择另一片连续的内存空间复制原来的数组的内容到新的内存区, 并把后续的扩容部分空间也申请下来;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
#define INIT_SIZE 5
#define INFOQUANTITY 7
#define LEN 128

    char ***res = (char ***)malloc(sizeof(char **) * INIT_SIZE);
    for (int i = 0; i < INIT_SIZE; i++) {
        res[i] = (char **)calloc(INFOQUANTITY, sizeof(char *));
        for (int j = 0; j < INFOQUANTITY; j++) {
            res[i][j] = (char *)calloc(LEN * 2, sizeof(char));
        }
    }
    // 赋值
    for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            strcpy(res[i][j], "world");
    }

    printf("--------打印指针数组值----------\n");
    for (int i = 0; i < INIT_SIZE; i++) {
        // printf("%p\n", res[i]);
        printf("&res[%d]: %p %p\n", i, &res[i], res[i]);
        for (int j = 0; j < INFOQUANTITY; j++) {
            printf("&: %p ", &res[i][j]);
            printf("%p\n", res[i][j]);
        }

        printf("\n");
    }
    // 打印
    for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            printf("%s ", res[i][j]);
        printf("\n");
    }

    // 数组扩容
    size_t beforeSize = INIT_SIZE;
    size_t extenedSize = beforeSize * 2;
    res = realloc(res, sizeof(char **) * extenedSize);
    for (int i = beforeSize; i < extenedSize; i++) {
        res[i] = calloc(INFOQUANTITY, sizeof(char *));
        for (int j = 0; j < INFOQUANTITY; j++)
            res[i][j] = calloc(LEN, sizeof(char));
    }

    // 扩容部分赋值
    for (int i = beforeSize; i < extenedSize; i++) {
        for (int j = 0; j < INFOQUANTITY; j++) {
            strcpy(res[i][j], "hello");
        }
    }

    // 打印内容
    printf("-------数组扩容后--------\n");
    for (int i = 0; i < extenedSize; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            printf("%s ", res[i][j]);
        printf("\n");
    }

    printf("--------打印指针数组值(扩容后)----------\n");
    for (int i = 0; i < extenedSize; i++) {
        printf("&res[%d]: %p %p\n", i, &res[i], res[i]);
        for (int j = 0; j < INFOQUANTITY; j++) {
            printf("&: %p ", &res[i][j]);
            printf("%p\n", res[i][j]);
        }

        printf("\n");
    }

    // free释放多维数组

    /*for (int i = 0; i < INIT_SIZE; i++) {
        for (int j = 0; j < INFOQUANTITY; j++)
            free(res[i][j]);
        free(res[i]);
    }
    */
    for (int i = 0; i < extenedSize; i++) {
        for (int j = 0; j < INFOQUANTITY; j++){
        	free(res[i][j]);
        	res[i][j] = NULL;
        }
        free(res[i]);
        res[i] = NULL;
    }
    free(res);
    res = NULL;
    return 0;
}

编译:

PS D:\codeCopyTest\C> make src=.\3arr.c           
gcc .\3arr.c -W -fexec-charset=GBK  -o .\3arr.ex

编译过程中有告警

.\3arr.c:45:32: warning: comparison of integer expressions of different signedness: 'int' and 'size_t' {aka 'long long unsigned int'} [-Wsign-compare]
     for (int i = beforeSize; i < extenedSize; i++) {

这个忽略因为我们的变量beforeSize 和 extendSize的类型是long long unsigned int,而变量i,j是int 类型;
我们把扩容的部分的空间通过strcpy 赋值字符串"hello",原来的部分保持不变

// 扩容部分赋值
    for (int i = beforeSize; i < extenedSize; i++) {
        for (int j = 0; j < INFOQUANTITY; j++) {
            strcpy(res[i][j], "hello");
        }
    }

这次添加了对res元素res[i]的地址和内容的打印以用来追踪扩容后res数组的变化

    printf("--------打印指针数组值(扩容后)----------\n");
    for (int i = 0; i < extenedSize; i++) {
        printf("&res[%d]: %p %p\n", i, &res[i], res[i]);
        for (int j = 0; j < INFOQUANTITY; j++) {
            printf("&: %p ", &res[i][j]);
            printf("%p\n", res[i][j]);
        }

        printf("\n");
    }

运行:

PS D:\codeCopyTest\C> .\3arr

输出:

PS D:\codeCopyTest\C> .\3arr
--------打印指针数组值----------
&res[0]: 0000000000A92410 0000000000A92440
&: 0000000000A92440 0000000000A92480
&: 0000000000A92448 0000000000A963C0
&: 0000000000A92450 0000000000A964D0
&: 0000000000A92458 0000000000A965E0
&: 0000000000A92460 0000000000A966F0
&: 0000000000A92468 0000000000A96800
&: 0000000000A92470 0000000000A96910

&res[1]: 0000000000A92418 0000000000A92590
&: 0000000000A92590 0000000000A96A20
&: 0000000000A92598 0000000000A96B30
&: 0000000000A925A0 0000000000A96C40
&: 0000000000A925A8 0000000000A96D50
&: 0000000000A925B0 0000000000A96E60
&: 0000000000A925B8 0000000000A96F70
&: 0000000000A925C0 0000000000A97080
&res[2]: 0000000000A92420 0000000000A925D0
&: 0000000000A925D0 0000000000A97190
&: 0000000000A925D8 0000000000A972A0
......

&res[3]: 0000000000A92428 0000000000A92610
&: 0000000000A92610 0000000000A981D0
&: 0000000000A92618 0000000000A97400
......

&res[4]: 0000000000A92430 0000000000A987C0
&: 0000000000A987C0 0000000000A97B70
&: 0000000000A987C8 0000000000A97C80
......
world world world world world world world
world world world world world world world
world world world world world world world
world world world world world world world 
world world world world world world world
-------数组扩容后--------
world world world world world world world
world world world world world world world
world world world world world world world
world world world world world world world
world world world world world world world
hello hello hello hello hello hello hello
hello hello hello hello hello hello hello
hello hello hello hello hello hello hello
hello hello hello hello hello hello hello
hello hello hello hello hello hello hello 
--------打印指针数组值(扩容后)----------
&res[0]: 0000000000A9B820 0000000000A92440
&: 0000000000A92440 0000000000A92480
&: 0000000000A92448 0000000000A963C0
&: 0000000000A92450 0000000000A964D0
&: 0000000000A92458 0000000000A965E0
&: 0000000000A92460 0000000000A966F0
&: 0000000000A92468 0000000000A96800
&: 0000000000A92470 0000000000A96910

&res[1]: 0000000000A9B828 0000000000A92590
&: 0000000000A92590 0000000000A96A20
&: 0000000000A92598 0000000000A96B30
&: 0000000000A925A0 0000000000A96C40
&: 0000000000A925A8 0000000000A96D50
&: 0000000000A925B0 0000000000A96E60
&: 0000000000A925B8 0000000000A96F70
&: 0000000000A925C0 0000000000A97080

&res[2]: 0000000000A9B830 0000000000A925D0
&: 0000000000A925D0 0000000000A97190
&: 0000000000A925D8 0000000000A972A0
......

&res[3]: 0000000000A9B838 0000000000A92610
&: 0000000000A92610 0000000000A981D0
&: 0000000000A92618 0000000000A97400
......

&res[4]: 0000000000A9B840 0000000000A987C0
&: 0000000000A987C0 0000000000A97B70
&: 0000000000A987C8 0000000000A97C80
......

&res[5]: 0000000000A9B848 0000000000A9B880
&: 0000000000A9B880 0000000000A9B8C0
&: 0000000000A9B888 0000000000A9B950
......

&res[6]: 0000000000A9B850 0000000000A9BCB0
&: 0000000000A9BCB0 0000000000A9BCF0
&: 0000000000A9BCB8 0000000000A9BD80
......

&res[7]: 0000000000A9B858 0000000000A9C0E0
&: 0000000000A9C0E0 0000000000A9C120
&: 0000000000A9C0E8 0000000000A9C1B0
......

&res[8]: 0000000000A9B860 0000000000A9D2E0
&: 0000000000A9D2E0 0000000000A9CC20
&: 0000000000A9D2E8 0000000000A9D0A0
......

&res[9]: 0000000000A9B868 0000000000A9D320
&: 0000000000A9D320 0000000000A9C950
&: 0000000000A9D328 0000000000A9CB90
......
PS D:\codeCopyTest\C> 

可以从打印结果上看出realloc扩容后,res数组的地址发生了变化,res[0]->res[4]的地址在新的内存空间,但是res[0]->res[4]的内容没变化,其保存的指针仍然是和原来一样(第一列是地址,第二列是内容);
在这里插入图片描述
示意图分析扩容的过程:
在这里插入图片描述
可以看到,我们只对res的向内一层做了realloc,从
[char**,char**,char**,char**,char**]
扩容为:
[char**,char**,char**,char**,char**,char**,char**,char**,char**,char**]
而再向内一层我们只是对扩容新增的部分再次calloc而已(malloc也可以);

    res = realloc(res, sizeof(char **) * extenedSize);
    for (int i = beforeSize; i < extenedSize; i++) {
        res[i] = calloc(INFOQUANTITY, sizeof(char *));
        for (int j = 0; j < INFOQUANTITY; j++)
            res[i][j] = calloc(LEN, sizeof(char));
    }

也就是res数组的首层元素在内存分布上才是连续的,我们每次扩容realloc或者calloc/malloc的时候申请的空间是连续的,但是realloc/calloc/malloc之间是不连续的
这就像是一个顺序表,我们用顺序表来记录下一级的数组的地址,而顺序表中的地址对应下一级子数组的内存空间,res的元素之间,res[i]和res[i+n]指针指向的内存片区它们之间的分布不需要连续,它们之间指向的片区在内存上是随机散列分布的,但是存放指针的res[i]和res[i+1]本身的空间是一次realloc的结果是连续的,所以&res[i]和&res[i+1]是连续,这一点从没扩容之前也可以看出来,res[0]->res[4]的内容是一个指针,指针指向的内存也不是连续的,但是&res[0] -> &res[4] 取地址是连续的;

扩容后数组的释放

其实和未扩容之间区别不大
也是先从内层的res[i][j]开始循环释放,然后释放外层res[i],随后释放res

for (int i = 0; i < extenedSize; i++) {
        for (int j = 0; j < INFOQUANTITY; j++){
        	free(res[i][j]);
        	res[i][j] = NULL;
        }
        free(res[i]);
        res[i] = NULL;
    }
    free(res);
    res = NULL;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值