目录
一,存储管理
1.存储类别
在 C 语言中,存储类别决定了变量的存储位置和生命周期。主要有以下四种:
——自动存储类别:在函数内部定义的变量,未使用存储类别说明符时默认为自动存储类别。函数执行时创建,函数执行结束后销毁。
比如在厨房做蛋糕,用到的搅拌器,当做完蛋糕离开厨房,这个搅拌器就被收起来了(变量超出作用域就不存在了)。就像下面的代码:
#include <stdio.h>
void autoStorageExample() {
int autoVariable = 10; // 自动存储类别变量
printf("自动变量的值: %d\n", autoVariable);
autoVariable = 20;
printf("修改后的自动变量的值: %d\n", autoVariable);
}
int main() {
autoStorageExample();
autoStorageExample();
return 0;
}
在上述代码中, autoVariable 是在函数 autoStorageExample 内部定义的自动存储类别变量。每次函数调用时都会重新创建并初始化,函数执行结束后销毁。所以每次调用函数时, autoVariable 的值都是重新开始计算的。
——静态存储类别:包括静态局部变量和静态全局变量。静态局部变量在函数多次调用之间保留其值,静态全局变量的作用域仅限于定义它的文件。
比如家里的大冰箱,一直都在那,不会因为你今天不做饭就消失了。例如:
#include <stdio.h>
void staticStorageExample() {
static int staticLocalVariable = 0; // 静态局部变量
printf("静态局部变量的值: %d\n", staticLocalVariable);
staticLocalVariable++;
}
int main() {
staticStorageExample();
staticStorageExample();
staticStorageExample();
return 0;
}
在这个示例中, staticLocalVariable 是静态局部变量。它在函数多次调用之间保留其值。第一次调用函数时,它被初始化为 0 ,然后每次调用都会在上一次的值基础上加 1 。
对于静态全局变量,假设在文件 file1.c 中定义:
// file1.c
static int staticGlobalVariable = 50; // 静态全局变量
void printStaticGlobal() {
printf("静态全局变量的值: %d\n", staticGlobalVariable);
}
由于它是静态全局变量,其作用域仅限于 file1.c 这个文件,在其他文件中无法直接访问和使用。
——外部存储类别:也称为全局变量,作用域从定义位置开始到整个程序结束,可被多个文件共享。
就像是小区门口的公告栏,整个小区的人都能看到和使用。
假设有两个文件: file1.c 和 file2.c
在 file1.c 中定义一个外部变量:
#include <stdio.h>
int globalVar = 10; // 定义外部变量
void functionInFile1() {
printf("Value of globalVar in file1: %d\n", globalVar);
}
在 file2.c 中使用这个外部变量:
#include <stdio.h>
extern int globalVar; // 声明使用外部变量
void functionInFile2() {
globalVar = 20;
printf("Value of globalVar in file2: %d\n", globalVar);
}
在这个例子中, globalVar 是一个外部变量,其作用域从定义位置开始到整个程序结束,并且可以在多个文件中被共享和使用。
——寄存器存储类别:建议编译器将变量存储在 CPU 的寄存器中,以加快访问速度。但编译器可自行决定是否真的将其存储在寄存器中。
#include <stdio.h>
void registerExample() {
register int num = 5; // 建议编译器将 num 存储在寄存器中
printf("Value of num: %d\n", num);
}
在这个示例中,我们使用 register 关键字建议编译器将 num 变量存储在寄存器中以加快访问速度。但最终是否存储在寄存器中由编译器决定。
注:它们在实际开发中如何选择,分别的作用域是?
在实际开发中,选择存储类别的依据通常取决于具体的需求和程序的逻辑结构。
自动存储类别:适用于在函数内部临时使用、不需要在函数调用之间保留值的变量。其作用域仅限于定义它的函数内部。
静态存储类别:中的静态局部变量,适用于需要在函数多次调用之间保留状态或值的情况。其作用域也在定义它的函数内部。静态全局变量则适用于仅在定义它的文件内共享和使用的全局数据,作用域限于定义所在的文件。
外部存储类别:常用于在多个文件之间共享数据。当需要在多个函数或多个文件中访问和修改相同的全局数据时,会选择外部存储类别。其作用域从定义位置开始到整个程序结束,并可以被多个文件共享。
寄存器存储类别:适用于频繁访问、对性能要求较高的变量。但由于编译器最终决定是否将其存储在寄存器中,所以不能完全依赖它来确保性能提升。其作用域规则与自动存储类别相同,即定义所在的函数内部。
总之,存储类别的选择应综合考虑程序的性能、可维护性、代码的结构和逻辑等多方面因素。
2. 内存动态管理
在 C 语言中,我们可以根据需要在运行时动态地分配和释放内存。这就像是家里来客人了,临时加几个凳子,客人走了再把凳子收起来。
使用 malloc 函数来分配内存, free 函数来释放内存。
例如:
int *p = (int *)malloc(sizeof(int)); // 分配一个整数大小的内存
*p = 20; // 给这块内存赋值
free(p); // 用完了释放内存
(1)为什么要动态内存分配
在程序运行时,有时无法提前确定所需内存的大小。例如,处理用户输入的不确定长度的字符串、处理数量不固定的对象等。动态内存分配允许程序在运行时根据实际需求获取所需的内存空间,提高了程序的灵活性和适应性。
(2)动态内存函数
1 . malloc
malloc 函数用于在内存的动态存储区中分配一块指定大小的连续内存空间。其函数原型为 void *malloc(size_t size) ,返回值为指向分配内存的指针,如果分配失败则返回 NULL 。
例:
int *p = (int *)malloc(sizeof(int) * 10); // 分配 10 个整数大小的内存空间
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
2. free
free 函数用于释放由 malloc 、 calloc 或 realloc 分配的动态内存。其函数原型为 void free(void *p) 。
例:
int *p = (int *)malloc(sizeof(int) * 10);
// 使用完内存后释放
free(p);
ptr = NULL; // 为了避免悬空指针,将指针置为 NULL
3. calloc
calloc 函数用于在内存的动态存储区中分配 n 个长度为 size 的连续内存空间,并将每一位初始化为 0 。其函数原型为 void *calloc(size_t n, size_t size) 。
例:
int *p = (int *)calloc(10, sizeof(int)); // 分配 10 个初始化为 0 的整数空间
4. realloc
realloc 函数用于重新分配已分配内存的大小。其函数原型为 void *realloc(void *p, size_t size) 。
例:
int *p = (int *)malloc(sizeof(int) * 10);
p = (int *)realloc(p, sizeof(int) * 20); // 将内存空间扩大到 20 个整数大小
(3) 常见动态内存错误
1. 对 NULL 指针的解引用操作
当 malloc 等函数分配内存失败返回 NULL 时,如果直接对该 NULL 指针进行解引用操作,会导致程序崩溃。
例:
int *p = (int *)malloc(sizeof(int) * 10);
if (p == NULL) {
// 错误:直接对 NULL 指针进行解引用
*p = 5;
}
2. 对动态开辟空间的越界访问
访问超出动态分配内存的边界,可能导致不可预测的结果,甚至破坏其他数据。
例:
int *p = (int *)malloc(sizeof(int) * 10);
for (int i = 0; i < 20; i++) { // 越界访问
p[i] = i;
}
3.对非动态开辟内存使用 free 释放
试图释放不是通过动态内存分配函数获取的内存,会导致未定义的行为。
例:
int num = 5;
free(&num); // 错误:num 不是动态分配的内存
4. 使用 free 释放一块动态开辟内存的一部分
free 函数只能用于释放整块通过动态分配获取的内存,不能只释放其中的一部分。
例:
int *p = (int *)malloc(sizeof(int) * 10);
free(p + 5); // 错误:不能只释放一部分
5. 对同一块动态内存多次释放
多次释放同一块动态内存会导致程序崩溃或不可预测的行为。
例:
int *p = (int *)malloc(sizeof(int) * 10);
free(p);
free(p); // 错误:多次释放
6 .动态开辟内存忘记释放(内存泄漏)
在使用完动态分配的内存后,如果没有及时释放,会导致内存资源的浪费,最终可能使程序因内存不足而崩溃。
例:
void someFunction() {
int *p = (int *)malloc(sizeof(int) * 10);
// 没有释放内存
}
避免动态内存管理中常见错误的方法:
1. 配对使用 malloc / free 、 new / delete :确保每次使用动态分配内存的操作(如 malloc 或 new )都有相应的释放操作(如 free 或 delete ),并且两者要正确匹配。
2. 检查返回值 :在进行内存分配操作后,检查返回值是否为 NULL ,以确定内存分配是否成功。
3. 避免内存泄漏 :释放不再使用的动态分配内存,特别是在函数结束或对象不再需要时。
4. 不要重复释放 :对同一块已释放的内存再次进行释放操作会导致未定义的行为。
5. 注意内存越界 :在使用动态分配的内存时,确保访问不超出分配的边界,否则可能会导致程序崩溃或数据损坏。
6. 正确处理数组 :使用 new[] 分配数组时,要用 delete[] 来释放;使用 malloc 分配数组时,用 free 释放。
7. 异常处理 :在可能出现异常的代码段中,确保在异常发生时也能正确释放已分配的内存。
8. 清晰的内存管理策略 :在复杂的程序中,制定明确的内存管理策略和规范,以便所有开发人员遵循。
总之,在进行动态内存管理时,需要保持谨慎和细心,严格遵循相关的编程规范和原则。