• 【《C Primer Plus》读书笔记】第12章:存储类别、链接和内存管理


    12.1 存储类别

    一些概念和术语:

    • 对象:存储数据的一块内存
    • 标识符:一个名称,软件指定硬件内存中的对象的方式
    • 左值:指定对象(即指定内存位置上的内容)的表达式

    12.1.1 作用域

    作用域是描述程序中可访问标识符的区域。

    一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。

    是用一对花括号括起来的代码区域,定义在块中的变量具有块作用域,变量的可见范围是从定义处到包含该定义的块的末尾。

    注:虽然函数的形参定义在函数的花括号外,但是它们属于函数体这个块。

    12.1.2 作用域规则

    任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。

    C 语言中有三个地方可以声明变量:

    1. 在函数或块内部的局部变量
    2. 在所有函数外部的全局变量
    3. 在形式参数的函数参数定义中

    让我们来看看什么是局部变量、全局变量和形式参数。

    局部变量

    在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。下面是使用局部变量的实例。在这里,所有的变量 a、b 和 c 是 main() 函数的局部变量。

    #include 
     
    int main ()
    {
      /* 局部变量声明 */
      int a, b;
      int c;
     
      /* 实际初始化 */
      a = 10;
      b = 20;
      c = a + b;
     
      printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
     
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    全局变量

    全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。

    全局变量可以被任何函数访问。也就是说,全局变量在声明后整个程序中都是可用的。下面是使用全局变量和局部变量的实例:

    #include 
     
    /* 全局变量声明 */
    int g;
     
    int main ()
    {
      /* 局部变量声明 */
      int a, b;
     
      /* 实际初始化 */
      a = 10;
      b = 20;
      g = a + b;
     
      printf ("value of a = %d, b = %d and g = %d\n", a, b, g);
     
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注:在程序中,局部变量和全局变量的名称可以相同,但是在函数内,如果两个名字相同,会使用局部变量值,全局变量不会被使用。

    形式参数

    函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用。下面是一个实例:

    #include 
     
    /* 全局变量声明 */
    int a = 20;
     
    int main ()
    {
      /* 在主函数中的局部变量声明 */
      int a = 10;
      int b = 20;
      int c = 0;
      int sum(int, int);
     
      printf ("value of a in main() = %d\n",  a);
      c = sum( a, b);
      printf ("value of c in main() = %d\n",  c);
     
      return 0;
    }
     
    /* 添加两个整数的函数 */
    int sum(int a, int b)
    {
        printf ("value of a in sum() = %d\n",  a);
        printf ("value of b in sum() = %d\n",  b);
     
        return a + b;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    当上面的代码被编译和执行时,它会产生下列结果:

    value of a in main() = 10
    value of a in sum() = 10
    value of b in sum() = 20
    value of c in main() = 30
    
    • 1
    • 2
    • 3
    • 4

    初始化局部变量和全局变量

    当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化,如下所示:

    在这里插入图片描述

    正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值。

    12.1.3 链接

    C变量有3种链接属性:外部链接、内部链接或无链接。

    • 外部链接:可以在多文件程序使用
    • 内部链接:只能在一个翻译单元(即一个源代码文件和它所包含的头文件)中使用
    • 无链接:具有块作用域、函数作用域或函数原型作用域的变量都是无链接变量

    具有文件作用域的变量可以是外部链接或内部链接。

    程序员用“内部链接的文件作用域”描述仅限于一个翻译单元的作用域,简称为“文件作用域”;而用“外部链接的文件作用域”描述可以延伸至其他翻译单元的作用域。,简称为“全局作用域”或“程序作用域”。

    例如:

    int giants = 5; // 文件作用域,外部链接
    static int dodgers = 3; // 文件作用域,内部链接
    int main()
    {
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    该文件和同一程序的其他文件都可以使用变量giants,但是变量dodgers是私有的,只供该文件的任意函数使用。

    12.1.4 存储期

    作用域和链接描述了标识符的可见性。

    存储期描述了通过这些标识符访问的对象的生存期。C对象有4种存储期:

    1. 静态存储期:文件作用域变量具有静态存储期,在程序的执行期间一直存在
    2. 线程存储期:用于并发程序设计,从被声明时到线程结束一直存在
    3. 自动存储期:块作用域的变量通常具有自动存储期
    4. 动态分配存储期

    注:块作用域变量也能拥有静态存储期。创建这样的变量需要把它声明在块中,且加上关键字static。

    12.1.5 C语言的5种存储类别

    C使用作用域、链接和存储期为变量定义了5种存储类别。

    自动变量auto

    属于自动存储类别的变量具有自动存储期、块作用域且无链接。

    关键字auto是存储类别说明符。

    注:默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。

    寄存器变量register

    寄存器变量存储在CPU的寄存器(最快的可用内存)中,具有块作用域、无链接和自动存储期。

    变量声明为register类别与直接命令相比更像是一种请求,编译器必须根据寄存器的数量衡量请求,或者直接忽略请求。

    块作用域的静态变量static

    块作用域的静态变量具有块作用域、无链接和静态存储期。

    静态变量的静态指的是该变量在内存中原地不动,并不是它的值不变。

    外部链接的静态变量extern

    外部链接的静态变量具有文件作用域、外部链接和静态存储期。

    使用关键字extern在该文件中声明这种变量。

    内部链接的静态变量extern

    内部链接的静态变量具有文件作用域、内部链接和静态存储期。

    使用关键字extern在该文件中声明这种变量。

    12.2 随机数函数——rand()

    C 库函数 int rand(void) 返回一个范围在 0 到 RAND_MAX 之间的伪随机数。

    RAND_MAX 是一个常量,它的默认值在不同的实现中会有所不同,但是值至少是 32767。

    下面是 rand() 函数的声明:

    int rand(void)
    
    • 1

    该函数返回一个范围在 0 到 RAND_MAX 之间的整数值。

    实例:

    #include 
    #include 
    #include 
     
    int main()
    {
       int i, n;
       time_t t;
       
       n = 5;
       
       /* 初始化随机数发生器 */
       srand((unsigned) time(&t));
     
       /* 输出 0 到 49 之间的 5 个随机数 */
       for( i = 0 ; i < n ; i++ ) {
          printf("%d\n", rand() % 50);
       }
       
      return(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    让我们编译并运行上面的程序,这将产生以下结果:

    38
    45
    29
    29
    47
    
    • 1
    • 2
    • 3
    • 4
    • 5

    12.3 分配内存

    C 语言为内存的分配和管理提供了几个函数。这些函数可以在 头文件中找到。

    在这里插入图片描述

    注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

    12.3.1 动态分配内存

    实例

    #include 
    #include 
    #include 
     
    int main()
    {
       char name[100];
       char *description;
     
       strcpy(name, "Zara Ali");
     
       /* 动态分配内存 */
       description = (char *)malloc( 200 * sizeof(char) );
       if( description == NULL )
       {
          fprintf(stderr, "Error - unable to allocate required memory\n");
       }
       else
       {
          strcpy( description, "Zara ali a DPS student in class 10th");
       }
       printf("Name = %s\n", name );
       printf("Description: %s\n", description );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    当上面的代码被编译和执行时,它会产生下列结果:

    Name = Zara Ali
    Description: Zara ali a DPS student in class 10th
    
    • 1
    • 2

    上面的程序也可以使用 calloc() 来编写,只需要把 malloc 替换为 calloc 即可,如下所示:

    calloc(200, sizeof(char));
    
    • 1

    当动态分配内存时,程序员有完全控制权,可以传递任何大小的值。而那些预先定义了大小的数组,一旦定义则无法改变大小。

    12.3.2 重新调整内存的大小

    通过调用函数 realloc() 来增加或减少已分配的内存块的大小。

    实例:

    #include 
    #include 
    #include 
     
    int main()
    {
       char name[100];
       char *description;
     
       strcpy(name, "Zara Ali");
     
       /* 动态分配内存 */
       description = (char *)malloc( 30 * sizeof(char) );
       if( description == NULL )
       {
          fprintf(stderr, "Error - unable to allocate required memory\n");
       }
       else
       {
          strcpy( description, "Zara ali a DPS student.");
       }
       /* 假设您想要存储更大的描述信息 */
       description = (char *) realloc( description, 100 * sizeof(char) );
       if( description == NULL )
       {
          fprintf(stderr, "Error - unable to allocate required memory\n");
       }
       else
       {
          strcat( description, "She is in class 10th");
       }
       
       printf("Name = %s\n", name );
       printf("Description: %s\n", description );
     
       /* 使用 free() 函数释放内存 */
       free(description);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    当上面的代码被编译和执行时,它会产生下列结果:

    Name = Zara Ali
    Description: Zara ali a DPS student.She is in class 10th
    
    • 1
    • 2

    可以尝试一下不重新分配额外的内存,strcat() 函数会生成一个错误,因为存储 description 时可用的内存不足。

    12.3.3 释放内存

    调用函数 free() 来释放内存。

    声明:

    void free(void *address);
    
    • 1

    该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

    malloc()或calloc()要与free()配套使用,否则可能造成内存泄漏。

    12.4 ANSI C类型限定符

    我们通常用类型和存储类别来描述一个变量。

    12.4.1 const类型限定符

    以const关键字声明的对象,其值不能通过赋值或递增、递减来修改。

    12.4.2 volatile类型限定符

    volatile限定符告知计算机,代理(而不是变量所在的程序)可以改变该变量的值。

    12.4.3 restrict类型限定符

    restrict关键字允许编译器优化某部分代码以更好地支持计算。它只能用于指针,表明该指针是访问数据对象的唯一且初始的方式。

    12.4.4 _Atomic类型限定符

    _Atomic关键字声明一个原子类型的变量,当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。

  • 相关阅读:
    产品软文怎么写?掌握这几个技巧你也能写
    flutter vscode gradle 配置
    postgresql字符串处理的函数
    cocos 2.4*版本的基础使用笔记分享(一)
    Linux中使用Docker安装MySQL5.7(CentOS)
    pycharm 中package, directory, sources root, resources root的区别
    怎样把1.ts-10.ts的文件拼接成一个MP4文件
    AndroidStudio使用高德地图API获取手机定位
    洛谷 P1548 [NOIP1997 普及组] 棋盘问题
    Vue2【webpack 的基本使用、webpack 中的插件、webpack 中的 loader、打包发布、Source Map】
  • 原文地址:https://blog.csdn.net/ProgramNovice/article/details/127752301