当前位置: 首页 > news >正文

如何判断栈生长的方向

文章目录

  • 栈生长的方向
  • 通过调试查看SP指针验证
  • 通过变量地址验证
  • 思考:谁分配的栈空间?
  • 小结

栈生长的方向

栈生长方向的定义

若入栈之后(PSUH后),栈指针寄存器(SP)值变大了,那么栈就是向上生长的。
若入栈之后(PUSH后),栈指针寄存器(SP)值变小了,那么栈就是向下生长的。

在这里插入图片描述


栈生长方向影响的内容

  1. 栈方向影响溢出保护机制的设计,如栈保护页(Guard Page)的放置位置需与生长方向匹配‌

  2. 在FreeRTOS中栈生长的方向也影响任务栈分配与内存管理


通过调试查看SP指针验证

通过调试在stm32f103zet6中查看SP指针验证栈生长方向。

思路:在调试模式下,单步通过进入子函数时观察栈指针的变化,来判断栈生长的方向。

测试代码如下:

void func2(void)
{
	char buf[100] = {0};
	buf[0] = 'A';
}

void func1(void)
{
	char buf[100] = {0};
	buf[0] = 'A';
	func2();
}

int main(void)
{
	func1();
	return 0;
}

进入main()函数时的SP值是0x200009B8
在这里插入图片描述

进入func1()函数后,可以发现SP值变成了0x20000950
在这里插入图片描述

所以,在入栈后,stm32f103zet6的SP指针值是减少的,因此stm32f103zet6的栈生长的方向是向下的

通过变量地址验证

思路:

因为每次调用子函数时,编译器会生成代码来 ‌动态调整栈指针(SP)‌,为该函数分配新的栈帧,用于存储:函数的局部变量‌、函数调用时的 参数‌等内容。

所以通过判断进入子函数时,可以根据函数与其子函数中变量的地址大小来判断栈生长方向。

函数变量的地址值 大于 子函数中变量的地址值,则栈向下生长。
函数变量的地址值 小于 子函数中变量的地址值,则栈向下生长。

#include <stdio.h>  

static int stack_dir = 0; 

static void find_stack_direction (void)
{
	static char* addr = NULL; /* address of first `dummy', once known */
	char dummy; /* to get stack address */
	if (addr == NULL)  
	{                             
		addr = &dummy; /* initial entry */
		find_stack_direction (); /* recurse once */  
	}
	else
	{
		if(&dummy > addr)
		{
			stack_dir = 1;
		}
		else
		{
			stack_dir = -1;
		}
	}
}

int main(void)
{
	find_stack_direction();
	if(stack_dir == 1)
	{
		puts("Stack grew upward\n");
	}
	else
	{
		puts("Stack grew downward\n");
	}
	return 0;
}

stm32f103zet6芯片上运行改代码的结果如下
在这里插入图片描述

思考:谁分配的栈空间?

编译器在帮助我们分配栈空间。

编译器编译阶段会为每个函数生成指令,‌调整栈指针(SP)‌ 以分配或释放栈空间。例如:

  • x86架构‌:通过 sub esp, N 分配栈空间,add esp, N 释放;
  • ARM架构‌:通过 sub sp, sp, #N 分配,add sp, sp, #N 释放。

这里的sub指令即是做减法的指令,add指令是做加法的指令。


计算栈空间的过程是静态的过程

*在编译阶段时,编译器 分析函数的 ‌局部变量、参数传递方式、寄存器保存需求‌ 等,计算出该函数需要占用的总栈空间,并将其写入生成的代码中


每次调用函数时,编译器生成的代码会 ‌动态调整栈指针(SP)‌,为该函数分配新的栈帧,用于存储:

  • 函数的 ‌局部变量‌
  • 函数调用时的 ‌参数‌(若通过栈传递)
  • 返回地址‌(调用结束后恢复执行的位置)
  • 可能被修改的 ‌寄存器值‌(需保存的上下文)

栈分配的本质是 ‌移动栈指针(SP)‌,而非显式的内存申请(如堆的 malloc)。


示例分析

main()函数调用func1()函数时,通过BL.w 0x8000A78指令,跳转到存储func1()函数的代码行中。
在这里插入图片描述

在存储func1()函数的代码中,
我们可以查看第一条指令就是入栈指令PSUH {lr},暂不分析该命令;
第二条指令是分配堆栈的指令SUB sp,sp,#0x64,该命令主要是为char buf[100]该变量分配栈空间。0x64=100,刚好是100个字节。
在这里插入图片描述

func1()函数执行完毕后,通过ADD sp, sp, #0x64来修改SP指针,从而释放了栈空间。
在这里插入图片描述

小结

在该文章中,我们主要了解了如何通过SP指针的变化来判断栈生长的方向,而且我们也分析了arm系列的芯片是怎么去修改栈指针的。

留下个小疑问:栈生长的方向的是由 编译器生成的修改栈指针寄存器的指令 决定的吗?

http://www.dtcms.com/a/113517.html

相关文章:

  • SDL显示YUV视频
  • 快速从零部署一个DeepSeek-R1服务
  • NAS原理与技术详解:从基础概念到实践应用
  • 基础知识补充篇:关于数据不可修改
  • 功能测试和性能测试的区别有哪些?
  • 使用Geotools中的原始方法来操作PostGIS空间数据库
  • java高并发------守护线程Daemon Thread
  • Redis数据结构之ZSet
  • P3654 First Step (ファーストステップ)
  • Linux:(五种IO模型)
  • 基于SSM的高校宿舍水电管理系统
  • 0201线性回归-机器学习-人工智能
  • 开篇 - 配置Unlua+VsCode的智能提示、调试以及学习方法
  • 【LeetCode 热题100】23:合并 K 个升序链表(详细解析)(Go语言版)
  • 《UNIX网络编程卷1:套接字联网API》第7章:套接字选项深度解析
  • 如何理解分类(Category)?Kotlin 扩展是何方神圣?C/C++编译器的C/C++扩展
  • 关于 Spring自定义缓存管理器 的详细说明,包含两种实现方式的对比和代码示例,并附表格总结
  • 复古未来主义屏幕辉光像素化显示器反乌托邦效果PS(PSD)设计模板样机 Analog Retro-Futuristic Monitor Effect
  • 多线程代码案例 - 2
  • 高速电路 PCB 设计要点二
  • 【代码模板】如何用FILE操作符打开文件?fopen、fclose
  • KUKA机器人软件WorkVisual更改语言方法
  • Springboot定时任务开发
  • Java 大视界 -- Java 大数据在智能医疗远程护理与患者健康管理中的应用与前景(175)
  • 游戏引擎学习第205天
  • infinityfree最新免费建站详细教程_无需备案_5G空间_无限流量_免费域名_免费SSL
  • [巴黎高师课程] 同步反应式系统(2024-2025)第三课 - Kind 2: 基于SMT的Lustre模型检查器
  • 快速解决 Java 服务 CPU 过高问题指南
  • Tomcat的部署
  • 泡棉压缩对显示模组漏光的定位分析及论述