上次说了静态数组可变长,今天知道原理了

cpp加油站
• 阅读 1675

之前发了一篇文章,讲c99变长数组的,链接如下:

多年老c++程序员在静态数组这里翻船了

发出去以后有了挺多的反馈,因为这并不是一个很难的知识点,所以如果接触过的自然而然是知道,但还真有挺多人表示不知道和不相信这个事,同时我上次也只是简单的说了一下这个事,没有去讲解这个变长静态数组的实现原理,今天补上。

先看一下思维导图:

上次说了静态数组可变长,今天知道原理了

1. 变长数组是长度一直可以变的吗

变长数组,那么是长度一直可以变的吗,到底什么时候这个长度会确定下来呢?

我们先看一下代码,如下:

#include <iostream>
using namespace std;

int main()
{
    int size = 1000;
    cout << "please input a number:";
    cin >> size;
    int arr[size];
    cout << "please input a number too:";
    cin >> size;
    cout << "arr's size is " << sizeof(arr)/sizeof(arr[0]) << endl;

    return 0;
}

假设我们第一次输入100,第二次输入10000,那么最后一个cout到底输出多少呢,答案是100。

这里的所谓变长数组,实际上指的是可以使用变量来作为数组的元素个数,在还未运行到声明数组的地方时,还可以通过改变变量的值来修改数组的元素个数,但是等到运行到数组声明的地方后,这个数组的大小就确定了,后续就不能再改变了,所以所谓的变长也只是相对于运行到这个数组声明的地方而言。

2. 变长数组是分配在堆上吗

当然不是,注意这里概念不要搞混淆了,变长数组不是动态数组,虽然是到运行时才确定大小,但说到底它还是局部变量,而局部变量,又没有动态申请内存的动作,当然也只会在栈上分配内存。

关于这一点我们可以结合gdb和寄存器地址来看一下,我的机器是x86架构,那么栈的内存分配方式就应该是从高地址往低地址分配,如果学过汇编的会知道,gcc编译的时候不添加-O1或者-O2这样的选项,那么栈底指针地址会保存在寄存器rbp中,而栈顶指针则会保存在rsp寄存器中,如果地址是在rsp和rbp之间的,那就可以肯定这段内存是保存在栈中了。

还是这段代码:

//test.cpp
#include <iostream>
using namespace std;

int main()
{
    int size = 1000;
    cout << "please input a number:";
    cin >> size;
    int arr[size];

    return 0;
}

使用g++ -g test.cpp(注意这里千万不能加优化选项)编译后,gdb ./a.out看一下:

(gdb) b main #打断点
Breakpoint 1 at 0x4007fd: file test.cpp, line 11.
(gdb) r
Starting program: /root/a.out 

Breakpoint 1, main () at test.cpp:11
11        return 0;
(gdb) n
6        int size = 1000;
(gdb) 
7        cout << "please input a number:";
(gdb) 
8        cin >> size;
(gdb) 
please input a number:10000
9        int arr[size];
(gdb) p $rbp  #打出基址寄存器的值
$1 = (void *) 0x7fffffffe840
(gdb) n
11        return 0;
(gdb) p &arr[0]
$3 = (int *) 0x7fffffff4ba0
(gdb) p $rsp  #打出栈顶指针的值
$4 = (void *) 0x7fffffff4ba0
(gdb) 

可以看到,arr数组的首地址是在(rsp, rbp)这个范围之间的,所以运行时的变长数组也是保存在栈中的,既然是在栈中,那么这段内存在这个变量作用域结束以后就会被释放掉。

3. 变长数组与alloca函数

同时这次也是涨知识了,我才知道c标准库中有个alloca函数,它的用法类似malloc,但内存因为是在栈上申请的,也是不需要手动释放的,这么一看,这个函数的原理和变长数组其实是一样的,都可以使用运行时才确定大小的变量值来申请栈空间,并且不需要手动释放。

知道以后,我也很好奇,所以还专门去研究了下这两者的汇编指令是不是一样,先看下c++代码:

#include <iostream>
#include <alloca.h>
using namespace std;

int main()
{
    int size = 1000;
    cout << "please input a number:";
    cin >> size;
    int arr[size];
    int *p = (int*)alloca(size);

    return 0;
}

然后通过gdb,我分别打印出来int arr[size]int *p = (int*)alloca(size)这两行代码所对应的汇编指令,做了一个对比,如图:

上次说了静态数组可变长,今天知道原理了

可以看得出来变长数组是在一开始先做了一些其他的动作,然后后面的指令跟alloca的指令基本就是一样的了,也就是说最终他两的实现是比较类似的。

4. 变长数组使用注意点

基于变长数组的特点,它其实相当于一个变相版的动态申请内存,只是不需要堆而言,而这种场景多应用于小型机里面,比如很多嵌入式环境,因为资源有限,是没有堆内存的,那如果又需要动态改变数组大小怎么办,就可以使用变长数组,但这时也需要限定一下大小,不然很容易就会造成栈溢出。

好了,本篇文章就为大家介绍到这里,觉得内容对你有用的话,记得顺手点个赞哦~

点赞
收藏
评论区
推荐文章
秃头王路飞 秃头王路飞
4个月前
webpack5手撸vue2脚手架
webpack5手撸vue相信工作个12年的小伙伴们在面试的时候多多少少怕被问到关于webpack方面的知识,本菜鸟最近闲来无事,就尝试了手撸了下vue2的脚手架,第一次发帖实在是没有经验,望海涵。languageJavaScript"name":"vuecliversion2","version":"1.0.0","desc
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
1年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
1年前
ArrayList源码分析(JDK1.8)
概述ArrayList底层是基于 数组实现的,并且支持 动态扩容 的动态数组(变长的集合类)。ArrayList允许空值和重复的元素,当向ArrayList中添加元素数量大于其底层数组容量时,会通过 扩容机制 重新生成一个容量更大的数组。另外,由于ArrayList底层数据结构是数组,所以保证了在O(1)复杂度下完成随机查
Stella981 Stella981
1年前
HashMap 的底层实现原理
HashMap是一个用于存储KeyValue键值对的集合,每一个键值对也叫做Entry。这些个Entry分散存储在一个数组当中,这个数组就是HashMap的主干。HashMap数组每一个元素的初始值都是Null。 !(https://oscimg.oschina.net/oscnet/8495d30fe00a2865dd74088d2
Wesley13 Wesley13
1年前
GNU C 与 ANSI C的区别
1.零长度数组GNUC允许使用零长度数组,定义变长度对象时比较方便structvar\_data{   intlen;   chardata\0\;};var\_data的大小仅为一个int型,data是常量地址,data\index\是访问其后的内存空间。structvar\_data\smal
Wesley13 Wesley13
1年前
Java数组的声明与创建
今天在刷Java题的时候,写惯了C发现忘记了Java数组的操作,遂把以前写的文章发出来温习一下。首先,数组有几种创建方式?Java程序中的数组\\必须先进行初始化才可以使用,\\所谓初始化,就是为数组对象的元素分配内存空间,并为每个数组元素指定初始值,而在Java中,数组是静态的,数组一旦初始化,长度便已经确定,不能再随意更改。
Wesley13 Wesley13
1年前
PHP中的NOW()函数
是否有一个PHP函数以与MySQL函数NOW()相同的格式返回日期和时间?我知道如何使用date()做到这一点,但是我问是否有一个仅用于此的函数。例如,返回:2009120100:00:001楼使用此功能:functiongetDatetimeNow(){
helloworld_34035044 helloworld_34035044
6个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为