目录
- 一、typedef
-
- 1.1 声明整型
- 1.2 声明结构体类型
- 1.3 声明数组
- 1.4 声明指针
- 1.5 声明数组指针
- 1.6 声明函数指针
- 1.7 声明枚举类型
- 二、共用体
-
- 2.1 共用体的定义
- 2.2 共用体 vs 结构体
- 2.3 使用示例
- 2.4 典型应用场景
- 2.5 与结构体嵌套使用
- 2.6 总结
- 三、位运算
-
- 3.1 按位与 &
- 3.2 按位或 |
- 3.3 异或 ^
- 3.4 取反运算符 ~
- 3.5 左移运算符 <<
- 3.6 右移运算符 >>
- 3.7 赋值运算符
- 四、 位段
- 五、枚举类型
-
- 5.1 基本语法
- 5.2 枚举常量的值
- 5.3 定义枚举变量
-
- 5.3.1 先定义类型,再声明变量
- 5.3.2 定义类型的同时声明变量
- 5.3.3 匿名枚举(不推荐,除非只用一次)
- 5.4 使用 typedef 简化
- 5.5 枚举的本质
- 5.6 典型应用场景
-
- 5.6.1 状态机
- 5.6.2 选项/模式选择
- 5.6.3 错误码
- 5.7 注意事项
- 5.8 实用技巧:将枚举值转为字符串
- 5.9 与 #define 对比
- 5.10 总结
- 六、内存管理
-
- 6.1 存储模型
- 6.2 动态内存
一、typedef
在 C 语言中,typedef 是一个关键字,用于为已存在的数据类型创建一个新的别名。它不会创建新类型,只是让代码更简洁、可读性更强,尤其在处理复杂类型(如结构体、函数指针、数组指针等)时非常有用。
当不同源文件中用到同一类型数据时,常用typedef声明一些数据类型,把它们单独放在一个文件中,然后在需要用到它们的文件中用 #include 命令把他们包含进来。
使用typedef 有利于程序的通用与移植
typedef 与 #define有相似之处,例如:typedef int COUNT; #define COUNT int; 的作用都是用COUNT 代替int.但是,它们二者是不同的
#define 是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译时处理的。实际上它并不是作简单的字符串替换,而是采用如同定义变量的方法来声明一个类型
基本语法 typedef 已有的数据类型 新类型名称;
1.1 声明整型
声明 INTEGER 为 整型:
#include <stdio.h>
#include <stdlib.h>
typedef int INTEGER;
int main(){
// INTEGER i=1; 等价于 int i=1;
INTEGER i = 1;
int j = 2;
printf("%d,%d,%d\\n", i, j, i+j);
return 0;
}
1.2 声明结构体类型
声明 date_one 为 DATE 结构体类型
#include <stdio.h>
#include <stdlib.h>
// 定义了一个名为 DATE 的结构体类型
typedef struct{
int month;
int day;
int year;
}DATE;
int main(){
// 声明了一个 DATE 类型的变量 date_one;
DATE date_one;
date_one.month = 9;
date_one.day = 30;
date_one.year = 2025;
printf("%d-%02d-%02d\\n",
date_one.year, date_one.month, date_one.day);
return 0;
}
1.3 声明数组
声明 NUM 为 整型数组类型
#include <stdio.h>
#include <stdlib.h>
// 使用 typedef 为 int[100] 定义了一个新的类型名 NUM
typedef int NUM[100];
int main(){
NUM a = {0};// 等价于 int a[100];
printf("%d\\n", sizeof(a)); // 400
return 0;
}
1.4 声明指针
声明 SRTING 为 字符指针类型
#include <stdio.h>
#include <stdlib.h>
typedef char* SRTING;
int main(){
SRTING a, b;// 等价于 char *a, *b;
a = "Zhangsan";
b = "Lisi";
printf("%d, %d\\n", sizeof(a), sizeof(b)); // 8, 8
printf("%s, %s\\n", a, b); // Zhangsan, Lisi
return 0;
}
1.5 声明数组指针
声明 POINT 为 指向数组的指针类型(数组指针)
#include <stdio.h>
#include <stdlib.h>
// 定义了一个 指向数组的指针类型
// 指向一个包含 3个int的数组 的指针
typedef int (*POINT)[3];
int main(){
POINT p1; // 等价于 int (*p1)[3];
int arr1[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
p1 = arr1;
printf("%d, %d, %d\\n", p1[0][0], p1[1][1], p1[2][2]);
return 0;
}
1.6 声明函数指针
声明 POINT 为 指向函数的指针类型(函数指针),该函数返回整型值
#include <stdio.h>
#include <stdlib.h>
// 定义了一个 函数指针类型, POINT 是一个类型别名
typedef int (*POINT)(void);// 明确:无参, 返回 int
int fun(void){
printf("nian is very handsome\\n");
return 0;
}
int main(){
// POINT p1; 等价于 int (*p1)();
POINT p1;
p1 = fun;
(void)p1();// 显式忽略返回值
return 0;
}
1.7 声明枚举类型
声明 Bool 为 枚举类型
typedef enum {
FALSE,
TRUE
}Bool;
// 使用
Bool flag = TRUE;
二、共用体
在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型成为共同体,简称共体,又称联合体。 共用体在定义,说明和使用形式上与结构体相似,两者在本质上的不同仅在于使用内存方式上。
2.1 共用体的定义
union 共用体名 {
成员列表;
};
示例:
#include <stdio.h>
union data {
int i;
float f;
char str[20];
};
这个 union data 中的三个成员 i、f 和 str 共享同一段内存,其大小等于最大成员所需的内存 (这里是 char[20],所以通常是 20 字节)。
2.2 共用体 vs 结构体
| 内存分配 | 每个成员独立存储,总大小 ≥ 各成员之和(考虑对齐) | 所有成员共享同一块内存,总大小 = 最大成员的大小 |
| 赋值影响 | 修改一个成员不影响其他成员 | 修改一个成员会覆盖其他成员的值 |
| 用途 | 存储多个相关数据 | 在同一内存位置存储不同类型的数据(节省空间、类型转换等) |
2.3 使用示例
注意:任何时候,只能安全地读取最近写入的那个成员。读取其他成员会导致未定义行为或垃圾值。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
union data{
int i;
float f;
char str[20];
};
int main(){
union data d1;
d1.i = 10;
printf("data.i:%d\\n", d1.i);
d1.f = 20;
printf("data.f:%.2f\\n", d1.f);
strcpy(d1.str, "Zhangsan");
printf("data.str:%s\\n", d1.str);
// 共用体的大小 输出通常是 20 (因为 char str[20] 是最大成员)
printf("Size of union Data: %zu\\n", sizeof(d1));
return 0;
}
2.4 典型应用场景
1、节省内存 当多个变量不会同时使用时,可用共用体共享内存。
2、类型双关 用于在不同数据类型之间“重新解释”同一块内存(需谨慎!)。
union {
unsigned int i;
float f;
} converter;
converter.f = 3.14f;
printf("As integer: 0x%x\\n", converter.i); // 查看 float 的二进制表示
3、协议解析 / 硬件寄存器访问 例如解析网络数据包或访问硬件寄存器的不同字段。
union Register {
uint32_t raw;
struct {
uint32_t enable : 1;
uint32_t mode : 3;
uint32_t value : 28;
} bits;
};
2.5 与结构体嵌套使用
struct Packet {
int type;
union {
int int_data;
float float_data;
char msg[32];
} payload;
};
根据 type 字段决定读取 payload 中的哪个成员 —— 这是实现“变体记录”的常用方式。
2.6 总结
- 共用体 = 多个成员共享同一内存
- 大小 = 最大成员的大小(考虑对齐)
- 任何时候只能安全使用最后一个写入的成员
- 用途:节省内存、底层数据操作、协议解析、硬件编程等
- 风险:误读未写入的成员 → 垃圾值或未定义行为
提示:C11 引入了 _Generic 和更安全的类型处理,但在资源受限环境(如嵌入式),union 仍是不可或缺的工具。
三、位运算
| ~ | 位逻辑反 | ~a |
| & | 位逻辑与 | a&b |
| I | 位逻辑或 | a I b |
| ^ | 位逻辑异或(相同为假,不同为真) | a^b |
| >> | 右移动 | a>>b |
| << | 左移动 | a<<b |
3.1 按位与 &
3 & 5 并不等于 8 , 应该是按位与运算: 0000 0011 (3) 0000 0101 (5) 一一一一一一一 0000 0001 (1)
两个相应的二进制中两个都为1,该位的结果值为1 与0运算都为0,与1运算为本身
如果参加 & 运算的是负数,如(-3 & -5),则要以补码形式表示为二进制数,然后再按位进行 “与运算”。
按位与 & 运算的用途 1、清零 若相对一个存储单元清零,即使其全部二进制位为0,只要找一个二进制数,其中各个位符合以下条件: 原来的数中为1的位,新数中相应位为0,然后使二者进行 & 运算,即可达到清零目的。
例如: 要求将二进制数 1110 0101 的第二位清零
1110 0101 1111 1011 一一一一一一一 1110 0001
2、取一个数中某些指定位 例如: 我们需要对一个字符数据取出其低八位的值时,我们可以这么做。
11010101 01011011 00000000 11111111 一一一一一一一一一一 00000000 01011011
3.2 按位或 |
两个相应的二进制中只要有一个为1,该位的结果值为 1 即:
0 | 0 = 0 ,0 | 1 =1 1 | 0 = 1,1 | 1 = 1
例如:
0011 0000 0000 1111 一一一一一 0011 1111
编写一个程序,将输入的大写字母转换为小写字母,输入的小写字母转换为大写字母,要求用位操作完成转换过程 A ASCII : 65 对应二进制为 -> 0100 0001 a ASCII : 97 对应二进制为 -> 0110 0001
#include <stdio.h>
#include <stdlib.h>
int main(){
char ch;
printf("Please enter a letter:");
ch = getchar();
while(!((ch>='A' && ch<='Z') || (ch>='a' && ch<='z'))){
printf("Input error, please re-enter:");
ch = getchar();
}
if(ch & 32){ // 0010 0000 判断是否为小写(非零表示小写)
ch = ch & 223; // 1101 1111 清除第 5 位,转大写
}
else{
ch = ch | 32; // 0010 0000 设置第 5 位,转小写
}
putchar(ch);
ch = getchar();
putchar(ch);
return 0;
}
3.3 异或 ^
与 0 异或保留原值,与 1 异或取反 异或运算符 ^ 也称 XOR运算符 它的规则是:若参加运算的是两个二进制位同号则结果为0(假), 异号则为1(真)
即: 0 ^ 0 = 0, 0 ^ 1 = 1 1 ^ 0 = 1, 1 ^ 1 = 0
例如:
0011 1001 0010 1010 一一一一一 0001 0011
1、使特定位翻转 设有 0111 1010,想使其低四位翻转,即 1 变为0 ,0 变成1。可以将它与 0000 1111 进行 ^ 运算,即(翻转位与 1 异或取反):
0111 1010 0000 1111 一一一一一 0111 0101
2、与 0 异或 ^ ,保留原值 例如:
0000 1010 0000 0000 一一一一一 0000 1010
因为原数中的1 与 0 进行 ^运算得1, 0 ^ 0 得0,故保留原数。
3、交换两个值,不用临时变量 例如: a=3,b = 4 ,现在想要将 a,b 变量的值交换位置 a = a ^ b; b = b ^ a; a = a ^ b; 这种方法也常用于加密算法
3.4 取反运算符 ~
~ 是一个单目运算符,用来对一个二进制数按位取反,即将 0 变成1,将1变成0; 例如:~ 025 是对八进制数 25 (二进制 0001 0101)按位取反
0001 0101 一一一一一 1110 1010
3.5 左移运算符 <<
左移运算符是用来将一个数的各二进制位全部左移若干位。
例如: a = << 2 将a的二进制数左移2 位,右边补0. 若 a = 15, 即二进制数 0000 1111,左移2位得, 0011 1100(十进制数60)。 若高位左移后溢出,舍弃。
左移n位相当于该数乘以2^n
左移1位相当于该数乘以2,左移2位相当于该数乘以4,15<<2 = 60,即乘了4 (2的2次方) 但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。
假设一个字节(8)位存一个整数,若a为无符号整型变量,则a = 64时,左移1位时溢出的是0,而左移2位时,溢出的高位中包含1。
64原:0100 0000 左移1位:0100 0000 左移2位:0000 0000
3.6 右移运算符 >>
右移运算符是a >>2表示将a的各二进制位右移2位,移到右端的低位被舍弃,对无符号数,高位补0。
例如: a = 017时, a的值用二进制形式表示为 0000 1111,舍弃低2位11,得到:a>>2 == 0000 0011
右移一位相当于除以2 右移n位相当于除于2^n
右移移动,需要注意符号位问题: 1、对无符号数,右移时左边高位移入0;
2、对于有符号的值,如果原来符号位为0 (该数为正),则左边也是移入0 如果符号位原来位1(即负号),则左边移入0还是1,要取决于所用的计算机系统。有的系统移入0,有的系统移入1。 移入0的称为 “逻辑右移”,即简单右移;移入1的称为“算术右移”。
例: a 的值是十进制数 -2; a == 1111 1110(二进制) 无符号数:a>>1 :0111 1111(逻辑右移) 有符号数:a>>1 :1111 1111(算术右移)
#include <stdio.h>
#include <stdlib.h>
void main()
{
unsigned char a = –2;// 1111 1110
a = a>>1;
printf("%d\\n",a); // 0111 1111 (127)
}
#include <stdio.h>
#include <stdlib.h>
void main()
{
char a = –2;// 1111 1110
a = a>>1;
printf("%d\\n",a); // 1111 1111 (-1)
}
3.7 赋值运算符
位运算符与赋值运算符可以组成复合赋值运算符。
例如: &= , |= , >>= , <<= , ^ =
a &= b 相当于 a = a & b a <<= 2 相当于 a = a << 2
例1:取一个 char a 从右端开始的 2-5位: ①先使a 右移 2位 : a>>2 目的是使要取出的那几位移到最右端 ②设置一个低四位全为1,其余全为0的数。 ~ (~0 << 4) // 0000 1111 ③将上面 ① ②进行 &运算 (a >> 2) & (~ 0 <<4 )
#include <stdio.h>
#include <stdlib.h>
void main()
{
char a, b, c, d;
printf("Please enter the array to be validated:");
scanf("%d", &a);
b = a>>2;
c = ~(~0 << 4); // ~(1111 1111 << 4) == 0000 1111
d = b & c;
printf("%d\\n", d);
// 输入23 0001 0111 右移2位 0000 0101 结果 5
}
例2:要求将 a 进行右循环移位 
#include <stdio.h>
#include <stdlib.h>
void main()
{
unsigned char a, b, c, d;
int n;
printf("Please enter the number to achieve circular right shift:\\n");
scanf("%d", &a);
printf("Please input the number of bits to be shifted right:\\n");
scanf("%d", &n);
b = a<<(sizeof(char)*8 – n); // 取出a的最后n位,左移到最前面,右边补0
c = a>>n;// a右移n位, 左边补0
d = b | c;// 实现右循环移位
printf("%d\\n", d);
// 23 3 226
}
四、 位段
信息的存取一般以字节位单位,实际上,有时存储一个信息不必用一个或多个字节。
例如:“真”或 “假”用0或1表示,只需 1 位即可。
在计算机用于过程控制,参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为"位段 "或称"位域 ",利用位段能够用较少的位数存储数据。
内存布局(以 GCC / Clang on x86-64 为例)
struct packed{
unsigned a:2; // 占 bit0~bit1
unsigned b:6; // 占 bit2~bit7 → 与 a 共享第 1 个字节
unsigned c:4; // 占 bit0~bit3(新字节开始?不一定!)
unsigned d:4; // 占 bit4~bit7 → 与 c 共享第 2 个字节
int i; // 4 字节, 通常从 4 字节对齐地址开始
}data;
内存布局(小端,GCC 默认):
| 0 | b[5:0] a[1:0] | a(2位), b(6位) |
| 1 | d[3:0] c[3:0] | c(4位), d(4位) |
| 2 | (填充) | |
| 3 | (填充) | |
| 4~7 | i(4字节整数) | int i |
所以: 第一个字节:低2位是 a,高6位是 b 第二个字节:低4位是 c,高4位是 d
1、位段成员的类型必须指定为 unsigned 或 int类型。 2、若某一位段要从另一个字开始存放,可用以下形式定义:
unsigned a:1;
unsigned b:2; 一个存储单元
unsigned :0;
unsigned c:3; 另一存储单元
a、b、c 应连续存放在一个存储单元中,由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放。因此,只将 a、b 存储在一个存储单元中,c 另存在下一个单元(存储单元可能是一个字节,也可能是两个字节,视不同的编译系统而异)。
3、一个位段必须存储在同一存储单元中,不能跨两个单元。如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。 4、可以定义无名位段。 5、位段的长度不能大于存储单元的长度,也不能定义位段数组。 6、位段可以用整型格式符输出。 7、位段可以在数值表达式中引用,它会被系统自动地转换成整型数。
五、枚举类型
在 C 语言中,枚举类型 是一种用户自定义的数据类型,用于定义一组命名的整型常量。它让代码更具可读性、可维护性和安全性。
5.1 基本语法
enum 枚举名 {
枚举常量1,
枚举常量2,
枚举常量3,
...
};
示例:
#include <stdio.h>
enum Color {
RED,
GREEN,
BLUE
};
int main() {
enum Color a = RED;
enum Color b = GREEN;
enum Color c = BLUE;
printf("RED = %d\\n", a); // 输出: RED = 0
printf("GREEN = %d\\n", b); // 输出: GREEN = 1
printf("BLUE = %d\\n", c); // 输出: BLUE = 2
return 0;
}
5.2 枚举常量的值
1、默认从 0 开始,依次递增 1。 2、可以显式指定值,后续未指定的常量从该值继续递增。 示例:
enum Status {
OFF = 0, // 0
ON, // 1(自动 = 0 + 1)
PENDING = 5, // 5
ERROR // 6(自动 = 5 + 1)
};
OFF=0, ON=1, PENDING=5, ERROR=6
5.3 定义枚举变量
有三种方式:
5.3.1 先定义类型,再声明变量
enum Week{
MON,
TUE,
WED
};
enum Week today = TUE;
5.3.2 定义类型的同时声明变量
enum Week{
MON,
TUE,
WED
}today, tomorrow;
5.3.3 匿名枚举(不推荐,除非只用一次)
enum{
MON,
TUE,
WED
}day = MON;
5.4 使用 typedef 简化
避免每次写 enum Name,可用 typedef 创建别名:
typedef enum {
FALSE,
TRUE
}Bool;
// 使用
Bool flag = TRUE;
这是 C 语言中模拟布尔类型的经典方法(C99 后有 _Bool 和 <stdbool.h>)。
5.5 枚举的本质
1、枚举常量是 int 类型的编译时常量。 2、枚举变量在内存中通常以 int 大小存储(但标准未强制,某些编译器可优化为更小类型)。 3、可以对枚举变量进行整数运算(但不推荐,会破坏类型安全)。 示例(合法但不推荐):
enum Color c = RED;
c = c + 1; // c 变成 GREEN(危险!可能越界)
5.6 典型应用场景
5.6.1 状态机
enum State {
IDLE,
RUNNING,
PAUSED,
STOPPED
};
enum State current = IDLE;
5.6.2 选项/模式选择
enum FileMode {
READ_ONLY,
WRITE_ONLY,
READ_WRITE
};
5.6.3 错误码
enum ErrorCode {
SUCCESS = 0,
FILE_NOT_FOUND = –1,
PERMISSION_DENIED = –2
};
5.7 注意事项
| 不是类型安全 | C 的 enum 本质是 int,可以赋任意整数值:enum Color c = 999; ✅(编译通过,但逻辑错误) |
| 不能直接输入/输出名字 | printf("%s", RED) ❌ 不行!需要手动映射(见下文技巧) |
| 作用域 | C89 中枚举常量是全局作用域,可能冲突;C 没有“枚举类”(像 C++ 的 enum class) |
5.8 实用技巧:将枚举值转为字符串
C 语言不支持反射,但可以用宏或数组模拟: 方法:字符串数组映射
#include <stdio.h>
enum Color{
RED,
GREEN,
BLUE,
COLOR_MAX
};
const char *color_names[] = {"RED", "GREEN", "BLUE"};
int main() {
enum Color c = GREEN;
printf("Color: %s\\n", color_names[c]); // 输出: Color: GREEN
return 0;
}
要求枚举从 0 开始且连续!
5.9 与 #define 对比
| 类型检查 | 有一定(仍是 int) | 无(纯文本替换) |
| 调试支持 | 调试器可显示名字 | 只能看到数字 |
| 作用域 | 遵循 C 作用域规则 | 全局宏,易冲突 |
| 自动生成值 | 支持(默认 0,1,2…) | 需手动写 |
| 内存 | 不占用(编译期常量) | 不占用 |
优先使用 enum 而不是 #define 来定义相关常量组!
5.10 总结
- enum 是定义命名整型常量集合的工具。
- 默认值从 0 开始,可显式赋值。
- 提高代码可读性,替代“魔法数字”。
- 本质是 int,不是强类型(C 语言限制)。
- 配合 typedef 使用更简洁。
- 适合表示状态、选项、类别等离散值。
最佳实践:
typedef enum{
STATUS_OK = 0,
STATUS_ERROR,
STATUS_TIMEOUT
}Status;
六、内存管理
6.1 存储模型
C和C++定义了四个内存区间:代码区、全局变量和静态变量区、局部变量区即栈区、动态存储区 即堆区
-
静态存储分配
- 通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。
- 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。
- 栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
-
动态内存分配
- 有些操作对象只有在程序运行时才能确定,这样编译器在编译时无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配。
- 所有动态存储分配都在堆区中进行。
- 从堆上分配,也叫做动态内存分配。程序在运行的时候用 malloc 申请任意多少的内存,程序员自己负责在何时用 free 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
-
堆内存的分配 和 释放
- 当程序运行到需要一个动态分配的 变量 或 对象时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放他所占用的存储空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。
- 堆区是不会自动在分配时做初始化的,所以必须用初始化来显式初始化。
6.2 动态内存
malloc / free
void *malloc(size_t num)
void free(void *p)
- malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
- malloc 申请到的是一块连续的内存,有时可能会比所申请的空间大,其有时会申请不到内存,返回 NULL。
- malloc 返回值的类型是 void ,所以在调用的时候需要显式的进行类型转化,将void 转化成所需的指针类型。
- 如果 free 的参数是 null 的话,没有任何效果 释放一块内存中的一部分是不被允许的。
注意事项 1、删除一个指针 p free(p); 实际意思是删除了p所指的目标(变量或对象等),释放了它所指占的堆空间,而不是删除p本身,释放堆空间后,p 成了空悬指针。
2、动态资源分配失败,返回一个空指针(null), 表示发生了异常,堆资源不足,分配失败。
3、 malloc 和 free是配对使用的,free只能释放堆空间。如果malloc返回的指针值丢失,则所分配的堆空间无法回收,称为内存泄漏
同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不好重复释放堆内存空间。
- 野指针:不是NULL指针,是指向"垃圾"内存的指针,野指针 很危险。
- 野指针造成的主要原因有两种
- 指针变量没有初始化
- 指针变量 p 被 free 之后,没有置为 NULL,让人误以为p是个合法的指针,指针操作超越了变量的使用范围。
网硕互联帮助中心






评论前必须登录!
注册