专栏名称: 嵌入式微处理器
关注这个时代最火的嵌入式微处理器,你想知道的都在这里。
目录
相关文章推荐
艾邦高分子  ·  BOPP电容膜生产企业盘点 ·  6 小时前  
高分子科学前沿  ·  重磅发布:武书连2025中国大学排名! ·  昨天  
艾邦高分子  ·  30+(国内)气凝胶材料生产企业盘点 ·  2 天前  
高分子科学前沿  ·  中国科学院高能物理所陈青ACS ... ·  2 天前  
51好读  ›  专栏  ›  嵌入式微处理器

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

嵌入式微处理器  · 公众号  ·  · 2024-06-07 14:00

正文

最近群里有位同学问“C语言中数组在内存中是怎样表示的”,今天我们就来聊一聊这个话题。

开局一张图:

如上图所示,这是经典的Linux进程内存布局,通常我们使用的数据存在这样几个地方:
  • 栈区,Stack
  • 全局区,Global
  • 堆区,Heap
接下来,我们分别看一下C语言中的数组在这几个区域是怎样表示的(注意:这里的机器是x86 64位)。

1、数组与栈区

先来看一段极其简单的代码:
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 for function arr_on_stack:
   0x0000000000400526 :     push   %rbp
   0x0000000000400527 :     mov    %rsp,%rbp
   0x000000000040052a :     movl   $0x64,-0x20(%rbp)
   0x0000000000400531 :    movl   $0xc8,-0x1c(%rbp)
   0x0000000000400538 :    movl   $0x12c,-0x18(%rbp)
   0x000000000040053f :    movl   $0x190,-0x14(%rbp)
   0x0000000000400546 :    movl   $0x1f4,-0x10(%rbp)
   0x000000000040054d :    movl   $0x258,-0xc(%rbp)
=> 0x0000000000400554 :    mov    -0x20(%rbp),%eax
   0x0000000000400557 :    mov    %eax,-0x4(%rbp)
   0x000000000040055a :    nop
   0x000000000040055b :    pop    %rbp
   0x000000000040055c :    retq
End of assembler dump.

我们在之前的文章《 函数在内存中是怎样表示的? 》多次提到过,每个函数在运行起来后都有属于自己的栈帧,栈帧组成栈区,此时arr_on_stack这个函数的栈区在哪里呢?答案就在寄存器rbp中。

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

(gdb) p $rbp
$3 = (void *) 0x7ffffffee2a0
啊哈,原来栈帧在0x7ffffffee2a0这个地方,那么我们的数组arr在哪里呢?别着急,这条指令会告诉我们答案:
0x000000000040052a :     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这一段内存嘛 ,数组在栈区就是这么表示的!

2、数组与全局区

同样看一段代码:

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 

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

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

再看一下开局那张图:

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

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

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

(gdb) x/6wd 0x601050
0x601050 :        1      2      3      4
0x601060 :     5      6

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

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

3、数组与堆区

再来段代码:

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这一段内存嘛 ,数组在堆区就是这么表示的!






请到「今天看啥」查看全文