探索递归奥秘——递归 与 栈

本文详细介绍了C程序在内存中的布局方式,包括BSS段、数据段、代码段、堆和栈的作用及特点。此外,还深入探讨了递归的工作原理,包括递归的三个阶段以及如何通过实例来理解递归过程。

   参考的博文来自   点击打开链接

自我总结加整理:

一.C程序在内存中的组织方式

1.1 BSS段/BSS segment

此块内存区域 存放 程序中未初始化的 全局变量,属于静态内存分配。

BSS = block started by symbol

 

1.2 数据段/DATA segment

此块内存区域 存放 程序中已经初始化的 全局变量,属于静态内存分配。

 

1.3 代码段/CODE segment/TEXT segment

此块内存区域 存放 程序的执行代码。

函数名称 和 代码 放在代码段中。

 

1.4 堆/heap

是把 内存 进行 动态分配 的内存段。

Heap大小并不固定,可以动态地扩张和缩减:

进程通过调用malloc分配的内存 会被 动态地 添加到堆上,这时堆扩张;

进程通过调用free释放的内存 会被 动态地 从堆中剔除,这时堆缩减。

堆存放数据是 由低地址到高地址

 

1.5 栈/堆栈/stack

存放 程序的 非static声明的局部变量 和 函数被调用时用到的参数和返回值

static变量 是在 数据段中 存放的变量

由于 栈的 先进后出 的特点,栈特别方便用来保存/恢复调用现场,从这个意义上说,可以把 堆栈/栈 看成一个寄存、交换临时数据的内存区域。

栈存放数据是 由高地址到低地址

 

二.C语言中函数的执行方式

C程序在调用一个函数的时候,栈 会分配一块空间保存和这个调用相关的信息,并且每一个调用的状态都被当作是活跃的。所以这块空间 称为活跃记录 或者 栈帧

栈帧 的 5个区域:

1. 输入参数

2. 返回值空间

3. 计算表达式用到的临时存储空间

4. 函数调用时保存的状态信息

5. 输出参数


         代   码          

#include <stdio.h>
#include <malloc.h>

int g1=0, g2=0, g3=0;

int max(int i)
{
    int m1 = 0, m2, m3 = 0, *p_max;
    static int n1_max = 0, n2_max, n3_max = 0;
    p_max = (int*)malloc(10);
    printf("打印max程序地址\n");
    printf("in max: 0x%08x\n\n",max);//0x00401350
    printf("打印max传入参数地址\n");
    printf("in max: 0x%08x\n\n",&i);//0x0028ff00
    printf("打印max函数中静态变量地址\n");
    printf("0x%08x\n",&n1_max);//0x00405014
    printf("0x%08x\n",&n2_max);//0x00405018
    printf("0x%08x\n\n",&n3_max);//0x0040501c
    printf("打印max函数中局部变量地址\n");
    printf("0x%08x\n",&m1);//0x0028fee8
    printf("0x%08x\n",&m2);//0x0028fee4
    printf("0x%08x\n\n",&m3);//0x0028fee0
    printf("打印max函数中malloc分配地址\n");
    printf("0x%08x\n\n",p_max); //0x006c17c8
    if(i) return 1;
    else return 0;
}

int main(int argc, char **argv)
{
    static int s1=0, s2, s3=0;
    int v1=0, v2, v3=0;
    int *p;
    p = (int*)malloc(10);
    printf("打印各全局变量(已初始化)的内存地址\n");//打印各全局变量的内存地址
    printf("0x%08x\n",&g1);//0x00405008
    printf("0x%08x\n",&g2);//0x0040500c
    printf("0x%08x\n\n",&g3);//0x00405010
    printf("======================\n");

    printf("打印程序初始程序main地址\n");
    printf("main: 0x%08x\n\n", main);//0x00401473

    printf("打印主参地址\n");
    printf("argv: 0x%08x\n\n",argv);//0x006c17a0

    printf("打印各静态变量的内存地址\n");//打印各静态变量的内存地址
    printf("0x%08x\n",&s1);//0x00405020
    printf("0x%08x\n",&s2);//0x00405024
    printf("0x%08x\n\n",&s3);//0x00405028

    printf("打印各局部变量的内存地址\n");//打印各本地变量的内存地址
    printf("0x%08x\n",&v1);//0x0028ff18
    printf("0x%08x\n",&v2);//0x0028ff14
    printf("0x%08x\n\n",&v3);//0x0028ff10

    printf("打印malloc分配的堆地址\n");
    printf("malloc: 0x%08x\n\n",p);//0x006c17b0
    printf("======================\n");

    max(v1);
    printf("======================\n");
    printf("打印子函数起始地址\n");
    printf("max: 0x%08x\n\n",max);//0x00401350
    return 0;
}

  代 码 执 行 结 果  





 

地址(0x)

变量名称

变量性质

所属内存区域

各变量在函数调用过程中的访问顺序:

先 全局变量;

再 main函数;

最后 子函数

 

(高地址)

 

命令行参数 和环境变量

 

stack

 

heap

 

BSS:存放未初始化的数据

 

Data:初始化数据存放区域

 

Code:代码段

 

(低地址)

高:006c17c8

P_max

函数max的指针变量,malloc申请空间

 

18

006c17b0

p

函数main的指针变量,malloc申请空间

 

12

006c17a0

argv

Main函数命令行参数

 

5

 

 

 

 

 

00405028

S3

函数main的局部变量(static声明)

Data seg

8

00405024

S2

函数main的局部变量(static声明)

Data seg

7

00405020

S1

函数main的局部变量(static声明)

Data seg

6

0040501c

n3_max

函数max的局部变量(static声明)

Data seg

21

00405018

n2_max

函数max的局部变量(static声明)

Data seg

20

00405014

n1_max

函数max的局部变量(static声明)

Data seg

19

00405010

g3

初始化的 全局变量

Data seg

3

0040500c

g2

初始化的 全局变量

Data seg

2

00405008

g1

初始化的 全局变量

Data seg

1

00401473

main

函数名

Code seg

4

00401350

max

函数名

Code seg

13

 

 

 

 

 

0028ff18

V1

函数main的局部变量(非static声明)

stack

9

0028ff14

V2

函数main的局部变量(非static声明)

stack

10

0028ff10

V3

函数main的局部变量(非static声明)

stack

11

0028ff00

i

函数max的参数

stack

14

 

 

 

 

 

0028fee8

M1

函数max的局部变量(非static声明)

stack

15

0028fee4

M2

函数max的局部变量(非static声明)

stack

16

低:0028fee0

M3

函数max的局部变量(非static声明)

stack

17

Stack 是 从高地址到地址值 对变量进行内存空间的分配

一.递归的工作原理

递归 就是 在过程或函数里调用自身。

递归分为两个阶段:

1)递推:把复杂的问题的求解推到比原问题简单一些的问题的求解;

2)回归:当获得最简单的情况后,逐步返回,依次得到复杂的解.

 

利用递归可以解决很多问题:如背包问题,汉诺塔问题,斐波那契数列问题,...等.


递归 需要有:

1. 边界条件,即明确的递归结束条件,称为递归出口。

2. 递归前进段

3. 递归返回段

边界条件不满足时,递归前进;

边界条件满足时,递归返回。

 

通过 阶乘 探索递归:

代码:

#include <stdio.h>

int factorial(int n,int count)// count:累加器
{
    printf("%d --------- ",count);
    if(n<0){
        printf("n = %d < 0, address[n] = 0x%p\n",n,&n);
        return -1;
    }else if(n==0 || n==1){
        printf("n = %d, address[n] = 0x%p\n",n,&n);
        return 1;
    }else{
        printf("n = %d, address[n] = 0x%p\n",n,&n);
        count++;
        return n*factorial(n-1,count);
    }
}

int main()
{
    printf("size of int = %d bytes\n",sizeof(int));

    int result =0;
    result = factorial(4,1);
    printf("result = %d, address[result] = 0x%p\n",result,&result);
}


代码执行结果:

1 --------- n = 4, address[n] = 0x0028FF00

2 --------- n = 3, address[n] = 0x0028FEE0

3 --------- n = 2, address[n] = 0x0028FEC0

4 --------- n = 1, address[n] = 0x0028FEA0

Result = 24, address[result] = 0x0028FF1C

这里需要注意的是:上面2个标黄的地址,相差的不是32个比特,而是28个比特,这说明什么呢?(哪个有缘人能帮我解答这个问题?)

对执行结果进行解析:

1.

int类型的变量 占 4bytes = 32bits,

字节地址:一个字节中有8比特,字节地址就是第一个比特单元的地址

本机的主机字节序是 小端字节序(具体看解析2)

24 = 1*16+8 = 0x 00 00 00 18 = 0b 0000 0000 0000 0000 0000 0000 0001 1000

 

变量

字节地址:上高下低

一个字节中的8比特

 

0x0028FF1C

0x0028FF1C

0x0028FF1B

0x0028FF1A

0x0028FF19

0x0028FF18

0x0028FF17

0x0028FF16

0x0028FF15

 

0x18

0

0

0

1

1

0

0

0

 

0x0028FF14

0x0028FF14

0x0028FF13

0x0028FF12

0x0028FF11

0x0028FF10

0x0028FF0F

0x0028FF0E

0x0028FF0D

 

0x00

0

0

0

0

0

0

0

0

 

0x0028FF0C

0x0028FF0C

0x0028FF0B

0x0028FF0A

0x0028FF09

0x0028FF08

0x0028FF07

0x0028FF06

0x0028FF05

 

0x00

0

0

0

0

0

0

0

0

 

0x0028FF04

0x0028FF04

0x0028FF03

0x0028FF02

0x0028FF01

0x0028FF00

0x0028FEFF

0x0028FEFE

0x0028FEFD

 

0x00

0

0

0

0

0

0

0

0

 

 

 

 

 

 

0

0

0

0

 

 

0x0028FEFC

0x0028FEFB

0x0028FEFA

0x0028FEF9

0x0028FEF8

0x0028FEF7

0x0028FEF6

0x0028FEF5

 

 

0

1

0

0

0

0

0

0

 

 

0x0028FEF4

0x0028FEF3

0x0028FEF2

0x0028FEF1

0x0028FEF0

0x0028FEEF

0x0028FEEE

0x0028FEED

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEEC

0x0028FEEB

0x0028FEEA

0x0028FEE9

0x0028FEE8

0x0028FEE7

0x0028FEE6

0x0028FEE5

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEE4

0x0028FEE3

0x0028FEE2

0x0028FEE1

0x0028FEE0

0x0028FEDF

0x0028FEDE

0x0028FEDD

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEDC

0x0028FEDB

0x0028FEDA

0x0028FED9

0x0028FED8

0x0028FED7

0x0028FED6

0x0028FED5

 

 

0

0

1

1

0

0

0

0

 

 

0x0028FED4

0x0028FED3

0x0028FED2

0x0028FED1

0x0028FED0

0x0028FECF

0x0028FECE

0x0028FECD

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FECC

0x0028FECB

0x0028FECA

0x0028FEC9

0x0028FEC8

0x0028FEC7

0x0028FEC6

0x0028FEC5

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEC4

0x0028FEC3

0x0028FEC2

0x0028FEC1

0x0028FEC0

0x0028FEBF

0x0028FEBE

0x0028FEBD

 

 

0

0

1

0

0

0

0

0

 

 

0x0028FEBC

0x0028FEBB

0x0028FEBA

0x0028FEB9

0x0028FEB8

0x0028FEB7

0x0028FEB6

0x0028FEB5

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEB4

0x0028FEB3

0x0028FEB2

0x0028FEB1

0x0028FEB0

0x0028FEAF

0x0028FEAE

0x0028FEAD

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEAC

0x0028FEAB

0x0028FEAA

0x0028FEA9

0x0028FEA8

0x0028FEA7

0x0028FEA6

0x0028FEA5

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FEA4

0x0028FEA3

0x0028FEA2

0x0028FEA1

0x0028FEA0

0x0028FE9F

0x0028FE9E

0x0028FE9D

 

 

0

0

0

1

0

0

0

0

 

 

0x0028FE9C

0x0028FE9B

0x0028FE9A

0x0028FE99

0x0028FE98

0x0028FE97

0x0028FE96

0x0028FE95

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FE94

0x0028FE93

0x0028FE92

0x0028FE91

0x0028FE90

0x0028FE8F

0x0028FE8E

0x0028FE8D

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FE8C

0x0028FE8B

0x0028FE8A

0x0028FE89

0x0028FE88

0x0028FE87

0x0028FE86

0x0028FE85

 

 

0

0

0

0

0

0

0

0

 

 

0x0028FE84

0x0028FE83

0x0028FE82

0x0028FE81

 

 

 

 

 

 

0

0

0

0

 

 

 

 

 

 

2. 

字节序:以字节为单位存储数值,有可能以大端字节序方式存储,也有可能以小端字节序存储

大端字节序:

地址 低位 存储 高位,

地址 高位 存储 低位

小端字节序:

地址 低位 存储 低位,

地址 高位 存储 高位

网络字节序:

网络中传输数据用到的字节序。

网络字节序 TCP/IP规定好的一种数据表示格式,并且它是确定的,与具体的CPU类型、操作系统无关, 所以能够保证数据在不同主机之间传输的时候能够被正确解释。

网络字节序 采用 大端字节序 的排列方式。

主机字节序:

计算机中传输数据用到的字节序。

不确定,有可能是大端字节序,也有可能是小端字节序。

测试本机的字节序程序如下:

#include <stdio.h>
#include <stdbool.h>//C语言中默认不支持bool这几个字符,所以需要用到C99标准定义的这个头文件

bool am_little_endian()
{
    unsigned short i=1;
    return (int)*((char *)(&i)) ? true : false;
}

int main()
{
    if(am_little_endian())
    {
        printf("本机字节序为小段序!\n");
    }else{
        printf("本机字节序为大段序!\n");
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值