C总结-part_4-内存管理

C总结-part_4-内存管理

void func (int a, int b, int c )
{
    // Tips :在这里,变量c是最先创建的,再创建了b,再创建了a;
}
/*
Tips:内存地址分配
栈内存,是从上到下存储的,先分配高地址,再分配低地址,

Tips:全局变量, 作用到整个项目当中,
要使用全局变量,需要在当前文件中进行声明;
extern int a;
*/

———————————————————————————————————————————————

静态static局部变量

int a1 = 10;
static int a2 = 10; // a2分配的内存区域只会分配一次,但是该内存区域可多次赋值;
void func()
{   
    static int a  = 10;
    a++;
}
// 第一次调用func,a为10,func执行完之后a变为11;
// 第二次调用func,a为11,执行完之后为12;
// 不管使用多少次func,变量a所在的内存空间都只分配了一次,只是每次都给这块内存进行了读写;
// static定义的变量只能在当前的文件中使用;
static int a1; 
// 以下两种定义变量的方法是等价的
int a = 10;
auto signed int a = 10;
extern int aa;      // 变量声明,该语句要放在文件的顶部

signed int aa = 10; // 变量定义,此时给变量分配了实际的内存空间

/*
Tips:
定义变量的时候自带声明,但是单独的声明需要置顶,
声明一般不写,如果要写则是放到头文件.h中;
*/
extern int func2();
void main()
{

}
// func2如果放在 main 函数的下面,文件置顶要有func2的声明才能使用。
// func2如果放在 main 函数的上面,则无需对func2进行声明;
int func2()
{
    return 2;
}

各种变量的作用区域

变量定义 作用域
局部变量 int a = 10 作用范围从变量定义到函数结束
全局变量 int a = 10 作用范围为整个项目文件中
static 局部变量 int a = 10 作用范围从变量定义到函数结束
static 全局变量 int a = 10 作用范围为当前文件
static void test() 作用范围为当前文件
void test() 作用范围为整个项目文件中

———————————————————————————————————————————————

内存布局

// size命令可以看到可执行文件各部分的相关信息
size xxx.exe
→ text data bss dec hex filename
可执行文件的组成

- text  代码区
- data  静态数据
- bss   未初始化数据区
- dec   十进制总和
- hex   十六进制总和
- filename

———————————————————————————————————————————————

内存四区

  • 代码区:存放程序指令

  • 数据区:有 静态区 和 全局区
①、初始化的数据
    1、初始化的全局变量
    2、静态全局变量
    3、静态局部变量

②、未初始化的数据
    1、静态局部变量,默认初值为0;
    2、全局变量,默认初值为0;
    3、静态全局变量,默认初值为0;

③、字符串常量
④、define定义的常量

Tips:代码区和数据区有一部分是两个区公用的;

  • 栈区

Tips:栈区各个程序之间不公用,本程序私有;
栈区向下增长,由高地址向低地址增长;

栈区的大小,由OS给每一个程序分配 → 一个程序一个栈区;
死循环、递归等会把栈区占满,因为多次调用会多次压栈;

一般的,windows 1~8M,linux 1~16M

栈区存放的有:局部变量、数组、结构体、指针、枚举类型、函数形参、常量;

注意:
1、C语言将数组放到栈区,而其他的语言一般是放到堆区;
2、C语言将常量放到栈区,而C++将常量放到数据区;


  • 堆区

有音频文件、视频文件、图像、文本、大的数据,都存放到堆区;
Tips:堆区理论上来说空间无限;

———————————————————————————————————————————————

内存分配和释放

  • malloc()
  • free()
// malloc开辟了一个1BYTE的空间,(char *)强制类型转换将这个空间指定为一个char类型
char *p = (char *)malloc(1); 

// ASCII的100是字符"d"
*p = 100;   
free(p);    // 使用完之后,释放p所指向的空间
char *p1 = (char *)malloc(sizeof(char));
char *p2 = (char *)malloc(sizeof(char) * 10);

// Tips:malloc返回的是一个void类型的数据
// 如果malloc一个0,会产生一个不确定的地址
int *p = (int *)malloc(0); 
*p = 100;
free(p); // 此时因为malloc的空间是0,因此这时候会报错;
int *test01()
{   
    // 注意,在函数内申请的局部变量,在test01执行完之后并不会释放
    int *p = (int *)malloc(sizeof(int));
    return p;
}
int main()
{
    int *p = test01();
    p = 10;

    // p在test01执行完之后空间还在,需要手动free,否则可能导致内存泄露;
    free(p);
}
free(p);
free(p); // 多次free同一个指针

// 第一次free的时候,p所在的空间被释放,第二次free相当于在free一个空指针,程序将报错
/*
正确的free内存方式:
①、判断指针是否为空指针;
②、释放完成后,手动将p指针置为NULL,避免野指针出现
*/
if(!p){
    free(p);
}
p = NULL;
int test()
{
    int *p = (int *)malloc(sizeof(int));
    free(p);

    // 注意,free后的p变成了一个野指针,return出去一个野指针,操作p可能程序将会报错
    return p;   
}

free(p);    // 此外,free一个野指针,也会报错;
// 例、在对空间开辟数组进行冒泡排序
int *p = (int *)malloc(sizeof(int) * 10);
sizeof(p); // 此时返回的是int的大小 → 4;
// 申请了一个char类型的空间,但是没有转类型,此时会打印乱码
char *p = malloc(sizeof(char) * 10);
printf("%s
",p);  
char *p1 = "hello world";   // 注意,在这里,p1 p2可能会被认为是相等的
char *p2 = "hello world";
char *p = malloc(sizeof(char) * 100);
strcpy(p,"hello world");

// 一些编译器会将相同的字符串常量认定为是同一个内容,也就是指向的是同一个地址

———————————————————————————————————————————————

内存操作方法

memset()

int *p = (int *)malloc(sizeof(int) * 10); // 这里的p不可更改

/*
第一个参数p,表示目标内存的起始地址
第二个参数0,表示要重置的值
第三个参数,表示从p起始多少个空间的大小需要重置
*/
memset(p,0,40);

// 如果重置的值不为0,会变得莫名其妙;
// 例如下面的这个重置成1,十六进制为01,十进制会变成一个大数;
memset(p,1,40);

// Tips:因此,memset使用时,最好重置为0;
char *p = (char *)malloc(sizeof(char) * 10);
memset(p,65,10);
// char类型,ASCII码65为字符"A",一个char占1B,10个char占用10B;
int arr[] = {1,2,3,4,5,6,7,8,9,10};
memset(arr,0,40);   // 10个int,占40B的大小;

———————————————————————————————————————————————

memcpy()

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = malloc(sizeof(int) * 10);
memcpy(p, arr, 40);
// 注意,两个地址不可以重叠,否则可能会出错
// 在这里,&arr[2]是目标的地址;
memcpy(&arr[2] , arr,20);

Tips:strcpy和memcpy的区别
1、函数参数不同;
2、strcpy用来复制string串,memcpy用来复制一块内存;
3、拷贝结束标志不同,strcpy以为结束,如果string串没有会打印乱码,
memcpy以内存长度结尾(只要内存长度没有错就安全)

———————————————————————————————————————————————

memmove(dest, src, count)

和memcpy功能一样,但是它允许dest和src地址重叠,但是效率更低一些 ———————————————————————————————————————————————

memcmp(a, b)

用于比较a和b的前n个字节

返回结果:
    相等 → 0,
    大于 → >0,
    小于 → <0
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int arr2[5] = {1,2,3,4,5};
memcmp(arr1, arr2, 20); // 比较arr1和arr2的前20个字节;
int *p1 = malloc(sizeof(int) * 10);
char *p2 = malloc(sizeof(char) * 40);
memcpy(p1,"hello",6);
memcpy(p2,"hello",6);

memcmp(p1,p2,6);    // 这里会返回一个0,p1 p2虽然数据类型不同,但是里面的数据是一致的
// 例、求出三名学生的功课成绩并排序;
// Tips:用堆空间实现,arr[3][3]
int **p = (int *)malloc(sizeof(int *) *3);
p[0] = (int *)malloc(sizeof(int *) *3);
......

———————————————————————————————————————————————