• 基本存储类别和动态内存分配


    本文部分内容参考了C Primer Plus(sixth edition)一书

    存储类别和内存分布

      简单介绍一下变量的存储类别和它的内存分布,我们先通过一张表来了解一些基本术语:

    术语 解释 举例
    作用域  描述程序中可访问的标识符的区域 块作用域,函数作用域,函数原型作用域,文件作用域
    链接 变量属于哪一部分私有 外部链接(全局可见),内部链接(单一文件可见),无链接(块内可见)
    存储期 描述通过标识符访问对象的生命期 静态存储期,线程存储期,自动存储期,动态分配存储期
    翻译单元 一个源文件包含多个头文件,编译时当作一个翻译单元,作一个文件 头文件包含在源代码文件内

      〉〉块指的是一对花括号括起来的代码。C99之后,块也可以是循环语句+循环体(单一语句)。具有块作用域的变量只能在块内可见。C99之前,变量只允许声明在块的开头。无链接。

      〉〉函数作用域仅用于goto标签,一个标签首次出现在函数的内层块中,它的作用域将延伸直整个函数。无链接。

      〉〉函数原型作用域:声明函数原型时形参的作用域,仅限于原型。无链接。注意,如果在某一函数int fun的原型中声明变长数组,应该这样声明int fun(int n, int m, int vla[n][m]);

      〉〉文件作用域:定义在函数外,从定义处到文件末尾均可见。具有内部和外部链接。

      〉〉外部链接:具有外部链接的变量在程序的所有文件中都可见。例如,main.c中定义了一个外部链接变量int var;在lock.c中就要用extern int x;来声明使用。

      〉〉内部链接:具有该链接的变量只在该文件可见。例如:static int var; var使用时不必加上像外部链接变量的声明。

      〉〉无链接:不具有文件作用域的变量无链接。

      〉〉静态存储期:生命(在内存中存在时间):程序开始到结束。

      〉〉线程存储期:生命:从线程开始到结束。

      〉〉自动存储期:生命以块为参照,离开块即消失。

      〉〉动态分配存储期:由程序员管理。

      总结表如下:

    存储类别 存储期 作用域 链接 声明方式
    自动 自动 块内
    静态外部链接 静态 文件(整个程序) 外部 所有函数外,在另一文件使用前需要用extern再次声明
    静态内部链接 静态 文件(单一翻译单元) 内部 所有函数外,用static声明
    静态无链接 静态 块内,用static声明

      此外还有寄存器变量,由于这种变量实用性不高,不讨论。

      在内存中,静态数据(包括常量)占用一个区域,自动数据占用另一个区域,动态分配的数据占第三个区域(内存堆)。

    动态内存分配函数malloc()和释放函数free()

      在C99标准发布之前,我们不能使用变量作为声明数组的元素个数。如果我们要创建一个自定义长度的数组,就不能使用原来定义普通静态数组的方法。那么该怎么办呢?这时候就要通过动态分配内存来声明动态数组了。

      malloc()函数原型在stdlib.h文件中。它返回一个指向void类型的指针,这个指针指向分配内存的首地址。下面这段代码分配一个具有10个int类型元素的动态数组,用指针ptr指向这个数组的首元素。

    int *ptr, n = 10;
    ptr = (int *)malloc( (size_t)n * sizeof(int) );

      malloc()的括号里应填入所需要分配的内存大小,以字节为单位,类型是size_t。为了提供程序的可移植性,我们用了sizeof()运算符来获取当前系统int类型的变量所占用的字节大小。建议大家使用强制类型转换来使用指向void类型的指针。

      但是,如果分配内存失败了,会怎么样?malloc()函数会返回一个指向NULL的指针,如果程序在内存分配失败后继续使用内存,就会导致程序异常终止。所以,在分配内存之后,需要检查一下成功分配了没有。

    int *ptr, n = 10;
    ptr = (int *)malloc( (size_t)n * sizeof(int) );
    if(ptr == NULL)
        exit(EXIT_FAILURE);

    或者:

    int *ptr, n = 10;
    if( !( ptr = (int *)malloc( (size_t)n * sizeof(int)) ) )
        exit(EXIT_FAILURE);

    都是很好的方法。

      我们知道,malloc()分配的内存具有动态分配存储期,由程序员控制变量的生命期。如果程序结束前没有及时地释放它,程序结束之后就仍然存在。更糟糕的是,如果程序运行中,指向该块内存首地址的指针全部被销毁,那么这块内存就永远无法正确访问,也就无法释放这块内存,这就会导致内存泄露。内存泄露问题很严重,会导致系统停止运行!因为没有空余的内存空间,所以,在分配内存之后如果不需要再次使用,一定要释放!警惕不小心更改指向动态分配内存空间的指针!释放动态分配的内存,我们用free()函数。

    free(ptr);

      这行代码就释放了之前用malloc()分配的内存空间。

      如果ptr所指向的内存块不是通过malloc(),calloc()或realloc()分配的,或者指向一个已经删除的空间,该结果未定义!

    动态数组排序实例

      动态数组和变长数组(C99)类似,但不表示数组的长度在程序运行中是可变的,只是说可以用变量来表示数组长度。

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <conio.h>
     4 
     5 int main(int argc, char * argv[]){
     6     int * ptr;
     7     int key, size, i, j;
     8 
     9     printf("请输入动态数组大小:");
    10     while( !( scanf("%d",&size) ) || size < 0 ){
    11         printf("输入错误,请重新输入。
    :");
    12         while( getchar() != '
    ' );//调整输入缓冲区
    13     }
    14 
    15     if( !( ptr = (int *)malloc( (size_t)size * sizeof(int)) ) ){//分配内存
    16         printf("内存请求失败!");
    17         exit(EXIT_FAILURE);
    18     }
    19 
    20     printf("请依次输入数组元素内容:
    ");
    21     for(i = 0; i < size; i++)
    22         scanf("%d",&ptr[i]);
    23     while( getchar() != '
    ' );
    24 
    25     for(i = 1; i < size; i++){//插入排序
    26         j = i - 1;
    27         key = ptr[i];
    28         while( j >= 0 && ptr[j] > key){
    29             ptr[j + 1] = ptr[j];
    30             j--;
    31         }
    32         ptr[j + 1] = key;
    33     }
    34 
    35     printf("各元素排序后输出如下:");
    36     for(i = 0; i < size; i++)
    37         printf("%d  ",ptr[i]);
    38 
    39     free(ptr); //释放内存,重中之重
    40 
    41     _getch();
    42     return 0;
    43 }
    动态数组排序实例

    运行结果:

    动态内存分配calloc()函数

      分配内存,除了使用malloc()函数外,还可以使用calloc()函数。calloc()函数和malloc()函数最大的区别就是:calloc()函数在分配内存之后,会把块中的所有位置0(某些硬件系统中,不是把所有的位都置0来表示浮点值0)。

      calloc()函数接受两个size_t类型的参数。第一个参数表明存储单元的数量,第二个参数表示每个存储单元的大小(以字节为单位)。

      现在如果我们要申请一个元素个数为n个的int类型动态数组ar,我们可以这样做:

    int * ar;
    int n = 10;
    ar = (int *)calloc((size_t)n, sizeof(int) ); 
    if( ar == NULL )
        exit(EXIT_FAILURE);

      如果分配内存成功,此时,ar数组里的每一个元素都为0。

      同样,我们可以使用free()来释放calloc()分配的内存:

    free(ar);

    补充:realloc()函数

      原型:void *realloc(void * ptr, size_t size);

      简单描述:把ptr指向的内存空间更改为size字节,size字节内的内存块内容不变。该函数返回块的位置。如果不能重新分配空间,则返回NULL。如果ptr为NULL,其行为与调用带size参数的malloc()相同。如果size为0且ptr不是NULL,其行为与调用带ptr参数的free相同。

      不推荐大量使用这个函数,因为当不能重新分配空间时,函数返回NULL,原来已分配的内存块地址丢失,会造成内存泄露。尽管在很多时候这个函数非常方便。如果你需要使用这个函数,请先保存原来内存块的地址!

  • 相关阅读:
    Mybatis动态SQL
    Mybatis05__#和$的区别
    Mybatis04__输入参数/输出参数
    数据库中的字段和实体类中的字段不一致
    Mybatis中常用注解
    Mybatis03__配置文件简介
    Mybatis实现增、删、改操作时返回主键
    Mybatis04__CURD
    Mybatis02__Mybatis搭建
    lxrunoffline的使用
  • 原文地址:https://www.cnblogs.com/mrblug/p/5757059.html
Copyright © 2020-2023  润新知