C语言 存储管理

目录

 

 

一,存储管理

1.存储类别

         注:它们在实际开发中如何选择,分别的作用域是?

 

 2. 内存动态管理

 

 (1)为什么要动态内存分配

 (2)动态内存函数

   (3) 常见动态内存错误

 

 

 


 

 

一,存储管理

 

 

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. 清晰的内存管理策略 :在复杂的程序中,制定明确的内存管理策略和规范,以便所有开发人员遵循。
 
总之,在进行动态内存管理时,需要保持谨慎和细心,严格遵循相关的编程规范和原则。
 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值