已释放的栈内存

(被调)函数内的局部变量在函数返回时被释放,不应被外部引用。虽然并非真正的释放,通过内存地址仍可能访问该栈区变量,但其安全性不被保证。后续若还有其他函数调用,则其局部变量可能覆盖该栈区内容。常见情况有两种:前次调用影响当前调用的局部变量取值(函数的"遗产");被调函数返回指向栈内存的指针,主调函数通过该指针访问被调函数已释放的栈区内容(召唤亡灵)。

1 函数的"遗产"


【示例1】先后连续调用Ancestor和Sibling函数,注意函数内的dwLegacy整型变量。

 1 void Ancestor(void){
2 int dwLegacy = 42;
3 }
4 void Sibling(void){
5 int dwLegacy;
6 printf("%d\n", dwLegacy);
7 }
8 int main(void){
9 Ancestor();
10 Sibling();
11 return 0;
12 }

若使用普通编译(如gcc test.c),则输出42,因为编译器重用之前函数的调用栈;若打开优化开关(如gcc
-O test.c),则输出一个随机的垃圾数,因为Ancestor函数将被优化为空函数,也不会被main函数调用。

因此,为避免这种干扰,建议声明自动局部变量时对其显式赋初值(初始化)。

【示例2】先后调用Ancestor和Sibling函数,注意函数内的aLegacy数组变量。

 1 void Ancestor(void){
2 int aLegacy[10], dwIdx = 0;
3 for(dwIdx = 0; dwIdx < 10; dwIdx++)
4 aLegacy[dwIdx] = dwIdx;
5 }
6 void Sibling(void){
7 int aLegacy[10], dwIdx = 0;
8 for(dwIdx = 0; dwIdx < 10; dwIdx++)
9 printf("%d ", aLegacy[dwIdx]);
10 }

若使用普通编译,则输出0 1 2 3 4 5 6 7 8
9(Ancestor函数内的数组赋值会影响Sibling函数的数组初值);若打开优化开关,则输出一串随机的垃圾数。

【示例3】连续调用两次Func函数。

1 void Func(void){
2 char acArr[25];
3 printf("%s ", acArr); //注意此句打印结果
4 acArr[0]= ‘a‘; acArr[1] = ‘b‘; acArr[2] = ‘c‘; acArr[3]= ‘\0‘;
5 printf("%s ", acArr);
6 }
7 void FuncInsert(void){char acArr[25] = {0};}

若使用普通编译,则输出(乱码) abc abc abc;若打开优化开关,则输出(空串) abc abc
abc。

若在两次调用中间插入其他函数调用(如FuncInsert),则使用普通编译时输出(乱码) abc (空串)
abc;若打开优化开关时仍输出(空串) abc abc
abc(FuncInsert函数被优化掉)。

2 召唤亡灵


【示例4】Specter函数返回局部变量dwDead的地址,main函数试图打印该地址内容。

1 int *Specter(void){
2 int dwDead = 1;
3 return &dwDead; //编译器将提出警告,如function returns address of local variable
4 }
5 int main(void){
6 int *pAlive = Specter();
7 printf("*pAlive = %d\n", *pAlive);
8 return 0;
9 }

若使用普通编译,则输出* pAlive =
1;若打开优化开关,则Specter函数跳过赋值语句直接返回dwDead变量地址,故输出*p = (随机的垃圾数)。

注意,Specter函数返回值(地址)存放在%eax寄存器内,main函数读取寄存器值,将其作为内存地址访问该地址处的存储内容——该内容很可能并未初始化,或即将被新的调用栈覆盖!

【示例5】GetString函数返回局部字符数组szStr的地址,main函数试图打印该地址内容。

 1 char *GetString(void){
2 char szStr[] = "Hello World"; //此句后增加printf("%s\n", szStr);可防止赋值被优化掉
3 return szStr; //编译器将提出警告,如function returns address of local variable
4 }
5 int main(void){
6 char *pszStr = GetString(); //pszStr指向"Hello World"的副本
7
8 //GetString函数返回后,尝试输出GetString函数内局部字符数组szStr的内存内容
9 #ifdef LOOP_COPY
10 unsigned char ucIdx = 0;
11 char szStackStr[sizeof("Hello World")] = {0};
12 for(ucIdx = 0; ucIdx < sizeof("hello world"); ucIdx++)
13 szStackStr[ucIdx] = pszStr[ucIdx];
14 printf("szStackStr = %s\n", szStackStr); //原szStr处的内容,"Hello World"
15 #endif
16 #ifdef MEMCOPY_CALL //当内存拷贝函数内部无局部或临时变量时,可用该法
17 char szStr[sizeof("Hello World")] = {0};
18 memcpy(szStr, pszStr, sizeof(szStr));
19 printf("szStr = %s\n", szStr);
20 #endif
21 #ifdef CHAR_PRINT
22 printf("pszStr = %c%c%c%c%c%c%c%c%c%c%c%c\n", 23 pszStr[0],pszStr[1],pszStr[2],pszStr[3],pszStr[4],pszStr[5], 24 pszStr[6],pszStr[7],pszStr[8],pszStr[9],pszStr[10],pszStr[11]);
25 #endif
26 #ifdef JUNK_PRINT
27 printf("pszStr = %s\n", pszStr); //当前pszStr处的内容,垃圾
28 #endif
29 return 0;
30 }

调用GetString函数时,将只读数据段存放的字符串常量"Hello
World"拷贝至堆栈临时分配的字符数组szStr,即szStr指向该字符串的可读写副本。函数返回szStr地址,同时栈顶指针下移以保证堆栈指针平衡。此时若有函数调用或单步跟踪(软中断也使用堆栈),则可能覆盖szStr所指向的内存。为保留和查看栈区szStr处的内容,可采用示例中的LOOP_COPY、MEMCOPY_CALL或CHAR_PRINT方法(为避免相互影响,三者中应任选一个)。

若使用普通编译,则三种方法均可输出"Hello
World";若打开优化开关且在GetString函数返回前添加输出szStr内容的语句(以防赋值被跳过),则三种方法仍可输出"Hello
World"。这也证明GetString函数调用返回后,堆栈内存szStr处的内容并未清除。

注意,JUNK_PRINT无论何种编译方式均输出乱码。

另见下面的代码片段:















测试1

测试2

测试3

//采用return返回动态内存地址

char* GetMemory1(char *p, int size){

p = (char *)malloc(size);*

return p;

}

void Test1(void){

char *str = NULL;

str = GetMemory1(str, 100);

strcpy(str, "Hello\n");

printf(str);

free(str);

}

//采用二级指针返回动态内存地址

void GetMemory2(char **p,int size){

*p = (char *)malloc(size);

}

void Test2(void){

char *str = NULL;

GetMemory2(&str, 100);

strcpy(str, "Hello");

printf(str);

free(str);

if(str != NULL)*

strcpy(str,"World\n");

printf(" %s", str);

}

//正确返回只读字符串地址,但无意义(无法修改内容)

char* GetMemory3(void){

char *p = "Hello World";*

return p;

}

void Test3(void){

char *str = NULL;

str = GetMemory3();

printf(str);

}

Test1输出Hello

【注*】malloc函数返回void*指针,但C++不允许void*隐式转换到任意类型指针(需要static_cast)。故建议如下兼容写法:

T* p = (T*)malloc(size * sizeof(*p));或

T* p = (T*)malloc(size * sizeof(T));

Test2输出Hello World

【注*】进程中内存管理由库函数完成。当释放内存时,通常不会将内存归还给操作系统,故可继续访问该地址。但因其已被”回收”,若输出语句前再次分配内存,则同段空间可能被重新分配给其他变量,造成错误。

Test3输出Hello World

【注*】此处若写为char p[] = "Hello World";则返回无效指针,输出不确定。


已释放的栈内存,布布扣,bubuko.com

时间: 2024-10-13 01:42:28

已释放的栈内存的相关文章

栈内存和堆内存的区别

总结: 1 栈:为编译器自动分配和释放,如函数参数.局部变量.临时变量等等 2 堆:为成员分配和释放,由程序员自己申请.自己释放.否则发生内存泄露.典型为使用new申请的堆内容. 除了这两部分,还有一部分是: 3 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在.它主要存放静态数据.全局数据和常量. 转自: 栈内存和堆内存的区别(一个笔试题的一部分)http://blog.csdn.net/richerg85/article/details/19175133 笔试

JAVA堆内存和栈内存初步了解

一.堆内存和栈内存 程序运行时内存分配有三种:静态存储分配,栈式存储分配,堆式存储分配 1.静态存储分配: 在程序编译时就可以确定数据目标在运行时所需要的内存,因此在编译时就为其分配固定大小的内存. 这种分配策略不允许代码中有可变数据结构,比如可变数组,嵌套循环结构或者递归,否则无法计算所需要的内存空间. 2.栈式存储分配: 在程序编译时无法计算所需要内存,只有在程序运行时才可以确定数据目标所需要的内存. 要求程序运行到一个模块的入口处,就可以计算该模块数据区所需要的内存空间. 栈内存是由编译器

JAVA内存管理之堆内存和栈内存

我们常常做的是将Java内存区域简单的划分为两种:堆内存和栈内存.这种划分比较粗粒度,这种划分是着眼于我们最关注的.与对象内存分配密切相关的两类内存域.其中栈内存指的是虚拟机栈,堆内存指的是java堆. 1.栈内存,即虚拟机栈.每个方法被执行的时候都会同时创建一个栈帧,用来存储局部变量,操作栈,动态链接,方法出口等信息.局部变量包括各种基本类型的变量和对象的引用变量都是在方法的栈内存中分配.其中,64位长度的long和double类型的数据占用2个局部变量的空间,其他数据类型只占用1个.局部变量

Java堆空间Vs栈内存

之前我写了几篇有关Java垃圾收集的文章之后,我收到了很多电子邮件,请求解释Java堆空间,Java栈内存,Java中的内存分配以及它们之间的区别. 您可能在Java,Java EE书籍和教程中看到很多有关堆和变量内存的参考,但是几乎没有就程序而言完全解释堆和栈的内存分配的. Java堆空间 Java运行时使用Java堆空间为对象和JRE类分配内存.每当我们创建任何对象时,它总是在堆空间中创建. 垃圾回收在堆内存上运行以释放没有任何引用的对象使用的内存.在堆空间中创建的任何对象都具有访问权限,并

java内存分配分析/栈内存、堆内存

前言 本文将由浅入深详细介绍Java内存分配的原理,以帮助新手更轻松的学习Java.这类文章网上有很多,但大多比较零碎.本文从认知过程角度出发,将带给读者一个系统的介绍. 进入正题前首先要知道的是Java程序运行在JVM(Java Virtual Machine,Java虚拟机)上,可以把JVM理解成Java程序和操作系统之间的桥梁,JVM实现了Java的平台无关性,由此可见JVM的重要性.所以在学习Java内存分配原理的时候一定要牢记这一切都是在JVM中进行的,JVM是内存分配原理的基础与前提

java堆内存,栈内存

栈内存 栈内存中储存基本类型的变量(int a =3:)和引用类型的变量(Car  car).当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用. 堆内存 堆内存中存放由new创建的对象和数组,如new Auto;new int [];引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放.而数组&对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句

java中的堆内存和栈内存

Java把内存分成两种: 一种叫做栈内存 一种叫做堆内存 栈内存 : 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用. 堆内存 : 堆内存用于存放由new创建的对象和数组.在堆中分配的内存,由java虚拟机自动垃圾回收器来管理.在数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着

Java中堆内存和栈内存详解

Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用. 堆内存用于存放由new创建的对象和数组.在堆中分配的内存,由java虚拟机自动垃圾回收器来管理.在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中

什么变量在堆内存里存放,什么变量在栈内存里存放

什么变量在堆内存里存放,什么变量在栈内存里存放 堆和栈的区别 (stack and heap) 一般认为在c中分为这几个存储区 1栈 - 有编译器自动分配释放 2堆 - 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 3全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域. 程序结束释放. 4另外还有一个专门放常量的地方. - 程序结束释放 在函数体中定义的变量通常是在栈上,用mal