操作系统把一些内存放入内存池供动态分配使用,没有了系统再放进去。malloc的空间都在内存池里,释放了也会返回到内存池供后续使用,减少了系统调用的次数,直到程序结束才回收。所以free后内存占用没有明显减少是因为这个,作用:提高性能、效率
#include
#include
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;
}
#include
#include
int main() {
int *arr = (int*)malloc(10 * sizeof(int));
if(arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 使用内存
free(arr);
return 0;
}
//只释放一部分错误实例
#include
#include
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
#include
int* invalid_return() {
int a = 10;
return &a; // 错误: 返回栈地址
}
int main() {
int *ptr = invalid_return();
printf("值: %d\n", *ptr); // 未定义行为
return 0;
}
分配0字节空间会怎么样:可以分配但不能用,尺子上的刻度,2个点才能连成线
#include
#include
#include
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
#include
#include
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;
}
资源泄露:
指针数组
一般不用于存放任意类型的数据的地址,一般用于结构体
#include
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
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
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
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
#include
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;
}
是字符串字面量
#include
int main() {
const char *str = "Hello, World!"; // 字符串字面量
printf("字符串: %s\n", str);
return 0;
}
字符数组是有内存空间分配的连续的,所以是一个个字符组成的数组自然是变量
而字符指针只是指向字符串,没有分配空间,所以这个字符串是已经在编译器分配好的常量
指针移动的步长取决于类型的大小。 指针类型的大小决定指针步长和指针解引用访问空间的大小
#include
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则是跳过整个结构体
解引用后控制权就不再是指针,而是指向的变量
-> 等价于 (*).
#include
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;
}
传值:临时拷贝
传址:间接达到本体效果,引用才是本体
#include
// 值传递
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;
}
#include
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;
}
作用:初始化为0,不指向任何,可防止悬挂或野指针
#include
int main() {
int *ptr = NULL; // 初始化为空指针
if(ptr == NULL) {
printf("指针未指向任何地址\n");
}
// 避免悬挂指针
// ptr = (int*)0x11; // 需要确保地址合法
return 0;
}
8.野指针和悬挂指针
野指针:指向和使用未知的未定义的。发生在(未初始化,指针越界访问)
悬挂指针:指向和使用已经销毁的。相当于宾馆房卡过期了还赖在宾馆住,发生在(指针被free后没赋予空指针,函数返回栈地址)
#include
#include
#include
// 野指针
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;
}