数组与栈区内存
前言
在上一篇 C++内存分区模型 中我提到了栈区,栈区在内存中的分配规则是 从高地址向低地址增长,从下面展示的代码就可以验证
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 20;
cout << &a << endl;
cout << &b << endl;
return 0;
}
输出结果:
变量a的地址是:0x33f6dff96c
变量b的地址是:0x33f6dff968
很明显,变量a的地址大于变量b的地址
疑问
这时候我突然有个问题,栈区不是从高地址向低地址分配吗?为什么我的数组地址是从低地址到高地址呢?
- 首先,数组中的元素是按照从低地址到高地址的顺序存储,这是肯定的,下方的代码可以清晰的证明这个结论
#include <iostream>
int main()
{
int arr[] = {10, 100, 1000};
std::cout << &arr[0] << std::endl;
std::cout << &arr[1] << std::endl;
std::cout << &arr[2] << std::endl;
return 0;
}
输出结果:
arr[0]的地址是:0xea3a3ffb3c
arr[1]的地址是:0xea3a3ffb40
arr[2]的地址是:0xea3a3ffb44
- 其次,栈区内存分配是 从高地址到低地址顺序分配的,这也没错,从上一个例子我们已经证明了
那么我们该如何理解:为什么数组的元素不按照栈区内存的分配方式,内存地址从高到低分配呢?
理解
数组元素的地址分配采用从低地址到高地址的方式,主要是因为历史原因和计算机架构的设计考虑
历史原因:
早期的计算机体系结构和编程语言的设计倾向于使用从低地址到高地址的方式来分配内存。在计算机发展的早期,硬件和编程环境都更适合这种从低地址到高地址的分配方式。因此,这种方式被广泛采用,并成为了传统的内存寻址方式
计算机架构的设计:
现代计算机体系结构中,内存寻址通常采用从0地址开始逐渐增加的方式。这样的设计使得内存寻址和访问变得更加简单和直观。数组在内存中的存储和访问也遵循这种从低地址到高地址的方式
具体原因包括:
缓存机制:现代计算机使用高速缓存(cache)来提高内存访问效率。采用从低地址到高地址的方式,使得连续的数组元素更有可能被预先加载到缓存中,从而提高了数组访问的效率
指针算术:从低地址到高地址的分配方式可以简化指针算术。在大多数编程语言中,数组名被解释为指向数组第一个元素的指针。因此,采用从低地址到高地址的方式,使得数组元素的索引可以直接转换为内存偏移量,从而更容易进行指针运算
虽然栈和数组在内存中的地址分配方式看起来有些矛盾,但实际上它们分别服务于不同的用途
栈是用于函数调用和局部变量的管理,需要高效地进行栈帧的分配和回收,因此采用从大到小的地址分配方式
而数组是用于连续存储相同类型的数据,需要高效的随机访问,因此采用从小到大的地址分配方式。这些设计都是为了在不同的场景下实现更高效的内存管理和数据访问
当然以上问题不值得去深思,当作了解即可,实际没什么作用,主要加深理解
最后附上数组的汇编代码,加深数组在栈区中的分配方式
#include <iostream>
int main()
{
int arr[] = {10, 100, 1000};
std::cout << &arr[0] << std::endl;
std::cout << &arr[1] << std::endl;
std::cout << &arr[2] << std::endl;
return 0;
}
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-12], 10
mov DWORD PTR [rbp-8], 100
mov DWORD PTR [rbp-4], 1000
lea rax, [rbp-12]
mov rsi, rax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
lea rax, [rbp-12]
add rax, 4
mov rsi, rax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
lea rax, [rbp-12]
add rax, 8
mov rsi, rax
mov edi, OFFSET FLAT:_ZSt4cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(void const*)
mov esi, OFFSET FLAT:_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
mov eax, 0
leave
ret