首页 > 其他分享 >从栈溢出到获取栈大小

从栈溢出到获取栈大小

时间:2024-05-17 22:41:28浏览次数:24  
标签:从栈 int 获取 depth 大小 main 溢出 size

从栈溢出到获取栈大小

Author: ChrisZZ [email protected]
Create Time: 2024-05-14 23:22:38
Update Time: 2024-05-17 22:39:39

目录

1. 栈溢出是一个运行时报错

在谷歌搜索 "Stack Overflow", 靠前的结果是一个问答网站。它的本意是“栈溢出”, 是一种运行时错误,例如编写的 C/C++ 代码,编译后运行的时在某个函数的第一句还没执行就挂了。

2. 为什么会出现栈溢出

2.1 运行时的栈大小被限定了

生成可执行程序时, 链接器可以指定运行时栈大小, 超过这个尺寸就发生栈溢出。

对于 MSVC 编译器, 默认栈大小是 1MB; 对于 Linux GCC, 默认栈大小是 8MB。

2.2 栈是怎么被消耗的

栈,是栈内存的简称, 是区别于堆的内存。函数里定义局部变量,就消耗栈内存,这是单个函数内的情况。 函数之间相互调用, 被调用者的栈内存开销, 累加到调用者身上, 构成了总的栈内存开销。

直观理解: C/C++ 程序运行时,入口函数通常为 main(), 假设它调用函数 f1(), f1() 里面调用 f2(), 那么在执行 f2() 时的栈开销就是 main + f1 + f2.

如果 main() 先调用 f1(), 调用完毕再调用 f2(), 那么在执行 f2() 时的栈开销就是 main + f2.

当然,这里假设了 f2() 是叶子函数, 不会调用函数,也不会调用 f2() 自身。

2.3 栈溢出的几种典型情况

  1. 函数递归调用,在到达递归终止条件之前,栈的开销超过了限定值。

  2. 函数调用链路过长,callstack 呈现为超长链表,累计的栈内存开销较大。

这种情况不是递归,但和递归很像。 好的程序的 callstack 应当是有分叉, 呈现树状, 而不是链表状。

  1. 局部变量size过大,尽管函数调用层次可能很浅(甚至只有 main 函数), 仍然栈溢出。 具体又包含如下典型情况:
  • 在函数内,直接定义了超大数组:解决方法是改用new/malloc申请
  • 定义了size很大的结构体或class,并且在函数内创建了实例:
    • 例如直观的: 单个结构体里有一个超大数组的成员(e.g. Visual Studio 鼠标悬停,Intellisense会提示栈大小)
    • 或者间接的: 结构体的成员也是结构体,内层结构体的数组成员M, 乘以外层结构体的数组元素数量N, 乘积很大
    • 解决方法是改为new/malloc申请
  1. 平台或编译工具链限定, 例如 HVX cDSP 平台的 Hexagon-clang, 限定了14000 bytes 左右。

3. 确定栈大小

3.1 暴力法

  1. 编写代码,创建大数组
  2. 编译
  3. 运行
  4. 判断运行结果
    a) 如果运行时没有 crash,则回到步骤1), 并增加数组大小, 否则进入5)
    b) 运行 crash 了,说明超过栈的最大大小,则回到步骤1), 减小数组大小
    c) 如果恰好到了临界值,多了会crash,少了不会crash,那就停止
int main()
{
    int a[1024*1024*8];
    return 0;
}

优点是直观,容易操作, 缺点是需要多次尝试, 在交叉编译和推送设备运行的情况下比较低效率。

3.2 递归法

get_stack_size.c:

#include <stdio.h>

enum { unit_size = 1024 };
const char* unit_str = "KB";

void f(int depth)
{
    char a[unit_size];
    printf("depth = %d, stacksize = %d %s\n", depth, depth * unit_size, unit_str);
    f(depth+1);
}

int main()
{
    int depth = 1;
    f(depth);

    return 0;
}

编译运行:

depth = 7577, stacksize = 7758848 KB
depth = 7578, stacksize = 7759872 KB
[1]    43864 segmentation fault  ./a.out

优点:一次编译、一次运行,比较省事,粗略得到了栈大小。
缺点:打印出的 stacksize 并不准确,单个函数的栈开销并不是 1024 字节。

3.3 更准确的获取栈大小

  1. -fstack-usage 编译选项

GCC/Clang 提供了 -fstack-usage 编译选项, 生成 .c/.cpp 源文件同名的 .su 文件,标注出了每个函数的栈开销大小.

$ gcc get_stack_size.c -fstack-usage
$ cat get_stack_size.su
get_stack_size.c:6:f	1104	static
get_stack_size.c:13:main	32	static

因此,更准确一点的栈大小是 8564864 字节:

>>> 32 + 1104*7758
8564864
  1. -stack_size 链接选项
gcc -Wl,-stack_size,0x1000000 -o my_program my_program.c

当有函数的栈开销超过这一数值时就报警.

4. 避免栈溢出

4.1 修改最大栈大小

不改代码的情况下,修改链接选项,增大栈大小。

Linux/macOS: ulimit 命令。

Windows MSVC: 给链接器指定 /stack:<栈大小> 参数, 例如 /stack:10485760, 10MB。

可在 CMakeLists.txt 中设定

if(MSVC)
  target_link_options(your_target_name PRIVATE "/STACK:10485760")
endif()

4.2 减小栈开销

  1. 使用 new/malloc

使用 new/malloc 替代直接定义大size的栈对象。

#include <stdlib.h>

struct Engine
{
    int id;
    int data[1024];
};

typedef struct Engine Engine;

int main()
{
    //Engine engine; // 这种方式下,总的栈大小是 4128 字节
    Engine* engine = (Engine*)malloc(sizeof(Engine)); // 这种方式下,总的栈大小是 48 字节
    return 0;
}
gcc -c main.c -fstack-usage

查看 .su 文件验证。

  1. 使用柔性数组

使用柔性数组替代结构体中定义的大数组。e.g. https://github.com/jonhoo/pthread_pool/blob/master/pthread_pool.c#L21-L27

struct pool {
	char cancelled;
	void *(*fn)(void *);
	unsigned int remaining;
	unsigned int nthreads;
	struct pool_queue *q;
	struct pool_queue *end;
	pthread_mutex_t q_mtx;
	pthread_cond_t q_cnd;
	pthread_t threads[1];// 柔性数组声明
};

void * pool_start(void * (*thread_func)(void *), unsigned int threads)
{
	struct pool *p = (struct pool *) malloc(sizeof(struct pool) + (threads-1) * sizeof(pthread_t)); //柔性数组填充
	int i;
    ...
}

5. Python 中的栈溢出

Python 默认限定递归次数不能超过1000次. 虽然不是直接的超过了栈大小,但是间接避免了栈开销过大的问题.

def f(depth: int):
    print(depth)
    f(depth+1)

f(1)
995
996
997
Traceback (most recent call last):
  File "/Users/zz/work/immitate/Cpp-homework/test2.py", line 5, in <module>
    f(1)
  File "/Users/zz/work/immitate/Cpp-homework/test2.py", line 3, in f
    f(depth+1)
  File "/Users/zz/work/immitate/Cpp-homework/test2.py", line 3, in f
    f(depth+1)
  File "/Users/zz/work/immitate/Cpp-homework/test2.py", line 3, in f
    f(depth+1)
  [Previous line repeated 993 more times]
  File "/Users/zz/work/immitate/Cpp-homework/test2.py", line 2, in f
    print(depth)
RecursionError: maximum recursion depth exceeded while calling a Python object

使用 sys.getrecursionlimit()和sys.setrecursionlimit()来观察和修改这个限制.

6. 总结

TODO

7. References

标签:从栈,int,获取,depth,大小,main,溢出,size
From: https://www.cnblogs.com/zjutzz/p/18198833

相关文章

  • Golang初学:获取程序内存使用情况,std runtime
    goversiongo1.22.1windows/amd64Windows11+amd64x86_64x86_64GNU/Linux--- 序章本文介绍golang程序占用内存的监控:使用stdruntime的ReadMemStats函数。 ReadMemStats函数https://pkg.go.dev/[email protected]//函数funcReadMemStats(m*MemStats......
  • iOS获取.ips文件并通过Xcode自带的symbolicatecrash解析
    文章讲述如下问题:1.如何获取.ips文件2.如何获取symbolicatecrash3.解析前的准备工作4.如何将.ips转为.crash文件5.如何使用symbolicatecrash解析.crash文件6.异常错误处理1.如何获取.ips文件?在iOS中,你可以通过几种方式找到应用程序的.ips文件,具体取决于你是在开发......
  • flex布局文本居中,文本溢出自动换行的方法
    exportconstColoredItem=({item})=>{return(<divclassName={`w-fullflexitems-centerspace-x-1borderrounded`}style={{backgroundColor:item.color+"1a",borderColor:item.color,borde......
  • 通过mybatisflex获取多数据源mapper
    基于mybatisflex1.8.4:@NoArgsConstructor(access=AccessLevel.PRIVATE)@Slf4jpublicclassDataSource{publicstaticfinalStringA="a";publicstaticfinalStringB="b";publicstaticfinalStringC="c";pub......
  • 微信开发-获取AccessToken授权
    获取微信授权有两种方式1:通过AppID和AppSecret,后台可以直接获取2:通过网页授权(主要应用场景是Web端例如公众号等需要获取用户基本信息,需要用户授权,最终通过Code换取access_token)由于目前Senparc等框架都比较重量级,往往使用其开发时不光要理解微信官方的开发文档,还要理解其类库......
  • C# GridView根据列名获取某行某列的数据
    前台代码前台代码<cimesui:cimesgridviewid="gvReIQC"runat="server"enablemodelvalidation="True"allowpaging="True"autogeneratecolumns="False"Style="background-col......
  • 青龙面板京东CK获取助手
    该程序起源主要是用于获取京东CK,免去了从浏览器中频繁的查找CK、点击、重登账号、变量内容的更换主要包含以下功能能够保存更多的账号配置并且自动输入,方便登入时繁琐的输入账号密码支持一键获取CK支持一键更换Key值支持一键提交到青龙面板中更多功能待续.....使用教......
  • 【Azure Developer】如何通过Azure Portal快速获取到对应操作的API并转换为Python代码
    问题描述对于Azure资源进行配置操作,门户上可以正常操作。但是想通过Python代码实现,这样可以批量处理。那么在没有SDK的情况下,是否有快速办法呢? 问题解答当然可以,AzurePortal上操作的所有资源都是通过RESTAPI来实现的,所以只要找到正确的API,就可以通过浏览器中抓取到的请求B......
  • msvc 获取c++类内存布局 /d1 reportAllClassLayout
     visualstudio配置获取所有类内存布局/d1reportAllClassLayout或者指定类/d1reportSingleClassLayoutXXXclass  编译时输出:     ps:https://www.openrce.org/articles/full_view/23   【原文地址】https://blog.csdn.net/qq_29542611/article......
  • Selenium4自动化测试8--控件获取数据--上传、下载、https和切换分页
    系列导航一、Selenium4自动化测试1--Chrome浏览器和chromedriver二、Selenium4自动化测试2--元素定位By.ID,By.CLASS_NAME,By.TAG_NAME三、Selenium4自动化测试3--元素定位By.NAME,By.LINK_TEXT和通过链接部分文本定位,By.PARTIAL_LINK_TEXT,css_selector定位,By.CSS_SELECTOR四、j......