代码分析栈(stack)的生长方向

创建时间:2022/6/6 21:04
标签:bingo


27 篇文章 1 订阅
3 篇文章 0 订阅

1.1背景

以前搞C++的时候,由于没有搞过什么底层逻辑,虽然对堆栈有所了解,但仍停留在表面。今天通过探索下堆栈的生长方向。

**栈:**由编译器在需要的时候分配,在不需要的时候自动清除的变量存储区。里面通常是局部变量,函数参数等。

**堆:**由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

**自由存储区:**由malloc等分配的内存块,和堆十分相似,不过它使用free来结束自己的生命。

**全局/静态存储区:**全局变量和静态变量被分配到同一块内存中,在以前的c语言中。全局变量又分为初始化的和未初始化的,在c++里面没有这个区分了,他们共同占用同一块内存。

查了些资料,有说向上生长,也说向下生长。
另外关于函数参数压栈,也有描述从右往左压栈,这些方向性的描述是什么意思?

1.2代码实例

#include <stdio.h>
#include <string.h>
void test_example(int a,int b,int c)
  {
     int arr[2] = {1,2};
     int d = 0;
     int e = 0;
  
    printf("a:%d a_ptr: %p,  b: %d b_ptr: %p, c: %d c_ptr: %p 	\n",a,&a,b,&b,c,&c);
    printf("d: %d d_ptr:%p , e: %d, e_ptr: %p \n",d,&d,e,&e);
    printf("arr: %p \n",arr);

    memcpy(&e,arr,sizeof(arr));

    printf("c: %d c_ptr: %p, d: %d e: %d ",c,&c,d,e); 
  }
 int main()
 {
    int a = 3;
    int b = 4;
    int c = 5; 
    printf("Hello World \n");
    test_example(a,b,c);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

代码中,很容易发现memcpy copy越界,e只有4bytes,被写越界4个字节。
我们不在这里讨论为什么写越界的问题。你知道写穿的这4个字节影响性吗?或者说覆盖了谁吗?

我们先看下linux操作系统下,gcc编译后运行结果:

可以看到d = 2,栈中d这块内存被覆盖了,证明在栈中d位于高地址,e位于低地址,实际打印也如下图所示。

同样一块代码,在windons系统下运行,如图:


函数参数和局部变量在内存中的位置如上图。

1.3总结

到这我们一直没有解释上下箭头是什么意思。

  • 1、内存没有方向的概念,只有高低的区别。
  • 2、如果压栈的顺序是从低地址往高低地址依次压栈,则是向上生长。
    高地址往低地址依次压栈,则是向下生长。
    这里其实有个潜台词,要先确定栈底位置,比如linux,一块栈内存,栈底位于低地址,所以压栈是从低地址往上增长。windows,栈底则位于高地址。
  • 3、关于函数参数,从右往左压栈没有问题,比如我们例子中的c–>b–>a从栈低依次压栈。
  • 4、关于局部变量,其实也有规律,linux平台是先定义,后入栈。Windows,则是先定义,先入栈。

有了如上认识,我们就可以,当遇到踩内存踩了谁的问题时候,就有了初步分析。
栈的生长方向不同平台不一样,以及一些书籍也是基于某一个平台给出的描述,不能说是错,试着了解下你工作中的栈呢。

文章知识点与官方知识档案匹配,可进一步学习相关知识