01. 栈和堆的区别
C 语言内存分四大区:栈、堆、全局区、代码区。栈是编译器自动管理的,函数调用时分配局部变量,返回时自动释放,非常快但空间有限。堆是程序员手动管理的,用 malloc 申请 free 释放,空间大但容易内存泄漏。理解栈和堆是 C 程序员的基本功。
c
#include <stdio.h>
#include <stdlib.h>
int global_var = 100; // 全局区
int main() {
int stack_var = 42; // 栈上分配
// 堆上分配
int *heap_var = (int*)malloc(sizeof(int));
*heap_var = 99;
printf("栈变量: %d\n", stack_var);
printf("堆变量: %d\n", *heap_var);
printf("全局变量: %d\n", global_var);
free(heap_var); // 别忘了释放!
return 0;
}malloc 和 free 必须配对,忘 free 就内存泄漏,free 两次就 double free 崩溃。
02. malloc、calloc、realloc、free
malloc 分配指定字节数的内存,不初始化(里面是垃圾值)。calloc 分配并初始化为零,适合数组。realloc 调整已分配的内存大小(扩容或缩容),可能移动内存块地址。free 释放内存,指针 free 后最好置为 NULL 防止野指针。
c
#include <stdio.h>
#include <stdlib.h>
int main() {
// malloc: 分配不初始化
int *p1 = (int*)malloc(5 * sizeof(int));
// 此时 p1[0] 的值不确定(垃圾值)
// calloc: 分配并初始化为0
int *p2 = (int*)calloc(5, sizeof(int));
// p2[0] 到 p2[4] 都是 0
// realloc: 扩容
int *p3 = (int*)malloc(3 * sizeof(int));
p3 = (int*)realloc(p3, 10 * sizeof(int));
// 释放
free(p1); free(p2); free(p3);
p1 = p2 = p3 = NULL; // 好习惯
return 0;
}malloc 的参数可以算 sizeof:malloc(num * sizeof(int)),不管什么平台都不会出错。
03. 动态分配数组和结构体
用 malloc 分配数组比定长数组灵活,运行时才知道需要多大。动态分配结构体也很常见,比如链表的节点、树的节点。分配时别忘了 sizeof,二维数组可以用指针数组实现,每一行单独 malloc,行之间内存不连续但灵活。
c
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
typedef struct Node Node;
Node* create_node(int data) {
Node *node = (Node*)malloc(sizeof(Node));
node->data = data;
node->next = NULL;
return node;
}
int main() {
int n = 5;
int *arr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) arr[i] = i * 10;
// 二维数组(指针数组)
int rows = 3, cols = 4;
int **matrix = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
// 使用...
matrix[1][2] = 99;
// 释放二维数组
for (int i = 0; i < rows; i++) free(matrix[i]);
free(matrix); free(arr);
return 0;
}04. 常见内存错误
C 语言最折磨人的就是内存错误,没有垃圾回收全靠自己管。常见五种:忘记 free(内存泄漏)、double free(重复释放崩溃)、野指针(用已经 free 了的指针)、越界访问(数组索引超出范围破坏其他数据)、缓冲区溢出(strcpy 拷贝超长字符串覆盖了返回地址)。valgrind 是查内存问题的神器。
c
#include <stdlib.h>
#include <string.h>
int main() {
// 1. 内存泄漏 - 忘了 free
int *p1 = malloc(100);
// 没有 free(p1) - 泄漏!
// 2. 野指针 - free 后还用
int *p2 = malloc(100);
free(p2);
*p2 = 10; // 危险!野指针
// 3. 缓冲区溢出
char buf[10];
strcpy(buf, "这个字符串太长了超过了10个字节"); // 溢出!
// 4. 越界访问
int arr[5];
arr[10] = 100; // 越界到未知区域
return 0;
}内存泄漏在小程序里不明显,但服务端长期运行会慢慢耗尽内存导致崩溃。每次 malloc 都要想清楚谁负责 free。
05. 智能的内存管理实践
好的习惯能避免大部分内存问题:malloc 后立刻检查是否返回 NULL(分配失败);free 后把指针置 NULL;定义一个 xxx_free 函数集中释放资源;使用内存池减少碎片;避免深层嵌套指针,能栈分配的不用堆。配合 valgrind --leak-check=full 工具检查泄漏。
c
#include <stdio.h>
#include <stdlib.h>
struct User {
char *name;
int age;
};
typedef struct User User;
User* user_new(const char *name, int age) {
User *u = malloc(sizeof(User));
if (u == NULL) return NULL; // 检查分配失败
u->name = malloc(strlen(name) + 1);
if (u->name == NULL) { free(u); return NULL; }
strcpy(u->name, name);
u->age = age;
return u;
}
void user_free(User *u) {
if (u == NULL) return;
free(u->name);
free(u);
}
int main() {
User *u = user_new("小明", 18);
if (u == NULL) return 1;
printf("%s, %d\n", u->name, u->age);
user_free(u);
// 检查: valgrind --leak-check=full ./program
return 0;
}每个数据结构写一对 new/free 函数,养成习惯后内存管理会清晰很多。
知识测验
第 1/4 题正确 0
malloc 和 calloc 的主要区别?
下一节
下一节 C++ 基础