编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • C语言入门精通

    • 入门教程

      • README
      • 基础语法
      • 数据类型
      • 运算符
      • 循环和选择
      • 输入输出
      • 函数
      • 指针
      • 数组和容器
      • 类和内存
        • 9.1 面向对象设计
          • 9.1.0 没有类概念
          • 9.1.1 结构体模拟类
          • 9.1.2 模拟成员方法
          • 9.1.3 模拟继承
          • 9.1.4 模拟多态
          • 9.1.5 综合案例与思考
          • 9.2.1 栈(Stack)
          • 9.2.2 堆(Heap)
          • 9.2.3 全局/静态区
          • 9.2.4 常量区
          • 9.2.5 代码区
          • 9.2.6 综合案例与思考
          • 9.3.1 malloc
          • 9.3.2 calloc
          • 9.3.3 realloc
          • 9.3.4 free
          • 9.3.5 综合案例与思考
          • 9.5.1 内存泄漏
          • 9.5.2 野指针
          • 9.5.3 双重释放
          • 9.5.4 越界访问
          • 9.5.5 最佳实践
        • 9.6 综合案例实践
          • 9.6.1 动态数组
          • 9.6.2 二维动态数组
      • 流与文件
      • 结构体
      • 线程和锁
      • 预处理器
      • 高级数据
    • 综合案例

    • 专栏博客

    • 标准集库

  • Cpp入门到精通

  • Java入门精通

  • Go入门到精通

  • JavaScript入门

  • CodeX
  • C语言入门精通
  • 入门教程
杨充
2025-07-09
目录

类和内存

# 09.类和内存

# 目录介绍

  • 9.1 面向对象设计
    • 9.1.0 没有类概念
    • 9.1.1 结构体模拟类
    • 9.1.2 模拟成员方法
    • 9.1.3 模拟继承
    • 9.1.4 模拟多态
    • 9.1.5 综合案例与思考
  • 9.2 内存模型
    • 9.2.1 栈(Stack)
    • 9.2.2 堆(Heap)
    • 9.2.3 全局/静态区
    • 9.2.4 常量区
    • 9.2.5 代码区
    • 9.2.6 综合案例与思考
  • 9.3 动态内存管理
    • 9.3.1 malloc
    • 9.3.2 calloc
    • 9.3.3 realloc
    • 9.3.4 free
    • 9.3.5 综合案例与思考
  • 9.5 常见内存问题
    • 9.5.1 内存泄漏
    • 9.5.2 野指针
    • 9.5.3 双重释放
    • 9.5.4 越界访问
    • 9.5.5 最佳实践
  • 9.6 综合案例实践
    • 9.6.1 动态数组
    • 9.6.2 二维动态数组

# 9.1 面向对象设计

# 9.1.0 没有类概念

C 语言没有类的概念,但可以通过结构体和函数指针模拟面向对象的特性。

  • 使用结构体封装数据,函数指针模拟成员方法。
  • 通过嵌套结构体模拟继承,通过函数指针和类型转换模拟多态。

虽然 C 语言不是面向对象的语言,但通过这些技巧可以实现类似的功能。

# 9.1.1 结构体模拟类

结构体可以用来封装数据,类似于类中的成员变量。但是这种类只有成员变量……

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个结构体模拟类
typedef struct {
    char name[50];
    int age;
} Person;

// 构造函数
Person* Person_create(const char *name, int age) {
    Person *p = (Person *)malloc(sizeof(Person));
    if (p != NULL) {
        strcpy(p->name, name);
        p->age = age;
    }
    return p;
}

// 成员函数
void Person_introduce(Person *p) {
    printf("Hello, my name is %s and I am %d years old.\n", p->name, p->age);
}

// 析构函数
void Person_destroy(Person *p) {
    free(p);
}

int main() {
    // 创建对象
    Person *person = Person_create("Alice", 25);
    // 调用成员函数
    Person_introduce(person);
    // 销毁对象
    Person_destroy(person);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 9.1.2 模拟成员方法

通过将函数指针作为结构体的成员,可以模拟类的成员方法。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义一个结构体模拟类
typedef struct {
    char name[50];
    int age;
    void (*introduce)(struct Person *); // 函数指针
} Person;

// 成员函数
void Person_introduce(Person *p) {
    printf("Hello, my name is %s and I am %d years old.\n", p->name, p->age);
}

// 构造函数
Person* Person_create(const char *name, int age) {
    Person *p = (Person *)malloc(sizeof(Person));
    if (p != NULL) {
        strcpy(p->name, name);
        p->age = age;
        p->introduce = Person_introduce; // 初始化函数指针
    }
    return p;
}

// 析构函数
void Person_destroy(Person *p) {
    free(p);
}

int main() {
    // 创建对象
    Person *person = Person_create("Bob", 30);
    // 调用成员函数
    person->introduce(person);
    // 销毁对象
    Person_destroy(person);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 9.1.3 模拟继承

通过嵌套结构体,可以模拟继承的特性。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 基类
typedef struct {
    char name[50];
    int age;
} Person;

// 派生类
typedef struct {
    Person base; // 继承基类
    char job[50];
} Employee;

// 基类构造函数
Person* Person_create(const char *name, int age) {
    Person *p = (Person *)malloc(sizeof(Person));
    if (p != NULL) {
        strcpy(p->name, name);
        p->age = age;
    }
    return p;
}

// 派生类构造函数
Employee* Employee_create(const char *name, int age, const char *job) {
    Employee *e = (Employee *)malloc(sizeof(Employee));
    if (e != NULL) {
        strcpy(e->base.name, name);
        e->base.age = age;
        strcpy(e->job, job);
    }
    return e;
}

// 基类成员函数
void Person_introduce(Person *p) {
    printf("Hello, my name is %s and I am %d years old.\n", p->name, p->age);
}

// 派生类成员函数
void Employee_introduce(Employee *e) {
    Person_introduce((Person *)e); // 调用基类方法
    printf("I work as a %s.\n", e->job);
}

// 析构函数
void Person_destroy(Person *p) {
    free(p);
}

int main() {
    // 创建派生类对象
    Employee *employee = Employee_create("Charlie", 35, "Engineer");

    // 调用派生类成员函数
    Employee_introduce(employee);

    // 销毁对象
    Person_destroy((Person *)employee);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# 9.1.4 模拟多态

通过函数指针和类型转换,可以模拟多态的特性。

示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 基类
typedef struct {
    void (*introduce)(void *); // 函数指针
} Animal;

// 派生类:Dog
typedef struct {
    Animal base; // 继承基类
    char name[50];
} Dog;

// 派生类:Cat
typedef struct {
    Animal base; // 继承基类
    char name[50];
} Cat;

// Dog 的成员函数
void Dog_introduce(void *dog) {
    Dog *d = (Dog *)dog;
    printf("Woof! My name is %s.\n", d->name);
}

// Cat 的成员函数
void Cat_introduce(void *cat) {
    Cat *c = (Cat *)cat;
    printf("Meow! My name is %s.\n", c->name);
}

// Dog 构造函数
Dog* Dog_create(const char *name) {
    Dog *d = (Dog *)malloc(sizeof(Dog));
    if (d != NULL) {
        strcpy(d->name, name);
        d->base.introduce = Dog_introduce; // 初始化函数指针
    }
    return d;
}

// Cat 构造函数
Cat* Cat_create(const char *name) {
    Cat *c = (Cat *)malloc(sizeof(Cat));
    if (c != NULL) {
        strcpy(c->name, name);
        c->base.introduce = Cat_introduce; // 初始化函数指针
    }
    return c;
}

// 析构函数
void Animal_destroy(Animal *a) {
    free(a);
}

int main() {
    // 创建对象
    Dog *dog = Dog_create("Buddy");
    Cat *cat = Cat_create("Whiskers");

    // 调用多态方法
    Animal *animals[] = {(Animal *)dog, (Animal *)cat};
    for (int i = 0; i < 2; i++) {
        animals[i]->introduce(animals[i]);
    }

    // 销毁对象
    Animal_destroy((Animal *)dog);
    Animal_destroy((Animal *)cat);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75

# 9.1.5 综合案例与思考

综合案例:用C语言实现面向对象的形状系统

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

// "基类":Shape
typedef struct Shape {
    const char *type;
    double (*area)(struct Shape *self);
    double (*perimeter)(struct Shape *self);
    void (*print)(struct Shape *self);
} Shape;

// "派生类":Circle
typedef struct {
    Shape base;
    double radius;
} Circle;

// "派生类":Rectangle
typedef struct {
    Shape base;
    double width, height;
} Rectangle;

// Circle的方法实现
double circle_area(Shape *self) {
    Circle *c = (Circle *)self;
    return 3.14159 * c->radius * c->radius;
}
double circle_perimeter(Shape *self) {
    Circle *c = (Circle *)self;
    return 2 * 3.14159 * c->radius;
}
void circle_print(Shape *self) {
    Circle *c = (Circle *)self;
    printf("圆(半径=%.1f): 面积=%.2f, 周长=%.2f\n",
           c->radius, self->area(self), self->perimeter(self));
}

// Rectangle的方法实现
double rect_area(Shape *self) {
    Rectangle *r = (Rectangle *)self;
    return r->width * r->height;
}
double rect_perimeter(Shape *self) {
    Rectangle *r = (Rectangle *)self;
    return 2 * (r->width + r->height);
}
void rect_print(Shape *self) {
    Rectangle *r = (Rectangle *)self;
    printf("矩形(%.1f x %.1f): 面积=%.2f, 周长=%.2f\n",
           r->width, r->height, self->area(self), self->perimeter(self));
}

// 构造函数
Circle *Circle_new(double radius) {
    Circle *c = (Circle *)malloc(sizeof(Circle));
    c->base.type = "Circle";
    c->base.area = circle_area;
    c->base.perimeter = circle_perimeter;
    c->base.print = circle_print;
    c->radius = radius;
    return c;
}

Rectangle *Rect_new(double w, double h) {
    Rectangle *r = (Rectangle *)malloc(sizeof(Rectangle));
    r->base.type = "Rectangle";
    r->base.area = rect_area;
    r->base.perimeter = rect_perimeter;
    r->base.print = rect_print;
    r->width = w;
    r->height = h;
    return r;
}

int main() {
    // 多态:用基类指针数组存储不同类型
    Shape *shapes[3];
    shapes[0] = (Shape *)Circle_new(5.0);
    shapes[1] = (Shape *)Rect_new(4.0, 6.0);
    shapes[2] = (Shape *)Circle_new(3.0);
    
    printf("=== 多态调用 ===\n");
    double total_area = 0;
    for (int i = 0; i < 3; i++) {
        shapes[i]->print(shapes[i]);
        total_area += shapes[i]->area(shapes[i]);
    }
    printf("总面积: %.2f\n", total_area);
    
    // 释放内存
    for (int i = 0; i < 3; i++) free(shapes[i]);
    
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

原理说明:这个案例展示了C语言实现面向对象三大特性的完整方法:封装(结构体包含数据和函数指针)、继承(派生结构体的第一个成员是基类结构体,利用内存布局兼容性进行类型转换)、多态(通过函数指针实现虚函数表,不同类型有不同的实现)。这正是GTK、Linux内核等大型C项目的编程模式。C++的虚函数机制在底层也是类似的原理。

思考题:

  1. 为什么派生结构体的"基类"必须是第一个成员?如果放在其他位置会怎样?
  2. 这种手动实现的多态与C++的虚函数有什么异同?C++编译器是如何实现虚函数表的?
  3. 如何为这个形状系统添加一个"析构函数"机制,确保不同类型能正确清理各自的资源?

# 9.2.1 栈(Stack)

用于存储局部变量、函数参数和函数调用的上下文。

  • 内存由编译器自动分配和释放。
  • 大小有限,通常较小(几 MB)。
  • 访问速度快。

示例:

void func() {
    int x = 10; // x 存储在栈中
}
1
2
3

# 9.2.2 堆(Heap)

用于动态内存分配。

  • 内存由程序员手动管理(使用 malloc、calloc、realloc 和 free)。
  • 大小较大,受系统内存限制。
  • 访问速度较慢。

示例:

int *p = (int *)malloc(sizeof(int)); // 在堆中分配内存
free(p); // 释放内存
1
2

# 9.2.3 全局/静态区

用于存储全局变量和静态变量。

  • 内存由编译器在程序启动时分配,在程序结束时释放。
  • 分为初始化和未初始化两部分。

示例:

int global_var = 10; // 全局变量,存储在全局区
static int static_var = 20; // 静态变量,存储在全局区
1
2

# 9.2.4 常量区

用于存储字符串常量和 const 变量。

  • 内存由编译器分配,程序结束时释放。
  • 通常是只读的。

示例:

const char *str = "Hello, World!"; // 字符串常量,存储在常量区
1

# 9.2.5 代码区

  • 用于存储程序的二进制代码(指令)。
  • 通常是只读的。

# 9.2.6 综合案例与思考

综合案例:探索C程序的内存布局

#include <stdio.h>
#include <stdlib.h>

// 全局变量 —— 全局/静态区
int global_init = 100;       // 已初始化的全局变量
int global_uninit;            // 未初始化的全局变量(BSS段)
static int static_global = 200;

// 常量 —— 常量区
const char *str_literal = "Hello, Memory!";

void show_memory_layout() {
    // 局部变量 —— 栈
    int local_var = 42;
    static int static_local = 300;  // 静态区(非栈)
    
    // 动态分配 —— 堆
    int *heap_var = (int *)malloc(sizeof(int));
    *heap_var = 999;
    
    printf("=== C程序内存布局 ===\n\n");
    printf("代码区(函数地址):\n");
    printf("  main()          = %p\n", (void *)main);
    printf("  show_memory()   = %p\n\n", (void *)show_memory_layout);
    
    printf("常量区:\n");
    printf("  字符串字面量    = %p\n\n", (void *)str_literal);
    
    printf("全局/静态区:\n");
    printf("  global_init     = %p (值=%d)\n", (void *)&global_init, global_init);
    printf("  global_uninit   = %p (值=%d)\n", (void *)&global_uninit, global_uninit);
    printf("  static_global   = %p (值=%d)\n", (void *)&static_global, static_global);
    printf("  static_local    = %p (值=%d)\n\n", (void *)&static_local, static_local);
    
    printf("栈区(从高地址向低地址增长):\n");
    printf("  local_var       = %p (值=%d)\n", (void *)&local_var, local_var);
    printf("  heap_var(指针)  = %p\n\n", (void *)&heap_var);
    
    printf("堆区(从低地址向高地址增长):\n");
    printf("  *heap_var       = %p (值=%d)\n", (void *)heap_var, *heap_var);
    
    free(heap_var);
}

int main() {
    show_memory_layout();
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

原理说明:C程序的内存布局从低地址到高地址依次是:代码区(.text)、常量区(.rodata)、已初始化全局/静态变量区(.data)、未初始化全局/静态变量区(.bss)、堆(向高地址增长)、栈(向低地址增长)。堆和栈之间有一大块空闲区域。栈的大小在程序启动时就确定了(通常几MB),而堆可以动态扩展。static 局部变量虽然写在函数内部,但实际存储在全局/静态区,生命周期与程序相同。

思考题:

  1. 运行上面的程序,观察各区域的地址,能否验证内存布局从低到高的顺序?
  2. 栈和堆如果增长方向相向而行,是否可能发生碰撞?这种情况叫什么?
  3. static 局部变量和全局变量在内存位置上有什么区别?它们的初始化时机分别是什么?

动态内存管理使用 malloc、calloc、realloc 和 free。

# 9.3.1 malloc

  • 分配指定大小的内存块。
  • 返回指向分配内存的指针。
  • 分配的内存未初始化。

示例:

int *p = (int *)malloc(sizeof(int)); // 分配 4 字节内存
if (p == NULL) {
    perror("Failed to allocate memory");
    return 1;
}
*p = 10;
free(p); // 释放内存
1
2
3
4
5
6
7

# 9.3.2 calloc

  • 分配指定数量和大小的内存块。
  • 返回指向分配内存的指针。
  • 分配的内存初始化为 0。

示例:

int *p = (int *)calloc(5, sizeof(int)); // 分配 5 个 int 大小的内存
if (p == NULL) {
    perror("Failed to allocate memory");
    return 1;
}
free(p); // 释放内存
1
2
3
4
5
6

# 9.3.3 realloc

  • 调整已分配内存块的大小。
  • 返回指向新内存块的指针。
  • 如果新大小大于原大小,新增部分未初始化。

示例:

int *p = (int *)malloc(5 * sizeof(int)); // 分配 5 个 int 大小的内存
p = (int *)realloc(p, 10 * sizeof(int)); // 调整为 10 个 int 大小
if (p == NULL) {
    perror("Failed to reallocate memory");
    return 1;
}
free(p); // 释放内存
1
2
3
4
5
6
7

# 9.3.4 free

  • 释放动态分配的内存。
  • 只能释放由 malloc、calloc 或 realloc 分配的内存。

示例:

int *p = (int *)malloc(sizeof(int));
free(p); // 释放内存
1
2

# 9.3.5 综合案例与思考

综合案例:动态内存管理实战——可增长的动态数组

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    int *data;
    int size;       // 当前元素个数
    int capacity;   // 当前容量
} DynArray;

// 创建动态数组
DynArray *dynarray_create(int init_cap) {
    DynArray *arr = (DynArray *)malloc(sizeof(DynArray));
    if (!arr) return NULL;
    arr->data = (int *)calloc(init_cap, sizeof(int));
    if (!arr->data) { free(arr); return NULL; }
    arr->size = 0;
    arr->capacity = init_cap;
    return arr;
}

// 添加元素(自动扩容)
int dynarray_push(DynArray *arr, int value) {
    if (arr->size >= arr->capacity) {
        int new_cap = arr->capacity * 2;
        int *new_data = (int *)realloc(arr->data, new_cap * sizeof(int));
        if (!new_data) return -1;
        arr->data = new_data;
        arr->capacity = new_cap;
        printf("  [扩容: %d -> %d]\n", arr->capacity / 2, new_cap);
    }
    arr->data[arr->size++] = value;
    return 0;
}

// 打印
void dynarray_print(DynArray *arr) {
    printf("数组(size=%d, cap=%d): ", arr->size, arr->capacity);
    for (int i = 0; i < arr->size; i++) printf("%d ", arr->data[i]);
    printf("\n");
}

// 销毁
void dynarray_destroy(DynArray *arr) {
    if (arr) {
        free(arr->data);
        arr->data = NULL;
        free(arr);
    }
}

int main() {
    DynArray *arr = dynarray_create(4);
    
    printf("=== 动态数组演示 ===\n");
    for (int i = 1; i <= 10; i++) {
        dynarray_push(arr, i * 10);
    }
    dynarray_print(arr);
    
    dynarray_destroy(arr);
    printf("内存已释放\n");
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

原理说明:malloc 从堆上分配内存但不初始化(内容是垃圾值);calloc 分配并初始化为0;realloc 调整已分配内存的大小,可能在原地扩展或移动到新位置。realloc 成功时返回新指针(可能与原指针不同),失败返回 NULL——注意不要直接写 p = realloc(p, ...), 否则失败时原内存地址丢失,造成内存泄漏。每次倍增容量(capacity * 2)是经典策略,保证 n 次插入的均摊时间复杂度为 O(1)。

思考题:

  1. realloc(p, new_size) 在什么情况下会返回与 p 不同的地址?这对程序有什么影响?
  2. 为什么动态数组扩容通常选择2倍而不是每次+1?从时间复杂度角度分析。
  3. malloc(0) 返回什么?realloc(NULL, size) 等价于什么?realloc(p, 0) 呢?

常见内存问题包括内存泄漏、野指针、双重释放和越界访问。

# 9.5.1 内存泄漏

  • 动态分配的内存未释放。
  • 导致程序占用内存不断增加。

示例:

void func() {
    int *p = (int *)malloc(sizeof(int));
    // 忘记调用 free(p);
}
1
2
3
4

# 9.5.2 野指针

  • 指针指向已释放的内存。
  • 访问野指针会导致未定义行为。

示例:

int *p = (int *)malloc(sizeof(int));
free(p);
*p = 10; // 野指针访问
1
2
3

# 9.5.3 双重释放

  • 对同一块内存多次调用 free。
  • 导致程序崩溃。

示例:

int *p = (int *)malloc(sizeof(int));
free(p);
free(p); // 双重释放
1
2
3

# 9.5.4 越界访问

  • 访问超出分配内存范围的数据。
  • 导致程序崩溃或数据损坏。

示例:

int *p = (int *)malloc(5 * sizeof(int));
p[5] = 10; // 越界访问
free(p);
1
2
3

# 9.5.5 最佳实践

  1. 初始化指针:在声明指针时初始化为 NULL。
  2. 检查返回值:在使用 malloc、calloc 或 realloc 后检查返回值是否为 NULL。
  3. 及时释放内存:动态分配的内存使用完毕后及时调用 free。
  4. 避免野指针:释放内存后将指针置为 NULL。
  5. 使用工具检测内存问题:如 Valgrind、AddressSanitizer 等。

# 9.6 综合案例实践

# 9.6.1 动态数组

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n = 5;
    int *arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        perror("Failed to allocate memory");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }

    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }

    free(arr);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 9.6.2 二维动态数组

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows = 3, cols = 4;
    int **arr = (int **)malloc(rows * sizeof(int *));
    if (arr == NULL) {
        perror("Failed to allocate memory");
        return 1;
    }

    for (int i = 0; i < rows; i++) {
        arr[i] = (int *)malloc(cols * sizeof(int));
        if (arr[i] == NULL) {
            perror("Failed to allocate memory");
            return 1;
        }
    }

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            arr[i][j] = i * cols + j + 1;
        }
    }

    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }

    for (int i = 0; i < rows; i++) {
        free(arr[i]);
    }
    free(arr);

    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
上次更新: 2026/06/10, 11:13:41
数组和容器
流与文件

← 数组和容器 流与文件→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式