彻底理解 C 语言的数组在内存中到底是怎么存放的!

图片

在C语言中,数组是经常被用到的重要数据类型,但在实际使用时,往往有很多工程师会出现各种各样的问题,如内存越界、错误的访问、初始化不当等。这其中有很大一个原因是没有彻底理解数组的存储机制,出现了一些非法地址或者非预期元素的访问和引用。因此,今天就来详细讲一下在C语言中,数组到底是怎么存储的。

首先我们来看一个一维数组:

int array[10] = {0};

这是最简单的数组,其内存结构也是最容易理解的,编译器会在内存中划出一段连续的空间用于存储这个数组的元素,并且对于 int 类型来说,每个元素占用的大小为 4 字节。因此,其内存排列如下:

图片

我们可以用下面一段代码验证一下:

#include <stdio.h>

int main() {
    int array[10] = {0};

    for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
        printf("array[%d] - %p - %ld\r\n", i, &array[i], (&array[i] - array) * 4);
    }

    return 0;
}

上面这段代码会将数组每个元素的元素名,实际地址和相对地址偏移打印出来,我们将其运行,并得到如下结果:

jay@jaylinuxlenovo:~/test$ ./test2 
array[0] - 0x7ffc729ca960 - 0
array[1] - 0x7ffc729ca964 - 4
array[2] - 0x7ffc729ca968 - 8
array[3] - 0x7ffc729ca96c - 12
array[4] - 0x7ffc729ca970 - 16
array[5] - 0x7ffc729ca974 - 20
array[6] - 0x7ffc729ca978 - 24
array[7] - 0x7ffc729ca97c - 28
array[8] - 0x7ffc729ca980 - 32
array[9] - 0x7ffc729ca984 - 36

可以看到输出结果与我们分析的一致,array 数组在内存中处于连续排列,其中下标0为低地址,随着下标的增大,内存地址增大,每次增大的步长为一个元素所占的大小,在这里就是整形大小4个字节。

当然,上面只是一种最简单的情况,相信无须讲解大家都能理解。那么接下来上正题:

多维数组在内存中的排布是什么样的呢?

实践是检验真理的唯一标准,我们直接来写一段代码测试一下:

#include <stdio.h>

int main() {
    int array[4][3] = {0};

    for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
        for (int j = 0; j < sizeof(array[0]) / sizeof(array[0][0]); j++) {
            printf("array[%d][%d] - %p - %ld\r\n", i, j, &array[i][j], (&array[i][j] - &array[0][0]) * 4);
        }
    }
    return 0;
}

以上程序定义了一个四行三列的二维数组:

图片

测试代码中我们以行为主序,依次打印出每一行中每一列的元素信息,到行结尾后再次从下一行开始,直到结束。可以看到运行结果如下:

jay@jaylinuxlenovo:~/test$ ./test2 
array[0][0] - 0x7fff25495070 - 0
array[0][1] - 0x7fff25495074 - 4
array[0][2] - 0x7fff25495078 - 8
array[1][0] - 0x7fff2549507c - 12
array[1][1] - 0x7fff25495080 - 16
array[1][2] - 0x7fff25495084 - 20
array[2][0] - 0x7fff25495088 - 24
array[2][1] - 0x7fff2549508c - 28
array[2][2] - 0x7fff25495090 - 32
array[3][0] - 0x7fff25495094 - 36
array[3][1] - 0x7fff25495098 - 40
array[3][2] - 0x7fff2549509c - 44

很巧,我们选用的打印顺序竟然就是数组内存中排列的顺序!可以看到这里的内存递增方式和一开始的一维数组一样,是按照一个整形元素的大小递增的。当然为了严谨起见,我们调换一下程序中的打印顺序,按列为主序打印:

#include <stdio.h>

int main() {
    int array[4][3] = {0};

    for (int j = 0; j < sizeof(array) / sizeof(array[0]); j++) {
        for (int i = 0; i < sizeof(array[0]) / sizeof(array[0][0]); i++) {
            printf("array[%d][%d] - %p - %ld\r\n", i, j, &array[i][j], (&array[i][j] - &array[0][0]) * 4);
        }
    }
    return 0;
}

可以看到与上例的程序相比,i 和 j 的位置对调了。此时我们看一下运行结果:

jay@jaylinuxlenovo:~/test$ ./test2 
array[0][0] - 0x7ffccc482230 - 0
array[1][0] - 0x7ffccc48223c - 12
array[2][0] - 0x7ffccc482248 - 24
array[0][1] - 0x7ffccc482234 - 4
array[1][1] - 0x7ffccc482240 - 16
array[2][1] - 0x7ffccc48224c - 28
array[0][2] - 0x7ffccc482238 - 8
array[1][2] - 0x7ffccc482244 - 20
array[2][2] - 0x7ffccc482250 - 32
array[0][3] - 0x7ffccc48223c - 12
array[1][3] - 0x7ffccc482248 - 24
array[2][3] - 0x7ffccc482254 - 36

不出所料,此时内存的偏移值不再是顺序增加。并且仔细计算可以发现,同一列的元素如 array[0][0] 和 array[1][0] 之间差了3个整形元素所占的大小 - 12字节,而这正好是一行元素的整体大小。

显然,我们可以得到一个结论:在二维数组中,数组元素是按照行的主序来存储的,也就是内存按行分配,一行分配完再分配下一行,并且是连续的。分配的过程我们可以用下图来形象的表示:

图片

分配完成后即可得到最终的内存排列:

图片

实际上,这种存储方式有一种专业的名词:行优先存储(Row-major order)。如果仔细观察上面的元素地址偏移,可以发现这种存储方式最显著的特点就是先把位于右侧的下标排满

这个特点可以让我们拓展到更高维的数组存储方式。

如果我们定义一个三维数组 array[x][y][z],那么其在内存中的排列方式就是先将z维排满,再将y维排满,最后将x维排满。为了加深理解,我们再结合实际的代码:

#include <stdio.h>

int main() {
    int array[2][3][4] = {0};

    for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
        for (int j = 0; j < sizeof(array[0]) / sizeof(array[0][0]); j++) {
            for (int k = 0; k < sizeof(array[0][0]) / sizeof(array[0][0][0]); k++) {
                printf("array[%d][%d][%d] - %p - %ld\r\n", i, j, k, &array[i][j][k],
                       (&array[i][j][k] - &array[0][0][0]) * 4);
            }
        }
    }

    return 0;
}

代码中拓展到了一个三维数组,其内存结构如下:

jay@jaylinuxlenovo:~/test$ ./test2 
array[0][0][0] - 0x7fffef296740 - 0
array[0][0][1] - 0x7fffef296744 - 4
array[0][0][2] - 0x7fffef296748 - 8
array[0][0][3] - 0x7fffef29674c - 12
array[0][1][0] - 0x7fffef296750 - 16
array[0][1][1] - 0x7fffef296754 - 20
array[0][1][2] - 0x7fffef296758 - 24
array[0][1][3] - 0x7fffef29675c - 28
array[0][2][0] - 0x7fffef296760 - 32
array[0][2][1] - 0x7fffef296764 - 36
array[0][2][2] - 0x7fffef296768 - 40
array[0][2][3] - 0x7fffef29676c - 44
array[1][0][0] - 0x7fffef296770 - 48
array[1][0][1] - 0x7fffef296774 - 52
array[1][0][2] - 0x7fffef296778 - 56
array[1][0][3] - 0x7fffef29677c - 60
array[1][1][0] - 0x7fffef296780 - 64
array[1][1][1] - 0x7fffef296784 - 68
array[1][1][2] - 0x7fffef296788 - 72
array[1][1][3] - 0x7fffef29678c - 76
array[1][2][0] - 0x7fffef296790 - 80
array[1][2][1] - 0x7fffef296794 - 84
array[1][2][2] - 0x7fffef296798 - 88
array[1][2][3] - 0x7fffef29679c - 92

掌握了上述方法后,即使是四维,五维甚至更高维的数组,也能立刻搞明白其内存排列的方式。相信在后续处理数组的场合中,大家能够根据数组内存的排列方式写出更高效、稳健的代码。

图片

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/710196.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch解决 多元回归 问题的算法

Pytorch解决 多元回归 问题的算法 回归是一种基本的统计建模技术&#xff0c;用于建立因变量与一个或多个自变量之间的关系。 我们将使用 PyTorch&#xff08;一种流行的深度学习框架&#xff09;来开发和训练线性回归模型。 二元回归的简单示例 训练数据集&#xff08;可获取&…

微信群发机器人.使用指南.

0.简介 1.介绍 微信群发机器人是用来群发微信消息的工具,通过控制电脑的键盘和鼠标操作微信app来实现群发.支持的消息类型有:文字,图片,视频,文件,小程序,位置等. 群发机器人也可以将微信联系人中的信息保存到电脑csv表格中,以供分析. 因其是通过模拟用户操作鼠标键盘来实现群…

如何在 Vue 3 中使用 vue3-print-nb 实现灵活的前端打印

你好&#xff0c;我是小白Coding日志&#xff0c;一个热爱技术的程序员。在这里&#xff0c;我分享自己在编程和技术世界中的学习心得和体会。希望我的文章能够给你带来一些灵感和帮助。欢迎来到我的博客&#xff0c;一起在技术的世界里探索前行吧&#xff01; 前言 在前端开…

go interface

package mainimport "fmt"// 接口 interface func main() {c : Chinese{} //创建一个中国人实例u : American{} //创建一个美国人实例greet(c) //中国人打招呼greet(u) //美国人打招呼 }// 接收具备SayHello接口能力的变量 func greet(s SayHello) {…

KOL营销策略:危机公关中的品牌修复与形象重塑

在当今数字化时代&#xff0c;品牌声誉的管理和维护愈发重要。危机公关作为品牌管理的重要一环&#xff0c;对于企业的长期生存和发展具有至关重要的影响。而KOL作为具有强大影响力和号召力的个体&#xff0c;在危机公关中扮演着不可或缺的角色。本文Nox聚星将和大家探讨KOL在危…

yolo案例项目学习记录

box-ocr: 监控摄像头视频流实时计数传送带的货物&#xff0c;并提取货物上面文字或二维码 1.本地环境&#xff1a; 1.1torch、torchvison、torchaudio版本对应关系 PyTorch中torch、torchvision、torchaudio、torchtext版本对应关系_torch2.0.1对应的torchvision-CSDN博客 1…

Leetcode419. 甲板上的战舰

Every day a Leetcode 题目来源&#xff1a;419. 甲板上的战舰 解法1&#xff1a;一次遍历 战舰的个数&#xff0c;等于战舰「头部」的个数。 具体来说&#xff0c;如果位于 (i, j) 的格子是战舰的头部&#xff0c;那么左边和上边的相邻格子不能是 X。 代码&#xff1a; …

php收银系统源码推荐

智慧新零售系统是一套线下线上一体化的收银系统。致力于给零售门店提供『多样化线下收银』、『ERP进销存』、『o2o小程序商城』、『精细化会员管理』、『丰富营销插件』等一体化行业解决方案&#xff01; 一、多样化线下收银 1.聚合收款码 ①适用商户&#xff1a;小微门店&am…

Vue40-vueComponent构造函数

一、组件的本质&#xff1a;VueComponent构造函数 组件的本质是&#xff1a;构造函数 二、每一次调用vue.extend&#xff0c;返回的事一个全新的 VueComponent VueComponent的源码如下&#xff1a; 三、组件中的this 组件中的this是VueComponent实例对象&#xff0c;结构和vm…

想学编程,什么语言最好上手?

Python是许多初学者的首选&#xff0c;因为它的语法简洁易懂&#xff0c;而且有丰富的资源和社区支持。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频 讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;…

「C系列」C 结构体

文章目录 一、C 结构体1. 定义结构体2. 声明结构体变量3. 初始化结构体变量4. 访问结构体成员5. 结构体数组6. 结构体指针7. 结构体嵌套 二、C 如何使用结构体1. 定义结构体类型2. 声明结构体变量3. 初始化结构体变量4. 访问结构体成员5. 结构体指针6. 在函数中使用结构体7. 注…

utm投影

一 概述 UTM (Universal Transverse Mercator)坐标系是由美国军方在1947提出的。虽然我们仍然将其看作与“高斯&#xff0d;克吕格”相似的坐标系统&#xff0c;但实际上UTM采用了网格的分带&#xff08;或分块&#xff09;。除在美国本土采用Clarke 1866椭球体以外&#xff0c…

【仿真建模-anylogic】Port原理解析

Author&#xff1a;赵志乾 Date&#xff1a;2024-06-14 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 1. 类图 2. 原理解析 2.1 核心函数&#xff1a; Port作为各类型端口的基类&#xff0c;其核心方法如下 &#xff1a; 函数功能Port(A…

德国欧洲杯观战掌中宝

点击标题下「蓝色微信名」可快速关注 今天03:00&#xff0c;德国欧洲杯即将拉开帷幕&#xff0c;首战德国对阵苏格兰&#xff0c;24支欧洲国家队&#xff0c;分为6个小组&#xff0c;你是谁的拥趸&#xff1f; 本届欧洲杯的比赛时间有三个&#xff0c;分别是零点、凌晨三点和晚…

利用MongoDB构建现代支付系统|附《利用AI在支付领域的优势》报告下载

MongoDB 支付 通过实时整合和分析任何格式的数据 来丰富支付体验 为消费者提供增值服务和功能 丰富的支付体验 接受并丰富任何支付数据类型 使用灵活的文档数据模型实时整合和处理任何支付数据类型。JSON 文档&#xff08;与 MongoDB 使用的数据格式相同&#xff09;是支…

FPGA中复位电路的设计

复位电路也是数字逻辑设计中常用的电路&#xff0c;不管是 FPGA 还是 ASIC 设计&#xff0c;都会涉及到复位&#xff0c;一般 FPGA或者 ASIC 的复位需要我们自己设计复位方案。复位指的是将寄存器恢复到默认值。一般复位功能包括同步复位和异步复位。复位一般由硬件开关触发引起…

2024海南省大数据教师培训-Hadoop集群部署

前言 本文将详细介绍Hadoop分布式计算框架的来源&#xff0c;架构和应用场景&#xff0c;并附上最详细的集群搭建教程&#xff0c;能更好的帮助各位老师和同学们迅速了解和部署Hadoop框架来进行生产力和学习方面的应用。 一、Hadoop介绍 Hadoop是一个开源的分布式计算框架&…

仿element-ui 实现自己组件库 <3>

目录 input 组件封装 v-model用在组件上 显示和隐藏密码 封装switch组件 实现转换的功能 设置checkbox input 组件封装 首先input组件的基本框架和样式&#xff1a; <div class"miao-input"><input class"miao-input_inner" > </div…

Java | Leetcode Java题解之第151题反转字符串中的单词

题目&#xff1a; 题解&#xff1a; class Solution {public String reverseWords(String s) {StringBuilder sb trimSpaces(s);// 翻转字符串reverse(sb, 0, sb.length() - 1);// 翻转每个单词reverseEachWord(sb);return sb.toString();}public StringBuilder trimSpaces(S…

这份简历让一位程序员在谷歌获得30万美元

大家好&#xff0c;我是弗雷。每次都在让大家尝试走出职场&#xff0c;但今天我们却选择再次走进职场。 作为曾经的职场人&#xff0c;我深深明白简历的敲门砖作用&#xff0c;制作一份优质的简历至关重要。 你所见过的最优秀的简历是什么样子&#xff1f; 或者你想象中最优秀…