C 语言数组在内存中是怎样表示的?

C语言数组内存表示解析

gdb告诉我们数组global_array存放在内存0x601050这个地址上。注意0x601050这个地址和刚才看到的0x7ffffffee280这个地址相去甚远,为什么呢?全局区几乎在最底部,栈区在最顶部,所以相差很远。

微信群里有同学问C语言中数组在内存中是怎样表示的,今天就给大家聊聊这个话题。

开局一张图:

图片

图片

这个是经典的Linux进程内存布局,通常我们使用的数据存在这样几个地方:

  • 栈区,Stack
  • 全局区,Global
  • 堆区,Heap

接下来我们分别看一下C语言中的数组在这几个区域是怎样表示的,注意,小风哥的机器是x86 64位。

数组与栈区

来看一段极其简单的代码:

void arr_on_stack() {
    int arr[6];
  
    arr[0]=100;
    arr[1]=200;
    arr[2]=300;
    arr[3]=400;
    arr[4]=500;
    arr[5]=600;

    int a = arr[0];
}

我们定义了一个局部变量arr作为int类型的数组,然后分别将100-600写到了数组中,那么数组arr在内存中是怎样表示的呢?

首先我们编译一下:

# gcc -g -fno-stack-protector a.c

    注意,-fno-stack-protector选项是为了禁止堆栈保护,让汇编更容易懂些。

    好啦,一切准备就绪,可以庖丁解牛啦,使用的刀就是gdb,代码面前了无秘密,gdb面前程序的运行时(run time)了无秘密。

    用gdb来调试刚刚编译出来的程序,这里看一下arr_on_stack函数的汇编指令:

    (gdb) disassemble arr_on_stack
    Dump of assembler code forfunction arr_on_stack:
       0x0000000000400526 <+0>:     push   %rbp
       0x0000000000400527 <+1>:     mov    %rsp,%rbp
       0x000000000040052a <+4>:     movl   $0x64,-0x20(%rbp)
       0x0000000000400531 <+11>:    movl   $0xc8,-0x1c(%rbp)
       0x0000000000400538 <+18>:    movl   $0x12c,-0x18(%rbp)
       0x000000000040053f <+25>:    movl   $0x190,-0x14(%rbp)
       0x0000000000400546 <+32>:    movl   $0x1f4,-0x10(%rbp)
       0x000000000040054d <+39>:    movl   $0x258,-0xc(%rbp)
    => 0x0000000000400554 <+46>:    mov    -0x20(%rbp),%eax
       0x0000000000400557 <+49>:    mov    %eax,-0x4(%rbp)
       0x000000000040055a <+52>:    nop
       0x000000000040055b <+53>:    pop    %rbp
       0x000000000040055c <+54>:    retq
    End of assembler dump.

    每个函数在运行起来后都有属于自己的栈帧,栈帧组成栈区,此时arr_on_stack这个函数的栈区在哪里呢?答案就在寄存器rbp中。

    我们来看一下rbp寄存器指向了哪里?

    (gdb) p $rbp
    $3 = (void *) 0x7ffffffee2a0

      啊哈,原来栈帧在0x7ffffffee2a0这个地方,那么我们的数组arr在哪里呢?别着急,这条指令会告诉我们答案:

      0x000000000040052a <+4>:     movl   $0x64,-0x20(%rbp)

        这行指令的含义是说把100(0x64)放到rbp寄存器减去0x20的地方,显然这就是数组的开头,让我们来计算一下rbp寄存器减去0x20:

        0x7ffffffee2a0(%rbp) - 0x20 =  0x7ffffffee280

          因此,我们预测arr应该在0x7ffffffee280这个位置上。

          接下来我们用gdb验证一下:

          (gdb) p &arr
          $2 = (int (*)[6]) 0x7ffffffee280

          哈哈,怎么样,是不是和我们猜想的一样,数组arr的确就放在了0x7ffffffee280这个位置,是这样存储的:

          图片

          这就是C语言中所谓的数组了,无非就是从0x7ffffffee280 到 0x7ffffffee298这一段内存嘛,数组在栈区就是这么表示的!

          数组与全局区

          同样看一段代码:

          int global_array[6];
          
          void arr_on_global() {
              global_array[0]=1;
              global_array[1]=2;
              global_array[2]=3;
              global_array[3]=4;
              global_array[4]=5;
              global_array[5]=6;
          
              int b = global_array[0];
          }

          同样使用# gcc -g -fno-stack-protector a.c编译,然后用gdb加断点在int b = global_array[0]这行代码,看下全局变量global_array的内存位置:

          (gdb) p &global_array
          $12 = (int (*)[6]) 0x601050 <global_array>

          gdb告诉我们数组global_array存放在内存0x601050这个地址上。

          注意0x601050这个地址和刚才看到的0x7ffffffee280这个地址相去甚远,为什么呢?

          再看下开局那张图:

          图片

          图片

          全局区几乎在最底部,栈区在最顶部,所以相差很远。

          接下来让我们看看0x601050这个内存区域中到底保存了些啥?

          我们使用命令x/6wd 0x601050,这个命令告诉gdb从0x601050这个位置开始以32bit为单位用10进制依次打印6次,让我们来看看打印的是什么?

          (gdb) x/6wd 0x601050
          0x601050 <global_array>:        1      2      3      4
          0x601060 <global_array+16>:     5      6

          哈哈,怎么样,是不是正是全局变量global_array中存放的内容:

          图片

          这就是C语言中所谓的数组了,无非就是从 0x601050到 0x601068这一段内存嘛,数组在全局区就是这么表示的!

          数组与堆区

          来段代码:

          void array_on_heap() {
              int* arr = (int*)malloc(sizeof(int) * 6);
              arr[0] = 100;
              arr[1] = 200;
              arr[2] = 300;
              arr[3] = 400;
              arr[4] = 500;
              arr[5] = 600;
          
              int a = arr[0];
          }

          使用gdb加断点在int a = arr[0];这行代码,然后打印数组arr的地址:

          (gdb) p arr
          $20 = (int *) 0x602010

          注意0x602010这个地址,这个地址和刚才的全局数组global_array的地址0x601050比较接近,因为堆区和全局区挨得比较近,可以再回过头看一下开局那张图。

          然后我们同样使用x命令查看这个区域的内存内容:

          (gdb)  x/6wd 0x602010
          0x602010:       100     200     300     400
          0x602020:       500     600

          依然不出我们所料,这个区域保存的正是数组的值。

          图片

          这就是C语言中所谓的数组了,无非就是从 0x602010到 0x602028这一段内存嘛,数组在堆区就是这么表示的!

          现在你应该明白了吧,C语言中所谓的数组是怎么表示的?很简单,其实也没啥表示,无非就是内存中一段连续的空间,仅此而已。

          AI大模型学习福利

          作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

          一、全套AGI大模型学习路线

          AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

          因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

          二、640套AI大模型报告合集

          这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

          因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

          三、AI大模型经典PDF籍

          随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


          因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

          四、AI大模型商业化落地方案

          因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

          作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

          评论
          成就一亿技术人!
          拼手气红包6.0元
          还能输入1000个字符
           
          红包 添加红包
          表情包 插入表情
           条评论被折叠 查看
          添加红包

          请填写红包祝福语或标题

          红包个数最小为10个

          红包金额最低5元

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

          抵扣说明:

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

          余额充值