Jun 10, 2024
安全

逆向基础 - 指针(二)

指针基础

逆向基础 - 指针(二)
Posted by
SYSTEM· 136 次阅读
专栏系列逆向基础

指针数组

什么是指针数组

首先回顾一下先前关于数组的知识:

所谓数组就是用于存储相同数据类型的集合

再结合先前关于指针的知识:指针的本质也是一种数据类型

于是当数组中存储的成员的数据类型为指针时,该数组就可以称为指针数组(本质是数组)

C
#include "stdafx.h"
void function() {
    int** arr[5]={(int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}
X86ASM
9:        int** arr[5]={(int**)1,(int**)2,(int**)3,(int**)4,(int**)5};
00401038   mov         dword ptr [ebp-14h],1
0040103F   mov         dword ptr [ebp-10h],2
00401046   mov         dword ptr [ebp-0Ch],3
0040104D   mov         dword ptr [ebp-8],4
00401054   mov         dword ptr [ebp-4],5

可以看到指针数组其实并没有什么特别之处,只不过存储的数组成员的数据类型为指针而已

指针数组的赋值也和先前对指针的赋值没有什么区别

结构体指针

什么是结构体指针

所谓结构体指针就是在结构体后加上若干个使其称为一个指针类型

C
#include "stdafx.h"
#include <typeinfo>

struct S1{
        int a;
};

void function(){
        S1* s1=(S1*)0x12345678;
    printf("%x\n",s1);
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}
image-20210311150637448
image-20210311150637448

可以看到,这里关于结构体指针的使用貌似和普通的指针没有什么区别,但此时会发现这里还没有操作结构体内部的成员

所以结构体指针的实际使用也并不是这样,下面看一个错误的例子

C
void function() {
    S1* s1=(S1*)0x12345678;
    int a=s1->a;
}

只是在上面代码的基础上添加了一个读取结构体成员的语句,查看运行结果

image-20210311150039289
image-20210311150039289

运行结果不出所料出错了,开始分析错误的原因

X86ASM
14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr [ebp-4],12345678h
15:       int a=s1->a;
0040103F   mov         eax,dword ptr [ebp-4]
00401042   mov         ecx,dword ptr [eax]
00401044   mov         dword ptr [ebp-8],ecx

反汇编分析

0.执行前s1和s1->a的状态

s1:

image-20210311152832764
image-20210311152832764

s1->a:

image-20210311152903468
image-20210311152903468

1.为结构体指针s1赋值

X86ASM
14:       S1* s1=(S1*)0x12345678;
00401038   mov         dword ptr [ebp-4],12345678h
image-20210311152423580
image-20210311152423580

此时再看看s1->a:

image-20210311152552476
image-20210311152552476

可以发现对s1的赋值操作,改变的不是s1->a的值,而是改变了s1->a的地址

其实从执行前s1和s1->a的状态就可以看出,s1存储的内容并不是直接存储结构体成员的内容,而是存储指向结构体成员的地址

所以这里对于先前对于s1的赋值操作改变的只是成员的地址,而没有改变成员的值

并且刚开始时,结构体成员并没有被分配对应的内存地址


2.访问s1->a

X86ASM
15:       int a=s1->a;
0040103F   mov         eax,dword ptr [ebp-4]
00401042   mov         ecx,dword ptr [eax]
00401044   mov         dword ptr [ebp-8],ecx

此时出错的原因已经显而易见了,先前对s1的赋值操作修改了s1->a的地址,使其指向了一个不可访问的地址而导致出错


正确代码

前面已经知道了出错的原因是访问了不可访问的地址导致出错,并且刚开始结构体成员没有被分配对应的内存地址

于是只要手动为结构体成员分配内存地址即可,这里将使用到malloc函数来进行分配内存地址


malloc函数

X86ASM
void *malloc(size_t size)

参数:size,内存块的大小,以字节为单位

返回值:返回一个指针 ,指向已分配大小的内存。如果请求失败,则返回 NULL

相关头文件:malloc.h、alloc.h、stdlib.h


大致了解了malloc函数,现在来看代码:

C
#include "stdafx.h"
#include <malloc.h>                //这里使用了malloc.h
struct S1 {
    int a;
    int b;
    int c;
};
void function() {
    S1* s1=(S1*) malloc(sizeof(S1));        //申请一块空间大小正好为S1大小的内存
    s1->a=610;
    s1->b=666;
    s1->c=52;
    printf("%d\n",s1->a);
    printf("%d\n",s1->b);
    printf("%d\n",s1->c);
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

image-20210311161113903
image-20210311161113903

可以看到结构体的成员能够正常地被改写和访问


反汇编代码

X86ASM
15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push        0Ch
0040D77A   call        malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr [ebp-4],eax
16:       s1->a=610;
0040D785   mov         eax,dword ptr [ebp-4]
0040D788   mov         dword ptr [eax],262h
17:       s1->b=666;
0040D78E   mov         ecx,dword ptr [ebp-4]
0040D791   mov         dword ptr [ecx+4],29Ah
18:       s1->c=52;
0040D798   mov         edx,dword ptr [ebp-4]
0040D79B   mov         dword ptr [edx+8],34h

反汇编分析

1.先看这个malloc函数

X86ASM
15:       S1* s1=(S1*) malloc(sizeof(S1));
0040D778   push        0Ch
0040D77A   call        malloc (00401150)
0040D77F   add         esp,4
0040D782   mov         dword ptr [ebp-4],eax
  1. 压入了参数0C,对应十进制为12,也就是S1的大小

  2. 调用malloc函数

  3. 堆栈外平衡

  4. 将返回值eax赋值给S1

看看返回值eax的内容:

image-20210311161655990
image-20210311161655990

可以看到eax就对应了结构体中的成员

eax=结构体成员首地址,里面的结构体成员连续存储


2.赋值,将610对应十六进制262赋值给[eax],对应前面的003807B8

X86ASM
16:       s1->a=610;
0040D785   mov         eax,dword ptr [ebp-4]
0040D788   mov         dword ptr [eax],262h

执行后:

image-20210311161920272
image-20210311161920272

3.赋值,将666对应十六进制29A赋值给[ecx+4],对应前面的003807BC

X86ASM
17:       s1->b=666;
0040D78E   mov         ecx,dword ptr [ebp-4]
0040D791   mov         dword ptr [ecx+4],29Ah

执行后:

image-20210311162143537
image-20210311162143537

4.赋值,将52对应十六进制34赋值给[edx+4],对应前面的003807C0

X86ASM
18:       s1->c=52;
0040D798   mov         edx,dword ptr [ebp-4]
0040D79B   mov         dword ptr [edx+8],34h

执行后:

image-20210311162242585
image-20210311162242585

小总结

  • 结构体指针和普通的指针实际上并没有什么不同

  • 在对结构体成员进行操作时,需要先对其进行初始化(为每个结构体成员分配内存地址)

  • 结构体指针并不直接存储结构体成员,而是存储了指向结构体成员的地址,该地址里存放着所有结构体成员


数组指针

前面学了指针数组,现在又来个数组指针,中间用结构体指针作了过渡,避免混淆

什么是数组指针

所谓数组指针,就是指向数组的指针(本质是指针)

既然是指针自然满足先前指针的一切特征:指针的赋值、指针的数据宽度、指针的加减、指针类型相减、指针之间比较

这里就不再赘述先前的内容,有需要可回顾:逆向基础笔记二十 汇编 指针(一)


数组指针的声明

C
int (*px)[2];

声明如上,数组指针变量为px,类型为:int(*)[2];该数组指针指向的数组为int[2]


数组指针和指向数组的指针区别

代码

C
#include "stdafx.h"

void function(){
        int arr[6]={1,2,3,4,5,6};
        //声明一个数组指针,该指针指向数组为:int[2]
        int (*px)[2];        
        //给数组指针赋值,使该数组指针指向arr数组的首地址
        px=(int (*)[2]) &arr[0];
        //用一个临时变量parr2 存储数组指针
        int (*parr2)[2]=px;
    //*px为数组的首地址,也就是arr,这里就相当于int* arr2=arr;此时的arr2就是指向数组的指针
        int* arr2=*px;
    //初始化变量,准备循环
    int i;
    //循环遍历数组
        for(i=0;i<6;i++){
                printf("%x\t%d\n",arr2+i,arr2[i]);
        }
        printf("\n");        
        int a=(int) (parr2+1);
        int b=(int) (arr2+1);
        printf("%x\t%x\n",a,b);   
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

image-20210312012226215
image-20210312012226215

首先可以看到数组的正常遍历

然后分别输出了parr21和arr21的结果,注意这里的结果不同


反汇编代码

X86ASM
8:        int arr[6]={1,2,3,4,5,6};
00401038   mov         dword ptr [ebp-18h],1
0040103F   mov         dword ptr [ebp-14h],2
00401046   mov         dword ptr [ebp-10h],3
0040104D   mov         dword ptr [ebp-0Ch],4
00401054   mov         dword ptr [ebp-8],5
0040105B   mov         dword ptr [ebp-4],6
9:
10:       int (*px)[2];
11:
12:       px=(int (*)[2]) &arr[0];
00401062   lea         eax,[ebp-18h]
00401065   mov         dword ptr [ebp-1Ch],eax
13:
14:       int (*parr2)[2]=px;
00401068   mov         ecx,dword ptr [ebp-1Ch]
0040106B   mov         dword ptr [ebp-20h],ecx
15:       int* arr2=*px;
0040106E   mov         edx,dword ptr [ebp-1Ch]
00401071   mov         dword ptr [ebp-24h],edx
16:
17:       int i;
18:
19:       for(i=0;i<6;i++){
00401074   mov         dword ptr [ebp-28h],0
0040107B   jmp         function+66h (00401086)
0040107D   mov         eax,dword ptr [ebp-28h]
00401080   add         eax,1
00401083   mov         dword ptr [ebp-28h],eax
00401086   cmp         dword ptr [ebp-28h],6
0040108A   jge         function+8Fh (004010af)
20:           printf("%x\t%d\n",arr2+i,arr2[i]);
0040108C   mov         ecx,dword ptr [ebp-28h]
0040108F   mov         edx,dword ptr [ebp-24h]
00401092   mov         eax,dword ptr [edx+ecx*4]
00401095   push        eax
00401096   mov         ecx,dword ptr [ebp-28h]
00401099   mov         edx,dword ptr [ebp-24h]
0040109C   lea         eax,[edx+ecx*4]
0040109F   push        eax
004010A0   push        offset string "%x\t%d\n" (00422024)
004010A5   call        printf (00401160)
004010AA   add         esp,0Ch
21:       }
004010AD   jmp         function+5Dh (0040107d)
22:       printf("\n");
004010AF   push        offset string "\n" (00422020)
004010B4   call        printf (00401160)
004010B9   add         esp,4
23:
24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr [ebp-20h]
004010BF   add         ecx,8
004010C2   mov         dword ptr [ebp-2Ch],ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr [ebp-24h]
004010C8   add         edx,4
004010CB   mov         dword ptr [ebp-30h],edx
26:       printf("%x\t%x\n",a,b);
004010CE   mov         eax,dword ptr [ebp-30h]
004010D1   push        eax
004010D2   mov         ecx,dword ptr [ebp-2Ch]
004010D5   push        ecx
004010D6   push        offset string "%x\t%x\n" (00422fa4)
004010DB   call        printf (00401160)
004010E0   add         esp,0Ch

反汇编分析

1.数组的初始化

X86ASM
8:        int arr[6]={1,2,3,4,5,6};
00401038   mov         dword ptr [ebp-18h],1
0040103F   mov         dword ptr [ebp-14h],2
00401046   mov         dword ptr [ebp-10h],3
0040104D   mov         dword ptr [ebp-0Ch],4
00401054   mov         dword ptr [ebp-8],5
0040105B   mov         dword ptr [ebp-4],6

数组初始化后对应地址和内容为:

image-20210312000021662
image-20210312000021662

2.数组指针的赋值

X86ASM
12:       px=(int (*)[2]) &arr[0];
00401062   lea         eax,[ebp-18h]
00401065   mov         dword ptr [ebp-1Ch],eax

直接将arr数组的首地址也就是0012FF14传给了eax

然后再将eax赋值给数值指针px

数值指针赋值后:

image-20210312012031446
image-20210312012031446

可以看到数值指针里存储的内容为0012FF14即arr的地址


3.将数值指针px赋值给另一个数组指针parr2

X86ASM
14:       int (*parr2)[2]=px;
00401068   mov         ecx,dword ptr [ebp-1Ch]
0040106B   mov         dword ptr [ebp-20h],ecx

赋值后:

image-20210312011934479
image-20210312011934479

可以看到此时parr2里存储的内容px里存储的内容0012FF14=arr首地址


4.将数组的首地址赋值给arr2,即arr2=arr

X86ASM
14:       int* arr2=*px;
0040D82E   mov         edx,dword ptr [ebp-1Ch]
0040D831   mov         dword ptr [ebp-24h],edx

这里要注意到这里和前面一样都是赋值了[ebp-1Ch]

也就是明明赋值的是*px,但是赋值却和px一样

也可以得出结论*px=px,那么*__为什么__px和px是一样的**?

首先要明确无论是px还是*px 它们都是指针,一个为数组指针,而另一个则为普通指针

它们所指向的地址相同,都指向了arr的首地址0012FF14

区别px和*px的本质就在于其数据类型是两种不同的指针结构

指针在相加减时,加减的基本单位是指针去掉一个*后的数据宽度(有疑惑可移步逆向基础笔记二十 汇编 指针(一))

  • px的数据类型为:int (*)[2],去掉一个*后变为int [2],数据宽度为int的数据宽度×数组的成员数4*28

  • *px的数据类型为:int*,去掉一个*后变为int,数据宽度=4


赋值后:

image-20210312012117742
image-20210312012117742

5.循环遍历数组

就是普通的指针循环数组,在先前的笔记中已经有详细介绍,这里不再赘述


6.第二种循环

X86ASM
24:       int a=(int) (parr2+1);
004010BC   mov         ecx,dword ptr [ebp-20h]
004010BF   add         ecx,8
004010C2   mov         dword ptr [ebp-2Ch],ecx
25:       int b=(int) (arr2+1);
004010C5   mov         edx,dword ptr [ebp-24h]
004010C8   add         edx,4
004010CB   mov         dword ptr [ebp-30h],edx

通过前面可以得知*pxpx,parr2arr2,所以这里的[ebp-20h]=[ebp-24h]的:

image-20210312012608527
image-20210312012608527
image-20210312012624438
image-20210312012624438

这里的不同之处就在于一个add了8,另一个add了4,和先前所分析的指针加减的单位相符合,于是产生了不同的结果


小总结

  • 在一个数组指针前加上*获得的就是指向数组的指针,如上例中的int __arr2=__px;

  • 数值指针和指向数组的指针中存储的内容都是数组的首地址,如上例中的px=*pxarr0012FF14

  • 数组指针和指向数组的指针的主要区别在进行运算时的单位不同,前者为数据类型宽度×数组成员数,后者为数据类型宽度


数组指针的应用

可以利用数组指针进行加减时的单位不同来遍历数组的固定间隔的成员

下例为从数组的第二个成员开始,取出间隔为3的数组成员

代码

C
#include "stdafx.h"
void function(){
        int arr[15]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};

        int (*px)[3];        
        //从数组的第二个成员开始
        px=(int (*)[3]) &arr[1];

        int i=0;
        for(i=0;i<15/3;i++){
                printf("%x\t%d\n",px+i,**(px+i));
        //注意这里取了两次*,第一次获得的是指向数组成员的指针,第二个获得的才是数组成员
        }
}
int main(int argc, char* argv[])
{
        function();
        return 0;
}

运行结果

image-20210312014359952
image-20210312014359952

总结

指针数组结构体指针数组指针
本质数组指针指针
描述数组成员类型为指针的数组指向结构体的指针指向数组的指针
声明type* arr[size]struct *structNameint (*name)[size]
补充指针数组的赋值也和先前对指针的赋值没有什么区别在对结构体成员进行操作时,需要先对其进行初始化;结构体指针并不直接存储结构体成员,而是存储了指向结构体成员的地址,该地址里存放着所有结构体成员数组指针和指向数组的指针的主要区别在进行运算时的单位不同,前者为数据类型宽度×数组成员数,后者为数据类型宽度

指针作为参数

代码

C
#include "stdafx.h"
void function(int num){
        num=52;
}
void function2(int* num){
        *num=52;
}
int main(int argc, char* argv[])
{
        int num=610;
        function(num);
        printf("%d\n",num);
        function2(&num);
        printf("%d\n",num);
        return 0;
}

运行结果

image-20210312142400195
image-20210312142400195

可以看到没有通过指针进行传参的数据在function内部修改后并没有影响到原本的数值

而通过指针进行传参的数据在function2内部修改后就影响了原本的数值


反汇编代码

函数外部

num初始化
X86ASM
20:       int num=610;
0040D978   mov         dword ptr [ebp-4],262h

function和输出
X86ASM
21:       function(num);
0040D97F   mov         eax,dword ptr [ebp-4]
0040D982   push        eax
0040D983   call        @ILT+15(function) (00401014)
0040D988   add         esp,4
22:       printf("%d\n",num);
0040D98B   mov         ecx,dword ptr [ebp-4]
0040D98E   push        ecx
0040D98F   push        offset string "%d\n" (0042201c)
0040D994   call        printf (00401130)
0040D999   add         esp,8

function2和输出
X86ASM
23:       function2(&num);
0040D99C   lea         edx,[ebp-4]
0040D99F   push        edx
0040D9A0   call        @ILT+20(function2) (00401019)
0040D9A5   add         esp,4
24:       printf("%d\n",num);
0040D9A8   mov         eax,dword ptr [ebp-4]
0040D9AB   push        eax
0040D9AC   push        offset string "%d\n" (0042201c)
0040D9B1   call        printf (00401130)
0040D9B6   add         esp,8

函数内部

function
X86ASM
码8:    void function(int num){
0040D870   push        ebp
0040D871   mov         ebp,esp
0040D873   sub         esp,40h
0040D876   push        ebx
0040D877   push        esi
0040D878   push        edi
0040D879   lea         edi,[ebp-40h]
0040D87C   mov         ecx,10h
0040D881   mov         eax,0CCCCCCCCh
0040D886   rep stos    dword ptr [edi]
9:        num=52;
0040D888   mov         dword ptr [ebp+8],34h
10:   }
0040D88F   pop         edi
0040D890   pop         esi
0040D891   pop         ebx
0040D892   mov         esp,ebp
0040D894   pop         ebp
0040D895   ret

function2
X86ASM
12:   void function2(int* num){
0040D7E0   push        ebp
0040D7E1   mov         ebp,esp
0040D7E3   sub         esp,40h
0040D7E6   push        ebx
0040D7E7   push        esi
0040D7E8   push        edi
0040D7E9   lea         edi,[ebp-40h]
0040D7EC   mov         ecx,10h
0040D7F1   mov         eax,0CCCCCCCCh
0040D7F6   rep stos    dword ptr [edi]
13:       *num=52;
0040D7F8   mov         eax,dword ptr [ebp+8]
0040D7FB   mov         dword ptr [eax],34h
14:   }
0040D801   pop         edi
0040D802   pop         esi
0040D803   pop         ebx
0040D804   mov         esp,ebp
0040D806   pop         ebp
0040D807   ret

反汇编分析

function分析

function外部
X86ASM
21:       function(num);
0040D97F   mov         eax,dword ptr [ebp-4]
0040D982   push        eax
0040D983   call        @ILT+15(function) (00401014)
0040D988   add         esp,4

将[ebp-4]也就是num赋值给eax,然后将eax作为参数压入到堆栈中

用内存窗口观察此时ebp-4的情况:

此时的ebp-4对应地址为0012FF7C,也就是前面的num

function内部

截取出赋值的关键代码

X86ASM
9:        num=52;
0040D888   mov         dword ptr [ebp+8],34h

注意到此时赋值为[ebp+8](参数),用内存窗口观察其地址并比较和先前的ebp-4的地址

image-20210312143757355
image-20210312143757355

可以看到此时要赋值的地址为0012FF2C,和先前的num地址0012FF7C不同,但值是相同的:0x262=610

于是就造成了就算修改了这里的地址中的值也不会影响原本的值

image-20210312144030058
image-20210312144030058

image-20210312144104146
image-20210312144104146

小总结

在函数内部获得的参数的值虽然和传入的参数的值一致,但实际的内存地址并不相同,于是在函数内部修改值并不会影响到原本的值


function2分析

function2外部
X86ASM
23:       function2(&num);
0040D99C   lea         edx,[ebp-4]
0040D99F   push        edx
0040D9A0   call        @ILT+20(function2) (00401019)
0040D9A5   add         esp,4

将ebp-4的地址传给edx,然后将edx作为参数压入到堆栈中

观察此时ebp-4的情况:

image-20210312144834028
image-20210312144834028

此时ebp-4就是num的地址=0012FF7C


function2内部

截取出关键的赋值代码

X86ASM
13:       *num=52;
0040D7F8   mov         eax,dword ptr [ebp+8]
0040D7FB   mov         dword ptr [eax],34h

首先看[ebp+8]:

image-20210312145250880
image-20210312145250880

会发现此时的ebp+8所存的内容就是num的地址:0012FF7C


这里是将[ebp+8](参数)赋值给eax,赋值后的eax是等于外面的num的

image-20210312145358433
image-20210312145358433

然后再将0x34赋值到参数所存储的内容里

image-20210312145506473
image-20210312145506473

小总结

使用指针作为参数可以实现在函数内部修改后外部的值也可以同步被修改

指针作为参数总结

通过比较指针传参和非指针传参得到了以下结论:

无论是指针传参还是非指针传参,将参数传入函数内部后,只保证了参数的内容一致,但地址不同

之所以指针传参能够实现修改函数外部值的效果,是因为传入的参数的值就是外部值的地址,传入的是个地址,通过修改同个地址里的值而达到了影响外部值的效果

传入参数差异
非指针传参参数是值修改值只修改了函数内部的值,不影响函数外部
指针传参参数是址修改值是通过地址来修改的,因而能够影响函数外部

使用指针作为参数十分常见,因为通过这种方式就可以不用返回值,直接将要返回的数据保存在指针里即可

常见的例子:

C
NTSTATUS
  ZwOpenProcess (
    __out PHANDLE  ProcessHandle,                //进程句柄,PHANDLE 句柄指针
    __in ACCESS_MASK  DesiredAccess,        //想要的权限
    __in POBJECT_ATTRIBUTES  ObjectAttributes,        //指向ObjectAttributes的指针
    __in_opt PCLIENT_ID  ClientId                //指向ClientId的指针
    )

这是一个内核函数,用来打开进程,具体使用细节这里不做重点

这里的ProcessHandle是一个指针,它作为参数就是用来存储打开进程后获得的进程句柄

很多内核函数都采用了这种形式——指针作为参数来存储要返回的数据


指针作为返回值

先前在数组里跳过了将数组作为返回值来传递,因为那时还没有讲到指针,接下来看看指针作为返回值的情形

错误的返回值

代码

C
#include "stdafx.h"
#include <malloc.h>
int* function(){
        int arr[5]={1,2,3,4,5};
        return arr;
}
int main(int argc, char* argv[])
{
        int*arr;        
        arr=function();
        int i;        
        for(i=0;i<5;i++){
                printf("%d\n",arr[i]);
        }
        return 0;
}

运行结果

image-20210312153757990
image-20210312153757990

可以看到运行结果并不是我们所期望的


结果分析

为什么输出的结果和预期结果不一致?

首先要知道,指针指向的地址是在函数内部的arr(临时变量存储在堆栈中),当函数内部的代码执行完毕后,这部分数据就变成了垃圾数据,可以被随意覆盖,而printf函数就是输出的时候讲这部分垃圾数据覆盖了,导致了输出结果的不正确


观察内存窗口,查看刚赋值完后的结果:

可以看到arr指向了一个地址,这个地址也是个堆栈地址:0012FF10

image-20210312154343530
image-20210312154343530

看看这个地址里存储的数据:

image-20210312154434723
image-20210312154434723

可以看到此时的数据是没有问题的


但是当printf执行后会看到:

image-20210312154540525
image-20210312154540525

原本的数据被覆盖了


小总结

指针作为返回值时,如果指针所指向的地址为一个堆栈地址,即指针所指向的为函数内部的临时变量时,其数据可能会在执行其它代码的时候被覆盖

如果想要让指针作为返回值可以正常使用,有以下两种方法:

  1. 让指针所指向的地址为一个全局变量,这样其数据就不会被覆盖

  2. 指针返回后,将其返回的数据拷贝到另一个数组里(拷贝时不能使用会影响堆栈的语句)

第一种方法比较简单,也容易理解,下面以第二种方法为例


正确的返回值

C
#include "stdafx.h"
#include <malloc.h>

int* function() {
    int arr[5]={1,2,3,4,5};
    return arr;
}

int main(int argc, char* argv[])
{
        int*arr;
        int*arr2=(int*)malloc(sizeof(int)*5);        //注意是在函数调用前申请的空间,如果在函数执行后再申请会影响数据
        arr=function();

        int i;
        for(i=0;i<5;i++){
                *(arr2+i)=*(arr+i);                                        //将返回的指针里的数据拷贝到arr2
        }        
        for(i=0;i<5;i++){
                printf("%d\n",arr2[i]);
        }
        return 0;
}

运行结果

image-20210312155525933
image-20210312155525933

可以看到能够正确地输出结果


总结

可以看到将指针作为返回值并没有想象中那么方便

一般来说想要让指针作为返回值一般是采用两种办法:

  • 指针作为参数,然后将要返回的数据存储到参数里

  • 先声明一个全局变量,然后指针指向的为全局变量

评论交流
暂无评论,欢迎分享您的见解