非典型性C语言教程-1.1 变量

2024-02-08 05:18

本文主要是介绍非典型性C语言教程-1.1 变量,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

语言最本质的东西就是函数和变量。 函数和变量在编译完成后会有实际的数据在那里,也就是运行时载入内存的时候会占用内存。现在先说变量。

变量按其存储在内存中的位置分有3种, 全局/静态的, 局部/栈(stack)的,和堆(heap)变量。这3个概念其实牵扯到OS对于进程内存的管理方式。

现 代的OS对于一个进程一般采用线性的内存,即对于32位系统而言一个进程的地址空间一般是从0x00000000开始一直到0xFFFFFFFF,很早在 8086机器上引入的分段模式已经不在使用了,数据和代码都放在一起。代码就对应于C语言的函数,全局静态的数据对应与全局/静态变量。除此之外OS载入 进程后还会动态的生成两种内存结构:一种是栈stack,一种是堆heap。由于有时stack的中文翻译也写成 堆栈,所以后面一律使用stack和heap来描述两者。Stack用于函数调用和返回,以及局部变量,heap对应与用malloc函数分配的内存空 间。

全局变量编译完成后在可执行文件中占用一个段,一般称为.data段。进程载入的时候,全局变量也跟着载入内存,在内存中占用固定的 地址。所有使用全局变量的地方最后都变成对固定内存地址的引用。堆变量一般由指针应用,由malloc分配。这种变量一旦分配也对应与内存中的固定地址, 但是要记得分配了就要释放,否则就是著名的内存泄漏错误(memory leak)。如果你的程序在结束时不会因为消耗过多的内存而引发系统响应变慢这样的问题,那么不释放也没有关系,程序结束时,malloc分配的内存会由 OS释放掉。

稍微麻烦一点的是局部变量。局部变量是在函数内部定义的,函数被调用的时候会移动栈顶,形成函数这次执行需要的active frame。下面都以x86机器为例。如图在x86上sp寄存器指向栈顶
bp 一般用于引用栈的内部。一个函数调用一般是这样:首先把函数的参数压入堆栈,然后调用call,call指令会自动压入返回地址。call之后就转到函数 的代码了,函数的头几条指令一般都是移动Sp和BP在栈中开辟一块内存放函数需要的局部变量。然后后面对局部变量的引用都被编译成相对bp的地址,[bp +10]这样的地址。这样处理局部变量的目的就是为了让函数可以重入。在单线程下,不可能有同时运行的代码调用同一个函数,所以重入可以等价与递归,函数 自己调用自己。

最早出现的高级语言Forturn是不支持递归的,当时Forturn对于局部变量的处理和全局变量一样,如果函数自己调用自己,就不能分清楚引用的局部变量到底是哪一个。后来改成了stack的形式。举个例子:

int ff(int n)
{
int ret=1;
if(n==0 || n==1)
ret=1;
else
{
ret=ff(n-1)*n;
}
return ret;
}

这是一个简单的计算阶乘的递归实现的例子。现在假设局部变量ret有固定的地址,那么递归层次中下一层的调用会修改ret的值,使得调用它的函数得不到正确的ret值。我们实际看一下VC8下这段程序产生的代码。

int ff(int n)
{
00411390 push ebp
00411391 mov ebp,esp
00411393 sub esp,0CCh
00411399 push ebx
0041139A push esi
0041139B push edi
0041139C lea edi,[ebp-0CCh]
004113A2 mov ecx,33h
004113A7 mov eax,0CCCCCCCCh
004113AC rep stos dword ptr es:[edi]
int ret=1;
004113AE mov dword ptr [ret],1
if(n==0 || n==1)
004113B5 cmp dword ptr [n],0
004113B9 je ff+31h (4113C1h)
004113BB cmp dword ptr [n],1
004113BF jne ff+3Ah (4113CAh)
ret=1;
004113C1 mov dword ptr [ret],1
else
004113C8 jmp ff+50h (4113E0h)
{
ret=ff(n-1)*n;
004113CA mov eax,dword ptr [n]
004113CD sub eax,1
004113D0 push eax
004113D1 call ff (411145h)
004113D6 add esp,4
004113D9 imul eax,dword ptr [n]
004113DD mov dword ptr [ret],eax
}
return ret;
004113E0 mov eax,dword ptr [ret]
}
004113E3 pop edi
004113E4 pop esi
004113E5 pop ebx
004113E6 add esp,0CCh
004113EC cmp ebp,esp
004113EE call @ILT+300(__RTC_CheckEsp) (411131h)
004113F3 mov esp,ebp
004113F5 pop ebp
004113F6 ret
也 许你不知道如何在VC8下显示反汇编,后面会专门讨论VC8。VC8的反汇编代码已经做了优化,比如局部变量ret的地址表示成了[ret] (004113AE)实际上它应该是类似与[bp+xx]这样的地址。其次VC8的调试版本里面所有的没有初始化的变量都用0xcc来填充,而不是 0x00。 我个人猜想之所有用0xcc来填充是因为0xcc在x86机器上恰好是int 3h指令的机器码,而int 3h指令就是调试中断的指令。可能你不懂汇编,这里只简单的说说。函数调用的开始几行汇编代码都类似,就是完成了建立局部变量空间,也就是active frame的过程,主要操作的是sp和bp寄存器。看一看算ff(2)时的系统内存:
0x0012FBC7 cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 01 00 00 00 f3 13 41 00 c8 fc 12 00 d6 13 41 00 01 00 00 00
0x0012FBF0 ac fd 12 00 9c f9 84 07 00 e0 fd 7f cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc
此时ESP寄存器是0x0012FB9E, 表示栈顶, 栈的第一个元素是01 00 00 00, 即ret=1,后面的一个32位数是00 d6 13 41 即0x4113d600,这个是返回地址。后一个01 00 00 00表示的是函数的参数。调试的时候VC8在栈中插入了大量的cc, 这个是为了VC8的-GS选项,即栈检查,防止缓冲区溢出错误。

可以看到函数返回的时候有退栈的动作(004113E6 add esp,0CCh ),就是释放了局部变量所占用的栈空间,于是局部变量的生存就结束了。

写程序的时候使用那种变量要了解这种变量的特性,和生存周期。比如返回局部变量的地址就是一种典型的错误,局部变量占用的内存,在函数结束之后就还给系统了,返回局部变量的地址在函数完成之后使用,属于未定义的行为。 

这篇关于非典型性C语言教程-1.1 变量的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!


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

相关文章

Go语言中泄漏缓冲区的问题解决

《Go语言中泄漏缓冲区的问题解决》缓冲区是一种常见的数据结构,常被用于在不同的并发单元之间传递数据,然而,若缓冲区使用不当,就可能引发泄漏缓冲区问题,本文就来介绍一下问题的解决,感兴趣的可以了解一下... 目录引言泄漏缓冲区的基本概念代码示例:泄漏缓冲区的产生项目场景:Web 服务器中的请求缓冲场景描述代码

Go语言如何判断两张图片的相似度

《Go语言如何判断两张图片的相似度》这篇文章主要为大家详细介绍了Go语言如何中实现判断两张图片的相似度的两种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 在介绍技术细节前,我们先来看看图片对比在哪些场景下可以用得到:图片去重:自动删除重复图片,为存储空间"瘦身"。想象你是一个

Go语言中Recover机制的使用

《Go语言中Recover机制的使用》Go语言的recover机制通过defer函数捕获panic,实现异常恢复与程序稳定性,具有一定的参考价值,感兴趣的可以了解一下... 目录引言Recover 的基本概念基本代码示例简单的 Recover 示例嵌套函数中的 Recover项目场景中的应用Web 服务器中

CnPlugin是PL/SQL Developer工具插件使用教程

《CnPlugin是PL/SQLDeveloper工具插件使用教程》:本文主要介绍CnPlugin是PL/SQLDeveloper工具插件使用教程,具有很好的参考价值,希望对大家有所帮助,如有错... 目录PL/SQL Developer工具插件使用安装拷贝文件配置总结PL/SQL Developer工具插

Java中的登录技术保姆级详细教程

《Java中的登录技术保姆级详细教程》:本文主要介绍Java中登录技术保姆级详细教程的相关资料,在Java中我们可以使用各种技术和框架来实现这些功能,文中通过代码介绍的非常详细,需要的朋友可以参考... 目录1.登录思路2.登录标记1.会话技术2.会话跟踪1.Cookie技术2.Session技术3.令牌技

Python使用Code2flow将代码转化为流程图的操作教程

《Python使用Code2flow将代码转化为流程图的操作教程》Code2flow是一款开源工具,能够将代码自动转换为流程图,该工具对于代码审查、调试和理解大型代码库非常有用,在这篇博客中,我们将深... 目录引言1nVflRA、为什么选择 Code2flow?2、安装 Code2flow3、基本功能演示

Java Spring 中的监听器Listener详解与实战教程

《JavaSpring中的监听器Listener详解与实战教程》Spring提供了多种监听器机制,可以用于监听应用生命周期、会话生命周期和请求处理过程中的事件,:本文主要介绍JavaSprin... 目录一、监听器的作用1.1 应用生命周期管理1.2 会话管理1.3 请求处理监控二、创建监听器2.1 Ser

MySQL 安装配置超完整教程

《MySQL安装配置超完整教程》MySQL是一款广泛使用的开源关系型数据库管理系统(RDBMS),由瑞典MySQLAB公司开发,目前属于Oracle公司旗下产品,:本文主要介绍MySQL安装配置... 目录一、mysql 简介二、下载 MySQL三、安装 MySQL四、配置环境变量五、配置 MySQL5.1

MQTT SpringBoot整合实战教程

《MQTTSpringBoot整合实战教程》:本文主要介绍MQTTSpringBoot整合实战教程,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录MQTT-SpringBoot创建简单 SpringBoot 项目导入必须依赖增加MQTT相关配置编写

在Java中基于Geotools对PostGIS数据库的空间查询实践教程

《在Java中基于Geotools对PostGIS数据库的空间查询实践教程》本文将深入探讨这一实践,从连接配置到复杂空间查询操作,包括点查询、区域范围查询以及空间关系判断等,全方位展示如何在Java环... 目录前言一、相关技术背景介绍1、评价对象AOI2、数据处理流程二、对AOI空间范围查询实践1、空间查