内存分配
内存池的概念:
操作系统把一些内存放入内存池供动态分配使用,没有了系统再放进去。malloc的空间都在内存池里,释放了也会返回到内存池供后续使用,减少了系统调用的次数,直到程序结束才回收。所以free后内存占用没有明显减少是因为这个,作用:提高性能、效率
#include <stdio.h>
#include <stdlib.h>
int main() {
// 从内存池中分配100字节
char *ptr = (char*)malloc(100);
if(ptr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用内存
strcpy(ptr, "示例字符串");
printf("内容: %s\n", ptr);
// 释放内存,返回内存池
free(ptr);
ptr = NULL; // 防止悬挂指针
return 0;
}
注意事项
- 分配的成功或失败要判断==NULL
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*)malloc(10 * sizeof(int));
if(arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用内存
free(arr);
return 0;
}
- 保证malloc和free成对使用
- 申请和释放次数匹配,将释放后赋为NULL, 避免悬挂指针
- 内存分配的常见错误
- 3个错误(同一个动态空间多次free。泄露。只释放一部分。返回栈地址。) free释放的是指针指向的空间,不是释放指针变量的地址,所以最后赋空指针
//只释放一部分错误实例
#include <stdio.h>
#include <stdlib.h>
int main() {
struct s {
char *name;
int age;
} *p;
p = (struct s*)malloc(sizeof(struct s));
if(p == NULL) return 1;
p->name = (char*)malloc(50);
if(p->name == NULL) return 1;
strcpy(p->name, "Alice");
p->age = 30;
// 只释放结构体本身,未释放name成员
free(p);
p = NULL;
return 0;
}
//返回栈地址错误示例
#include <stdio.h>
#include <stdlib.h>
int* invalid_return() {
int a = 10;
return &a; // 错误: 返回栈地址
}
int main() {
int *ptr = invalid_return();
printf("值: %d\n", *ptr); // 未定义行为
return 0;
}
-
分配0字节空间会怎么样:可以分配但不能用,尺子上的刻度,2个点才能连成线
4.动态分配结构体时指针成员要单独开辟和释放空间(必须先释放成员空间再释放结构体空间
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person {
char *name;
int age;
};
int main() {
struct Person *p = (struct Person*)malloc(sizeof(struct Person));
if(p == NULL) {
printf("内存分配失败\n");
return 1;
}
// 单独为指针成员分配内存
p->name = (char*)malloc(50);
if(p->name == NULL) {
free(p);
return 1;
}
strcpy(p->name, "张三");
p->age = 25;
printf("姓名: %s, 年龄: %d\n", p->name, p->age);
// 释放指针成员空间
free(p->name);
p->name = NULL;
// 释放结构体空间
free(p);
p = NULL;
return 0;
}
总结:动态开辟内存时结构体要为指针成员单独分配空间
结构体本身的成员是有被编译器分配内存的,给这个结构体开辟动态内存只是换了一个空间而已,但成员是指针,要明白给指针开辟内存和给指针的指向分配空间是两回事。
错误例子
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct s {
char *name; // 没有初始化
} p;
int main(){
p = (struct s*)malloc(40);
strcpy(p->name, "lc"); // 未分配name的指向空间,name仍然是野指针
return 0;
}
纠正:
struct s {
char *name;
} p;
int main(){
p = (struct s*)malloc(sizeof(struct s));
if(p == NULL) return 1;
p->name = (char*)malloc(50);
if(p->name == NULL) {
free(p);
return 1;
}
strcpy(p->name, "lc");
printf("姓名: %s\n", p->name);
// 释放内存
free(p->name);
p->name = NULL;
free(p);
p = NULL;
return 0;
}
内存其他和扩展:
资源泄露:
- 包括内存泄露和句柄泄露。句柄泄露就是关于文件之类的泄露的(还有操作系统、数据库连接、进程、线程等)。文件打开了没有关闭,创建使用完后没有销毁。
- 防止内存耗尽
- 其实内存不可能耗尽。栈虽然效率高但容量有限。少用静态区的变量
指针:
1.高级指针的应用场景
指针数组
一般不用于存放任意类型的数据的地址,一般用于结构体
#include <stdio.h>
struct Student {
char name[50];
int age;
};
int main() {
struct Student s1 = {"张三", 20};
struct Student s2 = {"李四", 22};
struct Student s3 = {"王五", 21};
struct Student *ptrArray[3] = {&s1, &s2, &s3};
for(int i = 0; i < 3; i++) {
printf("姓名: %s, 年龄: %d\n", ptrArray[i]->name, ptrArray[i]->age);
}
return 0;
}
数组指针
#include <stdio.h>
int main() {
int arr[3][4] = {0};
int (*p)[4] = arr; // p指向一个包含4个int的数组
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 4; j++) {
p[i][j] = i * 4 + j;
printf("%d ", p[i][j]);
}
printf("\n");
}
return 0;
}
函数指针
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int (*func_ptr)(int, int) = add;
int result = func_ptr(5, 3);
printf("结果: %d\n", result);
return 0;
}
函数指针数组
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
int (*func_arr[2])(int, int) = {add, subtract};
printf("加法: %d\n", func_arr[0](10, 5));
printf("减法: %d\n", func_arr[1](10, 5));
return 0;
}
二级指针
#include <stdio.h>
#include <stdlib.h>
int main() {
int **dp = (int**)malloc(2 * sizeof(int*));
if(dp == NULL) return 1;
for(int i = 0; i < 2; i++) {
dp[i] = (int*)malloc(3 * sizeof(int));
if(dp[i] == NULL) return 1;
for(int j = 0; j < 3; j++) {
dp[i][j] = i * 3 + j;
}
}
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 3; j++) {
printf("%d ", dp[i][j]);
}
printf("\n");
}
// 释放内存
for(int i = 0; i < 2; i++) {
free(dp[i]);
}
free(dp);
return 0;
}
2.指针指向字符串的含义
是字符串字面量
#include <stdio.h>
int main() {
const char *str = "Hello, World!"; // 字符串字面量
printf("字符串: %s\n", str);
return 0;
}
字符数组是有内存空间分配的连续的,所以是一个个字符组成的数组自然是变量
而字符指针只是指向字符串,没有分配空间,所以这个字符串是已经在编译器分配好的常量
重点:3.指针步长及指针数组的移动
指针移动的步长取决于类型的大小。 指针类型的大小决定指针步长和指针解引用访问空间的大小
#include <stdio.h>
int main() {
int a[3][4] = {
{0,1,2,3}, {4,5,6,7}, {8,9,10,11}};
int (*p)[4] = a; // 指向二维数组中的一维数组
p = p + 1; // 移动到第二行
printf("第二行第一个元素: %d\n", (*p)[0]); // 输出4
struct MyStruct {
int x;
double y;
} s[2] = {
{1, 2.0}, {3, 4.0}};
struct MyStruct *sp = s;
sp = sp + 1; // 移动到第二个结构体
printf("结构体y: %.1f\n", sp->y); // 输出4.0
return 0;
}
指针数组移动:因为数组也是类型,所以按类型来走步长
int a[3][4不可省略] = {
{0}};
int (*p)[4不可省略] = &a;
p+1等价于a[0]+1*[4]等于a[1]跳了4个元素,即到了第二行
结构体指针+1则是跳过整个结构体
4.解引用含义
解引用后控制权就不再是指针,而是指向的变量
-> 等价于 (*).
#include <stdio.h>
int main() {
int num = 10;
int *ptr = #
printf("指针指向的值: %d\n", *ptr); // 解引用访问值
*ptr = 20; // 通过解引用修改值
printf("修改后的值: %d\n", num);
// 等价于 (*ptr).Var
typedef struct {
int Var;
} MyStruct;
MyStruct s;
MyStruct *sp = &s;
sp->Var = 30; // 等价于 (*sp).Var = 30;
printf("结构体Var: %d\n", s.Var);
return 0;
}
5.传值和传址的区别
传值:临时拷贝
传址:间接达到本体效果,引用才是本体
#include <stdio.h>
// 值传递
void increment(int a) {
a++;
}
// 地址传递
void increment_address(int *a) {
(*a)++;
}
int main() {
int x = 5;
increment(x);
printf("值传递后x: %d\n", x); // 输出5
increment_address(&x);
printf("地址传递后x: %d\n", x); // 输出6
return 0;
}
6.void指针的含义和规则
- void能接受任何,但别人接受不了void
- void*不是通用指针类型,是保存其类型的指针
- void指针不能++——、解引用,除非强转
- void无类型,不知道变量的内存放在哪里,所以不能创建变量实例,void代表一种抽象的类型,理解了面向对象的抽象基类的概念就容易理解void了
#include <stdio.h>
int main() {
int a = 10;
double b = 3.14;
void *ptr;
ptr = &a; // 可以指向任何类型
printf("整数: %d\n", *(int*)ptr);
ptr = &b;
printf("浮点数: %.2f\n", *(double*)ptr);
// void指针不能直接解引用或进行算术运算
// printf("%d\n", *ptr); // 错误
// ptr++; // 错误
void a;//也是是错的
return 0;
}
7. 0指针的作用和规则
- NULL只能用于指针,相当于 (void*)0,有时候也是宏定义的0,所以严格意义上可能会混淆整型0和指针类型。
- 尺子上刻度为0的地址,因此不能被使用
- 0地址是空指针。
作用:初始化为0,不指向任何,可防止悬挂或野指针
#include <stdio.h>
int main() {
int *ptr = NULL; // 初始化为空指针
if(ptr == NULL) {
printf("指针未指向任何地址\n");
}
// 避免悬挂指针
// ptr = (int*)0x11; // 需要确保地址合法
return 0;
}
8.野指针和悬挂指针
野指针:指向和使用未知的未定义的。发生在(未初始化,指针越界访问)
悬挂指针:指向和使用已经销毁的。相当于宾馆房卡过期了还赖在宾馆住,发生在(指针被free后没赋予空指针,函数返回栈地址)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 野指针
void wild_pointer() {
int *ptr; // 未初始化
*ptr = 10; // 未定义行为
}
// 悬挂指针
void suspended_pointer() {
int *ptr = (int*)malloc(sizeof(int));
if(ptr == NULL) return;
*ptr = 20;
free(ptr);
// 悬挂指针,没有赋NULL
// printf("%d\n", *ptr); // 未定义行为
}
int main() {
// wild_pointer(); // 会导致程序崩溃
suspended_pointer();
return 0;
}