不可或缺 Windows Native (9) - C 语言: 动态分配内存,链表,位域
作者:webabcd
介绍
不可或缺 Windows Native 之 C 语言
- 动态分配内存
- 链表
- 位域
示例
cMemory.h
#ifndef _MYHEAD_MEMORY_ #define _MYHEAD_MEMORY_ #ifdef __cplusplus extern "C" #endif char *demo_cMemory(); #endif
cMemory.c
/* * 动态分配内存,链表,位域 */ #include "pch.h" #include "cMemory.h" #include "cHelper.h" void alloc_demo1(); void alloc_demo2(); void chain_demo(); void bitfield_demo(); char *demo_cMemory() { // 动态分配内存基础 1 alloc_demo1(); // 动态分配内存基础 2 alloc_demo2(); // 链表 chain_demo(); // 位域(所谓位域,就是可以按 bit 存放数据) bitfield_demo(); return "看代码及注释吧"; } // 动态分配内存基础 1 void alloc_demo1() { // malloc - 分配指定大小的连续的内存区域,并返回该区域的首地址,返回值的类型是 void* (具体类型需要用户自己指定) // calloc - 分配 n 块,每块长度为指定大小的连续内存区域,并返回该区域的首地址,返回值的类型是 void* (具体类型需要用户自己指定) // free - 释放由 malloc 或 calloc 函数所分配的内存区域(必须的) char *s = "i am webabcd"; // 开一个 100 字节大小的连续内存区域,并将其强制转换为字符数组类型,函数的返回值为数组首地址 char *p1 = (char *)malloc(100); // 把 s 所指的内容复制到 p1 所指的内存区域 strncpy(p1, s, strlen(s) + 1); // 释放掉由 malloc 开辟的,p1 所指的内存空间 free(p1); // 下面这段是不对的,因为开辟的内存空间不够,赋值的时候虽然正常,但是 free 的时候会报错 /* char *p2 = (char *)malloc(1); strncpy(p2, s, strlen(s) + 1); free(p2); // 这里会报错 */ // 下面这段是不对的,因为虽然为 p3 动态开辟了内存空间,但是却将一个常量赋值给 p3,也就是说 p3 并没有使用我们动态开辟的内存空间 /* char *p3 = (char *)malloc(100); p3 = "webabcd"; free(p3); // 这里会报错 */ // 开一个 100 块的,每块长度为 1 字节大小的连续内存区域,并将其强制转换为字符数组类型,函数的返回值为数组首地址 char *p4 = (char *)calloc(100, 1); // 把 s 所指的内容复制到 p4 所指的内存区域 strncpy(p4, s, strlen(s) + 1); // 释放掉由 calloc 开辟的,p4 所指的内存空间 free(p4); } // 动态分配内存基础 2 void alloc_demo2() { char *s = "i am webabcd"; char *p = (char *)malloc(100 * sizeof(char)); // 申请内存时有可能失败,失败时会返回 NULL(' '),这个判断是必须的 if (p == NULL) { printf("内存分配出错!"); exit(1); // exit(0) - 告诉系统我是正常退出;非 0 值则是告诉系统我是非正常退出 } strncpy(p, s, strlen(s) + 1); // 一定要释放(释放指针所指的动态分配的内存空间) free(p); // 释放后,指针置空(将指针本身置空,即空指针)是好习惯,防止野指针 p = NULL; } /* * 关于链表的概念 * * 什么是数据域:在结点结构中用于存放实际数据的区域称为“数据域” * 什么是指针域:在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员就称为“指针域” * * 什么是链表:在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内存入第三个结点的首地址,如此串连下去直到最后一个结点。 * 最后一个结点因无后续结点连接,其指针域一般赋值为 ' '。这样一种连接方式,在数据结构中称为“链表”。 * 链表中的每个结点都分为两个域,一个是数据域,另一个域为指针域。 * 指向整个链表的指针一般称之为“头结点”,其存放的其实就是第一个结点的首地址。 */ // 定义一个名为 employee 的结构体(用于演示“单向链表”) struct employee { int num; char *name; struct employee *next; // 指针域,用于存放下一个节点的首地址 // struct employee *prev; // 如果需要“双向链表”的话,可以在这个指针域中存放上一个节点的首地址 }; void chain_demo() { // 创建一个有 100 个 employee 结点的链表 struct employee *creat(int n); struct employee *employee_chain = creat(100); // 如果不用了,别忘了 free 掉链表 struct employee *temp; while (employee_chain) { temp = employee_chain; employee_chain = employee_chain->next; free(temp); } } // 创建一个指定结点数的链表(n 是指定的节点数) struct employee *creat(int n) { struct employee *head = NULL, *p1 = NULL, *p2 = NULL; for (int i = 0; i<n; i++) { p2 = (struct employee *)malloc(sizeof(struct employee)); p2->num = i; p2->name = "webabcd"; if (i == 0) head = p1 = p2; else p1->next = p2; p2->next = NULL; p1 = p2; } return head; } // 位域(所谓位域,就是可以按 bit 存放数据) void bitfield_demo() { // 注:以下如果有列出结果的,均为我的环境的结果。比如我这里 int 是占用 4 个字节的 // 1、如果相邻位域成员的类型相同,且每个成员的位长之和不大于类型的 sizeof 大小,则后面的成员将紧邻前一个成员存储 struct bf // 此位域类型占用 4 个字节 { int a : 1; // 位长为 1,它存储在 4 字节的第 1 位 int b : 3; // 位长为 3,它存储在 4 字节的第 2 位到第 4 位 int c : 7; // 位长为 7,它存储在 4 字节的第 5 位到第 11 位 } bf1; // 2、如果相邻位域成员的类型相同,且每个成员的位长之和大于类型的 sizeof 大小,则后面的成员如果紧邻前一个成员存储时超过类型 sizeof 大小了,则此成员会从新的存储单元开始存储(而不是紧邻前一个成员存储) struct // 此位域类型占用 12 个字节 { int a : 31; // 位长为 31,它存储在第 1 个 4 字节的第 1 位到第 31 位 int b : 2; // 位长为 2,它存储在第 2 个 4 字节的第 1 位到第 2 位(不会紧邻前一个成员存储) int c : 31; // 位长为 31,它存储在第 3 个 4 字节的第 1 位到第 31 位(不会紧邻前一个成员存储) } bf2; // 3、如果相邻位域成员的类型不相同,则各编译器的具体实现有差异,VC 采取不压缩方式(不同的位域成员存放在不同的位域类型空间中);GCC 和其它都采取压缩方式(参考第 1 知识点和第 2 知识点) struct // 此位域类型占用 8 个字节 { char c : 2; int i : 4; // 和前一个成员的类型不同,会在新的空间中存储(如果是 GCC 的话则会紧邻存储) } bf3; // 4、如果位域成员之间穿插着非位域成员,则不进行压缩,即此非位域成员后面的位域成员不会与此非位域成员前面的位域成员紧邻存储 struct // 此位域类型占用 12 个字节 { int a : 1; // 位长为 1,它存储在第 1 个 4 字节的第 1 位 char x; // 非位域成员,它存储在第 2 个 4 字节 int b : 3; // 位长为 3,它存储在第 3 个 4 字节的第 1 位到第 3 位 int c : 7; // 位长为 7,它存储在第 3 个 4 字节的第 4 位到第 10 位 } bf4; // 5、空域 - 用于强制后面的位域从新的空间存储 struct // 此位域类型占用 8 个字节 { int a : 1; // 位长为 1,它存储在第 1 个 4 字节的第 1 位 int : 0; // 空域,强制后面的位域从新的空间存储 int b : 3; // 位长为 3,它存储在第 2 个 4 字节的第 1 位到第 3 位 int c : 7; // 位长为 7,它存储在第 2 个 4 字节的第 4 位到第 10 位 } bf5; // 6、占位域 - 仅占位用,不可使用 struct // 此位域类型占用 4 个字节 { int a : 1; // 位长为 1,它存储在 4 字节的第 1 位 int : 2; // 占位域,仅占位用,不可使用,位长为 2,它占用了 4 字节的第 2 位到第 3 位 int b : 3; // 位长为 3,它存储在 4 字节的第 4 位到第 6 位 int c : 7; // 位长为 7,它存储在 4 字节的第 7 位到第 13 位 } bf6; } /* * 关于内存泄漏注意事项 * 1、内存分配未成功,却使用了它 * 2、内存分配虽然成功,但是尚未初始化就引用它 * 3、内存分配成功并且已经初始化,但操作越过了内存的边界 * 4、忘记了释放内存,造成内存泄露 * 5、释放了内存却继续使用它 * * * 关于内存的几点注意 * 1、如果在函数内使用了动态分配内存的话,那么函数结束后,其是不会自动消亡的,必须要手动 free 掉。也就是说凡是动态 alloc 的内存,必须要手动 free 掉 * 2、动态分配内存后,如果只是对相应的指针赋值 NULL,那样内存是不会释放的(现代语言中,有引用计数器的概念,如果一块区域无人引用的话就会被释放或者被 GC 释放) * * * 关于内存区域的类型 * 1、栈:在栈里面存储的是局部变量以及形参 * 2、字符常量区:用于存储字符常量,比如:char *p = "webabcd"; 其中 "webabcd" 就存储在字符常量区里面 * 3、全局区:用于存储全局变量和静态变量 * 4、堆:在堆里面存储的主要是通过动态分配的内存空间 * * * 本例讲的是内存的动态分配 * 1、什么是静态分配:是指在程序运行期间分配固定的存储空间的方式(可以这么理解:编译器在编译时会对类似 int i; 这样的代码生成一种内存分配方案并将其写入编译后的文件,等到运行时就按指定的方案分配) * 2、什么是动态分配:是在程序运行期间根据需要进行动态的分配存储空间的方式 */
OK
[源码下载]