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

12.1 存储类别

一些概念和术语:

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

12.1.1 作用域

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

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

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

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

12.1.2 作用域规则

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

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

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

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

局部变量

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

#include <stdio.h>
 
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;
}

全局变量

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

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

#include <stdio.h>
 
/* 全局变量声明 */
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;
}

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

形式参数

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

#include <stdio.h>
 
/* 全局变量声明 */
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;
}

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

value of a in main() = 10
value of a in sum() = 10
value of b in sum() = 20
value of c in main() = 30

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

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

在这里插入图片描述

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

12.1.3 链接

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

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

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

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

例如:

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

该文件和同一程序的其他文件都可以使用变量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)

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

实例:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
 
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);
}

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

38
45
29
29
47

12.3 分配内存

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

在这里插入图片描述

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

12.3.1 动态分配内存

实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
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 );
}

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

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

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

calloc(200, sizeof(char));

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

12.3.2 重新调整内存的大小

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

实例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
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);
}

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

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

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

12.3.3 释放内存

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

声明:

void free(void *address);

该函数释放 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关键字声明一个原子类型的变量,当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值