彻底弄懂为什么不能把栈上分配的数组(字符串)作为返回值

背景

最近准备一个教程,案例的过程中准备了如下代码碎片,演示解析http scheme

#include <stdio.h>
#include <stdlib.h>
#include <string.h>char *parse_scheme(const char *url)
{char *p = strstr(url,"://");return strndup(url,p-url);
}int main()
{const char *url = "http://static.mengkang.net/upload/image/2019/0907/1567834464450406.png";char *scheme = parse_scheme(url);printf("%s\n",scheme);free(scheme);return 0;
}

上面是通过strndup的方式,背后也依托了malloc,所以最后也需要free
有人在微信群私信parse_scheme能用char []来做返回值吗?我们知道栈上的数组也能用来存储字符串,那我们可以改写成下面这样吗?

char *parse_scheme(const char *url)
{char *p = strstr(url,"://");long l = p - url + 1;char scheme[l];strncpy(scheme, url, l-1);return scheme;
}

大多数人都知道不能这样写,因为返回的是栈上的地址,当从该函数返回之后,那段栈空间的操作权也释放了,当再次使用该地址的时候,值就是不确定的了。

那我们今天就一起探讨下出现这样情况的背后的真正原理。

基础预备

每个函数运行的时候因为需要内存来存放函数参数以及局部变量等,需要给每个函数分配一段连续的内存,这段内存就叫做函数的栈帧(Stack Frame)。
因为是一块连续的内存地址,所以叫帧;为什么叫要加一个呢?
想必大家都熟悉了函数调用栈,为什么叫函数调用栈呢?比如下面的表达式

array_values(explode(",",file_get_contents(...)));

函数的执行顺序是最内层的函数最先执行,然后依次返回执行外层的函数。所以函数的执行就是利用了栈的数据结构,所以就叫栈帧。

x86_64 cpu上的 rbp 寄存器存函数栈底地址,rsp 寄存器存函数栈顶地址。

实验

#include <stdio.h>void foo(void)
{int i;printf("%d\n", i);i = 666;
}int main(void)
{foo();foo();return 0;
}
$gcc -g 2.c$./a.out
0
666

为什么第二次调用foo函数输出的结果都是上次函数调用的赋值呢?先看下反汇编之后的代码

000000000040052d <foo>:
#include <stdio.h>void foo(void)
{40052d:    55                       push   %rbp40052e:    48 89 e5                 mov    %rsp,%rbp400531:    48 83 ec 10              sub    $0x10,%rspint i;printf("%d\n", i);400535:    8b 45 fc                 mov    -0x4(%rbp),%eax400538:    89 c6                    mov    %eax,%esi40053a:    bf 00 06 40 00           mov    $0x400600,%edi40053f:    b8 00 00 00 00           mov    $0x0,%eax400544:    e8 c7 fe ff ff           callq  400410 <printf@plt>i = 666;400549:    c7 45 fc 9a 02 00 00     movl   $0x29a,-0x4(%rbp)
}400550:    c9                       leaveq400551:    c3                       retq0000000000400552 <main>:int main(void)
{400552:    55                       push   %rbp400553:    48 89 e5                 mov    %rsp,%rbpfoo();400556:    e8 d2 ff ff ff           callq  40052d <foo>foo();40055b:    e8 cd ff ff ff           callq  40052d <foo>return 0;400560:    b8 00 00 00 00           mov    $0x0,%eax
}400565:    5d                       pop    %rbp400566:    c3                       retq400567:    66 0f 1f 84 00 00 00     nopw   0x0(%rax,%rax,1)40056e:    00 00

理论分析

第一次进入 foo函数前后

在进入foo函数之前,因为main里没有参数也没有局部变量,所以,main 的栈帧的长度就是0,rbprsp相等(0x7fffffffe2c0)。当执行

callq  40052d <foo>

会把main函数的在调用foo之后需要返回执行的下一行代码的地址压栈,因为是64位机器,地址8字节。
进入foo之后

push   %rbp

rbp的值压栈,因为也是存的地址,所以又占了8字节,所以当初始化foo函数的rbp的时候

mov    %rsp,%rbp

rsp已经在原来的基础上加了16字节,所以从0x7fffffffe2c0变成了0x7fffffffe2b0

sub    $0x10,%rsp

因为foo函数里面局部变量,编译的时候就预留了16字节,所以rsp变为了0x7fffffffe2a0
最后执行了

movl   $0x29a,-0x4(%rbp)

666放在了0x7fffffffe2ac,当第二次调用的时候,打印i的汇编代码如下

    printf("%d\n", i);400535:    8b 45 fc                 mov    -0x4(%rbp),%eax400538:    89 c6                    mov    %eax,%esi40053a:    bf 00 06 40 00           mov    $0x400600,%edi40053f:    b8 00 00 00 00           mov    $0x0,%eax400544:    e8 c7 fe ff ff           callq  400410 <printf@plt>

第二次进入 foo函数前后

因为上次-0x4(%rbp)存了666,而第二次调用foorbp的值又和第一次一样,所以是一个地址。所以666就被打印出来了。

回到主题

#include <stdio.h>
#include <stdlib.h>
#include <string.h>char *parse_scheme(const char *url)
{char *p = strstr(url,"://");long l = p - url + 1;char scheme[l];strncpy(scheme, url, l-1);printf("%s\n",scheme);return scheme;
}int main()
{const char *url = "http://static.mengkang.net/upload/image/2019/0907/1567834464450406.png";char *scheme = parse_scheme(url);printf("%s\n",scheme);return 0;
}


调试信息如下,当从parse_scheme返回时,打印scheme的结果还是http,但是当我们调用printf之后,和上面样例中一样,parse_scheme出栈,printf入栈,则栈上内存就又替换了,所以打印出来的结果则不一定是http了。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/517695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

健壮F.T.+新裸金属重磅发布!全新升级版ZStack加速新基建!

6月2日&#xff0c;以“健壮F.T.新裸金属 新基建下的新IaaS”为主题的2020年ZStack新品线上超级发布会引爆了企业级云市场。面向新基建发展契机&#xff0c;致力于普惠云计算的ZStack与英特尔、阿里云等伙伴强势携手发布2大重磅新品&#xff1a;首个采用了F.T.技术的ZStack Min…

使用datax同步cassandra数据

DataX 是阿里巴巴集团内被广泛使用的离线数据同步工具/平台&#xff0c;实现各种异构数据源之间高效的数据同步功能。最近&#xff0c;阿里云cassandra团队为datax提供了cassandra读写插件&#xff0c;进一步丰富了datax支持的数据源&#xff0c;可以很方便实现cassandra之间以…

整理了一份 Docker系统知识,从安装到熟练操作看这篇就够了 | 原力计划

作者 | IronmanJay责编 | 王晓曼出品 | CSDN博客Docker 基础介绍下图为 Docker 图标&#xff08;是一个鲸鱼上面是集装箱&#xff09;。1、我们为什么要使用Docker当我们在工作中&#xff0c;一款产品从开发设计到上线运行&#xff0c;其中需要开发人员和运维工程师&#xff0c…

阿里巴巴的云原生与开发者

摘要&#xff1a;利用云原生技术构建应用简便快捷&#xff0c;部署应用轻松自如&#xff0c;运行应用按需伸缩。如今&#xff0c;云原生已经成为下一代技术发展的趋势。在 2019 杭州云栖大会开发者峰会上&#xff0c;阿里巴巴资深技术专家李响就为大家分享了阿里巴巴的云原生技…

(企业案例)使用Nacos持久化规则,改造sentinel-dashboard

文章目录一、前言1. 版本选取2. 克隆代码3. 导入 IDEA二、全局修改2.1. 修改 POM2.2. 修改配置文件三、后端代码修改3.1. 包结构部分3.2. nacos 配置文件四、创建规则与 Nacos 交互类4.1. 创建授权规则与 Nacos 交互类4.2. 创建降级规则与 Nacos 交互类4.3. 创建流控规则与 Nac…

如何基于 Nacos 和 Sentinel ,实现灰度路由和流量防护一体化

Nepxion Discovery框架在实现灰度发布和路由功能前提下&#xff0c;结合Nacos和Sentinel&#xff0c;对流量再实施一层防护措施&#xff0c;更能达到企业级的流量安全控制的目的。它的功能包括&#xff1a; 封装远程配置中心和本地规则文件的读取逻辑&#xff0c;即优先读取远…

神操作!一行Python代码搞定一款游戏?给力!

来源&#xff1a;pypl编程榜一直以来Python长期霸占编程语言排行榜前三位&#xff0c;其简洁&#xff0c;功能强大的特性使越来越多的小伙伴开始学习Python 。甚至K12的同学都开始学习Python 编程。新手入门的时候趣味性其实最重要的。那么一行Python 代码到底能玩出什么花样&a…

详解阿里云数据中台,一篇文章全面了解大数据“网红”

一直想写一篇关于数据中台正面文章&#xff0c;现在有闲时做些总结&#xff0c;想充分诠释一下DT内部人如何看待数据中台。 数据中台的概念是最早由阿里巴巴首次提出&#xff0c;是为了应对内部众多业务部门千变万化的数据需求和高速时效性的要求而成长起来的&#xff0c;它既要…

云原生时代,蚂蚁金服公开了新的金融混合云架构

蚂蚁金服在过去十五年重塑支付改变生活&#xff0c;为全球超过十二亿人提供服务&#xff0c;这些背后离不开技术的支撑。在 2019 杭州云栖大会上&#xff0c;蚂蚁金服将十五年来的技术沉淀&#xff0c;以及面向未来的金融技术创新和参会者分享。我们将其中的优秀演讲整理成文并…

Python 薪资降温?不存在的

当你学习编程时&#xff0c;最先被困扰在哪一步&#xff1f;是不是很容易陷入在语法之类的细节而忽视基础概念&#xff1f;解决当前任务的最佳方法是什么&#xff1f;在多种编程语言之间来回切换&#xff0c;却感觉不到效率的提高&#xff1f;0 基础学习编程&#xff0c;最先入…

隐私与AI兼得,蚂蚁金服是如何做到的?

蚂蚁金服在过去十五年重塑支付改变生活&#xff0c;为全球超过十二亿人提供服务&#xff0c;这些背后离不开技术的支撑。在 2019 杭州云栖大会上&#xff0c;蚂蚁金服将十五年来的技术沉淀&#xff0c;以及面向未来的金融技术创新和参会者分享。我们将其中的优秀演讲整理成文并…

Nacos配置中心规范

文章目录一、版本选取和概念理解1. 版本选择2.Namespace3. 如何进行配置和服务的管理、隔离&#xff08;Group&#xff09;二、方案选取1. 命名空间创建2. Namespace实施方案三、nacos配置实战3.1. dev环境配置创建3.2. test环境配置创建四、代码coding实战4.1. 创建2项目4.2. …

干货|Flutter 原理与闲鱼深度实践

王康&#xff08;正物&#xff09;—— Flutter 官方成员 阿里巴巴技术专家&#xff0c;之前主要负责 Flutter 在闲鱼中的混合开发体系&#xff0c;目前重点关注 Flutter 深入度以及生态相关的工作。本文将分享三方面内容&#xff0c; Flutter 的原理、 Flutter 在闲鱼中的应用…

云计算,巨头们的背水一战

作者 | 马超责编 | 伍杏玲头图 | CSDN 下载自视觉中国出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;最近国内各IT巨头技术布局都颇有看点&#xff0c;先是腾讯宣布将投入5000亿&#xff0c;用于新基建的进一步布局&#xff08;将重点投入云计算、人工智能、区块链…

基于函数计算的 Serverless AI 推理

前言概述 本文介绍了使用函数计算部署深度学习 AI 推理的最佳实践, 其中包括使用 FUN 工具一键部署安装第三方依赖、一键部署、本地调试以及压测评估&#xff0c; 全方位展现函数计算的开发敏捷特性、自动弹性伸缩能力、免运维和完善的监控设施。 1.1 DEMO 概述 通过上传一个…

《Flutter in action》开放下载!闲鱼Flutter企业级实践精选

复制链接到浏览器 https://yq.aliyun.com/download/3792?utm_contentg_1000081730 下载。 闲鱼是国内最早使用Flutter的团队&#xff0c;也是Flutter业务线渗入最深的团队之一。 现在承载亿级流量的闲鱼将多年最佳实践经验整理成册&#xff0c;《Flutter in action》 正式面世…

阿里HBase高可用8年抗战回忆录

前言 2011年毕玄和竹庄两位大神将HBase引入阿里技术体系&#xff0c;2014年接力棒转到东8区第一位HBase commiter天梧手中&#xff0c;多年来与淘宝、旺旺、菜鸟、支付宝、高德、大文娱、阿里妈妈等几乎全BU合作伙伴携手共进&#xff0c;支撑了双十一大屏、支付宝账单、支付宝…

nginx 1.9.9 Linux 环境安装

文章目录一、软件下载和安装Nginx相关依赖1. 安装Nginx相关依赖2. 下载Nginx二、源码安装Nginx2.1. 解压2.2. nginx默认配置2.3. 编译安装2.3. 查找安装路径2.4.启动nginx2.5. 查看是否启动成功一、软件下载和安装Nginx相关依赖 1. 安装Nginx相关依赖 yum -y install gcc zli…

Java面向对象部分小结

Java面向对象部分小结 第一天: 1. 了解面向对象和面向过程 2. 对象是什么&#xff0c;静态特性&#xff08;属性&#xff09;&#xff0c;动态特征&#xff08;方法&#xff09; 3. 类和对象的关系 类是抽象的&#xff0c;对象是具体的类是具有相同属性和行为&#xff08;…

音视频应用驶入快车道 开发者如何快速追赶这波技术红利?

受访人 | 融云CPO 任杰 作者 | June 图片来源 | 视觉中国 毋庸置疑&#xff0c;随着5G时代的到来&#xff0c;实时音视频技术将会上升到一个全新的高度。 5G时代发生巨变的远远不止网速&#xff0c;凭借5G网络的高带宽&#xff0c;低延迟和大并发性&#xff0c;音视频应用场…