博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
已释放的栈内存
阅读量:5967 次
发布时间:2019-06-19

本文共 4508 字,大约阅读时间需要 15 分钟。

 

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

 

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 }
View Code

     若使用普通编译(如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 }
View Code

     若使用普通编译,则输出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};}
View Code

     若使用普通编译,则输出(乱码) 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 variable4 }5 int main(void){ 6     int *pAlive = Specter();7     printf("*pAlive = %d\n", *pAlive);8     return 0;9 }
View Code

     若使用普通编译,则输出* 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_COPY10     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 #endif16 #ifdef MEMCOPY_CALL //当内存拷贝函数内部无局部或临时变量时,可用该法17 char szStr[sizeof("Hello World")] = {
0};18 memcpy(szStr, pszStr, sizeof(szStr));19 printf("szStr = %s\n", szStr);20 #endif21 #ifdef CHAR_PRINT22 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 #endif26 #ifdef JUNK_PRINT27 printf("pszStr = %s\n", pszStr); //当前pszStr处的内容,垃圾28 #endif29 return 0;30 }
View Code

     调用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";则返回无效指针,输出不确定。

 

 

转载地址:http://mgmax.baihongyu.com/

你可能感兴趣的文章
HDU 2571(dp)题解
查看>>
数据类型的内置函数
查看>>
自定义选中文字背景色
查看>>
win10+ubuntu双系统安装方案
查看>>
菜鸟回归……
查看>>
杭电2066--一个人的旅行(Floyd)
查看>>
【随笔】 我的努比亚z7 mini 相机复活记
查看>>
让我的网站变成响应式的3个简单步骤
查看>>
最短路中部分点只能从中任意选取K个问题
查看>>
UDP编程
查看>>
onInterceptTouchEvent / onTouchEvent响应事件的详析
查看>>
html5实例-闪烁的星星
查看>>
PAT_A1143#Lowest Common Ancestor
查看>>
[转载]Linux驱动-SPI驱动 之二:SPI通用接口层
查看>>
Python之网络编程(Socket)
查看>>
HDU - 5493 Queue 2015 ACM/ICPC Asia Regional Hefei Online(线段树)
查看>>
2002-2003 ACM-ICPC Northeastern European Regional Contest (NEERC 02) A Amusing Numbers (数学)
查看>>
java中拦截器 过滤器 监听器都有什么区别
查看>>
面试题:String StringBufere StringBuilder 不用看
查看>>
Django 分页组件替换自定义分页
查看>>