参考的博文来自 :点击打开链接
自我总结加整理:
一.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;
}
本文详细介绍了C程序在内存中的布局方式,包括BSS段、数据段、代码段、堆和栈的作用及特点。此外,还深入探讨了递归的工作原理,包括递归的三个阶段以及如何通过实例来理解递归过程。
3432

被折叠的 条评论
为什么被折叠?



