lesson8 内存管理
2021/12/15 7:19:37
本文主要是介绍lesson8 内存管理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
8. 内存管理
8.1 作用域
C语言变量的作用域分为:
- 代码块的作用域
- 函数作用域
- 文件作用域
#include <stdio.h> void fun1(int a) { int b = 20; } int main() { //定义变量 局部变量:在函数内部的变量,可以用auto来修饰,与全局变量作区分 //作用域:在函数内部 //声明周期:从创建到函数结束 auto int a = 1; printf("%d\n", a); return 0; }
函数内部不重新定义就会使用全局变量,可以直接修改全局变量的值
全局变量:在函数外定义,可被本文件及其他文件中的函数使用,若其他文件中的函数调用此变量,须用extern声明
不同文件中的全局变量名不可以重复,但可以和局部变量重名
#include <stdio.h> int a = 10;//全局变量 在函数外部定义的变量,储存在数据区 //全局变量可以和局部变量重名,使用的时候采用就进原则 //作用域:整个项目中所有的文件,如果在其他的文件中使用,需要申明 //声明周期是从程序创建到程序销毁。 void fun02(int a) //在自己调试的时候发现,不传参就可以直接改变全局变量的值,在返回main之后的a被改成了fun02中的值 { a = 100; printf("%d\n", a); } int main() { int a = 123; //匿名内部类 { int a = 456; printf("%d\n", a); } printf("%d\n", a); fun02(a); printf("%d\n", a); return 0; }
静态局部变量:
#include <stdio.h> void fun04() { int b = 10; b++; printf("%d\t", b); } void fun05() { static int b = 10; //静态局部变量只会初始化一次,可以多次赋值 //在数据区进行存储,作用域、:只能在函数内部使用,生命周期:程序创建到销毁 b++; printf("%d\t", b); } int main() { //static int b = 10;//静态局部变量 //printf("%d\n", b); for (size_t i = 0; i < 10; i++) { fun04(); } putchar(10); for (size_t i = 0; i < 10; i++) { fun05(); } return 0; }
输出:
11 11 11 11 11 11 11 11 11 11
11 12 13 14 15 16 17 18 19 20
静态全局变量:
#include <stdio.h> static int c = 10; //静态全局变量 //作用域:可以在本文件中使用,不可以在其他文件中使用 //声明周期:程序创建到销毁 void fun06() { c = 20; printf("%d\n", c); } int main() { printf("%d\n", c); fun06(); return 0; }
10
20
全局函数和静态函数
在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。
对于不同文件中的staitc函数名字可以相同。
#include <stdio.h> static void fun07() { printf("hello world"); } int main() { fun07(); }
输出:hello world
类型 | 作用域 | 生命周期 |
---|---|---|
auto变量 | 一对{}内 | 当前函数 |
static局部变量 | 一对{}内 | 整个程序运行期 |
extern变量 | 整个程序 | 整个程序运行期 |
static全局变量 | 当前文件 | 整个程序运行期 |
extern函数 | 整个程序 | 整个程序运行期 |
static函数 | 当前文件 | 整个程序运行期 |
register变量 | 一对{}内 | 当前函数 |
全局变量 | 整个程序 | 整个程序运行期 |
8.2 内存布局
安全的常量:存储区域为数据区常量区
#include <stdio.h> //未初始化全局变量 int a1; //初始化全局变量 int b1 = 10; //未初始化静态全局变量 static int c1; //初始化的静态全局变量 static int d1 = 10; int main() { int e1 = 10; static int f1; static int h1 = 10; char* p = "helloworld";//字符串常量 int arr[] = { 1,2,3,4 }; int* pp = arr; printf("未初始化的全局变量:%p\n", &a1); printf("初始化的全局变量:%p\n", &b1); printf("未初始化的静态全局变量:%p\n", &c1); printf("初始化的静态全局变量:%p\n", &d1); printf("局部变量:%p\n", &e1); printf("未初始化的静态局部变量:%p\n", &f1); printf("初始化的静态局部变量:%p\n", &h1); printf("字符串常量:%p\n", p); printf("数组:%p\n", arr); printf("指针变量:%p\n", pp); printf("指针地址:%p\n",&pp); return 0; }
未初始化的全局变量:00B0A164
初始化的全局变量:00B0A004
未初始化的静态全局变量:00B0A158
初始化的静态全局变量:00B0A008
局部变量:00C0F728
未初始化的静态局部变量:00B0A15C
初始化的静态局部变量:00B0A00C
字符串常量:00B07B44
数组:00C0F704
指针变量:00C0F704
指针地址:00C0F6F8
C代码经过预处理、编译、汇编、链接4步后生成一个可执行程序。
通过上图可以得知,在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把data和bss合起来叫做静态区或全局区)。
代码区
存放 CPU 执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。
全局初始化数据区/静态数据区(data段)
该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
未初始化数据区(又叫 bss 区)
存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为 0 或者空(NULL)。
程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。
栈区
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
堆区(heap)
堆是一个大容器,它的容量要远远大于栈,但没有栈那样先进后出的顺序。用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
存储是从高地址往低地址进行存储的
#include <stdio.h> int main() { //栈区大小 int arr[820000] = { 0 }; return 0; }
调试直接报错:0x008E2477 处有未经处理的异常(在 day08.exe 中): 0xC00000FD: Stack overflow (参数: 0x00000000, 0x00E82000)。
堆区最大内存就是1M
堆区内存分配和释放
-
malloc() : #include <stdlib.h>
形式:void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
-
free() :
库:#include <stdlib.h>
形式:void free(void *ptr);功能:放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。对同一内存空间多次释放会出错。
#include <stdio.h> #include<stdlib.h> int main() { //栈区大小 //int arr[820000] = { 0 }; //开辟堆空间存储数据 int* p = (int*)malloc(sizeof(int)); printf("%p\n", p); //使用堆空间 *p = 123; printf("%d\n", *p); //释放堆空间 free(p); printf("%p\n", p); //释放完p之后变成野指针,操作它可能会报错,也可能不报 *p = 456; printf("%d\n", *p); p = NULL; return 0; }
输出:
00F966C8
123
00F966C8
456
开辟和释放的指针必须是同一个,如果在过程中改变了指针的值,必须要改回去才能释放
内存操作函数
-
memset()
include <string.h>
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入.
#include <stdio.h> #include<stdlib.h> #include<string.h> int main() { int* p = (int*)malloc(sizeof(int) * 10);//开辟40个空间,存储10个整形变量 //memset()可以重置内存空间的值 memset(p, 0, 40);//单位是字节 for (int i = 0; i < 10; i++) { printf("%d\n", p[i]); } free(p); return 0; }
0
0
0
0
0
0
0
0
0
0
四位存储一个数,八位是一个字节,int一共四个字节,如果memset把所有字节变为1,则存储的数就是01010101
-
memcpy()
include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。memcpy和strcpy的区别:内存拷贝的内容和字节有关,与拷贝内容无关,
int main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = (int*)malloc(sizeof(int) * 10); memcpy(p, arr, 40);//内存拷贝 for (int i = 0; i < 10; i++) { printf("%d\t", p[i]); } free(p); return 0; }
输出:1 2 3 4 5 6 7 8 9 10
int main() { int arr[]= { 1,2,3,4,5,6,7,8,9,10 }; memcpy(&arr[5], &arr[3], 20); for (int i = 0; i < 10; i++) { printf("%d\t", arr[i]); } return 0; }
输出1 2 3 4 5 4 5 6 7 8
dest和src所指的内存空间不可重叠,可能会导致程序报错
-
memmove()
memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。
- memcmp()
include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节
返回值: 相等:=0 大于:>0 小于:<0
int main() { int arr1[] = { 1,2,3,4,5,6,7,8,9,10 }; int arr2[] = { 1,2,3,4,5 }; int value = memcmp(arr1, arr2, 20); int value2=memcmp(arr1, arr2, 28); printf("%d\n", value); printf("%d\n", value2); char arr3[] = "hello\0 world"; char arr4[] = "hello\0 world"; int value3 = memcmp(arr3, arr4, 10);//strcmp只能比较\0之前的内容,memcmp可以比较后面的 printf("%d\n", value3); return 0; }
输出:
0
-1
0
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include<stdlib.h> #include<string.h> int main() { //数组下标越界 //char ch[] = "hello world"; char* p = (char*)malloc(sizeof(char) * 11); strcpy(p, "hello world"); printf("%s\n", p); //free(p); 这里释放程序会卡死,因为创建了11个字节,但是存进去了12个 return 0; }
输出:hello world
int main() { int* p = (int*)malloc(0);//类似野指针,没有开辟空间,操作不了 printf("%p\n", p); *p = 100; printf("%d\n", *p); //free(p); 会因为越界卡死 return 0; }
输出:005D6808
100
开辟内存和空间要对应,不能越界,所以平时写的时候尽量写成malloc(sizeof(xxx)*xx)的形式
int main() { int* p = (int*)malloc(sizeof(int) * 11); free(p);//堆空间不允许多次释放 p = NULL;//释放空指针是没问题的 free(p);//空指针允许多次释放 free(p); free(p); free(p); free(p); free(p); return 0; }
输出空的
void fun08(int *p) { p = (int*)malloc(sizeof(int) * 10); } int main() { int* p = NULL; fun08(p); for (int i = 0; i < 10; i++) { p[i] = i; } free(p); return 0; }
这样写传递过去的p和fun08函数里面p是同级的,使用完以后就会被销毁,相当于值传递。后面操纵的都是空指针,会报错
void fun08(int **p) { *p = (int*)malloc(sizeof(int) * 10); printf("形参:%p\n", p); } int* fun09() { int* p = (int*)malloc(sizeof(int) * 10); return p; } int main() { int* p = NULL; fun08(&p);//地址传递 printf("实参:%p\n", p); for (int i = 0; i < 10; i++) { p[i] = i; } for (int i = 0; i < 10; i++) { printf("%d\t", p[i]); } free(p); return 0; }
输出:
形参:0095FB08
实参:00BA6788
0 1 2 3 4 5 6 7 8 9
这篇关于lesson8 内存管理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-09聊聊如何通过arthas-tunnel-server来远程管理所有需要arthas监控的应用
- 2024-05-09log4j2这么配就对了
- 2024-05-09nginx修改Content-Type
- 2024-05-09Redis多数据源,看这篇就够了
- 2024-05-09Google Chrome驱动程序 124.0.6367.62(正式版本)去哪下载?
- 2024-05-09有没有大佬知道这种数据应该怎么抓取呀?
- 2024-05-09这种运行结果里的10.100000001,怎么能最快改成10.1?
- 2024-05-09企业src漏洞挖掘-有意思的命令执行
- 2024-05-08阿里云域名注册流程,分享给第一次购买域名的新手站长!
- 2024-05-082024年,行业变动下的程序员应该首先学习哪种编程语言?