重温C语言(6)之存储类别

Feb 5, 2020


对象

被存储的每一个值都占用一定的物理内存,这样的内存称为对象。

int a = 73;

a 是一个标志符。指定了硬件内存中的对象的方式,并提供了存储在对象中的值。

int * p = &a;

p 是一个标志符,指定了储存地址的对象。但是 *p 不是标志符,是个表达式, 它指向了一个对象。

作用域

作用域描述程序中可访问标识符的区域。 一个C变量的作用域可以是块作用域、函数作用域、函数原型作用域或文件作用域。块是用一对花括号括起来的代码区域。

函数原型作用域: int a(int a, double b) 小括号扩起来的就是函数原型作用域。 文件作用域即声明的变量在所在文件从定义处到文件末尾都可用,这个变量也称为全局变量。

int a = 10; // 本文件和同程序的其他文件都可以使用 a变量
static b = 22;  // 只有本文件可以访问 b
int main(){
}

如果文件要使用其他文件中的外部变量,则必须用 extern 声明该变量。

extern char Name;

存储期

静态存储期

存储在静态内存中存储地址不变但值可以变,在程序的执行期间一直存在,关键字 static 声明的变量变量具有静态存储期。

线程存储期

自动变量

默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。可以使用 auto 关键字来显示的定义它。 变量具有自动存储期意味着,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失。原来该变量占用的内存位置现在可做他用。 自动变量不会初始化,除非显式初始化它。

int a; // a 变量的值是之前占用分配给 a 的空间中的任意值(如果有的话),它不会是0
int b = 10;

寄存器变量

变量通常储存在计算机内存中。如果幸运的话,寄存器变量储存在CPU的寄存器中,或者概括地说,储存在最快的可用内存中。 由于寄存器变量储存在寄存器而非内存中,所以无法获取寄存器变量的地址。 用 register 声明寄存器变量,编译器必须根据寄存器或最快可用内存的数量衡量你的请求,或者直接忽略你的请求,这个时候寄存器变量会成为普通变量,不管怎样都不能使用地址运算符。

register int a;

存储类别说明符

  1. auto说明符表明变量是自动存储期,只能用于块作用域的变量声明中;
  2. register说明符也只用于块作用域的变量,它把变量归为寄存器存储类别,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取;
  3. 用static说明符创建的对象具有静态存储期,载入程序时创建对象,当程序结束时对象消失;
  4. extern说明符表明声明的变量定义在别处;

注意:外部函数可以被其他文件的函数访问,但是静态函数只能用于其定义所在的文件。

doubleg amma(double);/*该函数默认为外部函数*/
static double beta(int, int);
extern double delta(double, int);

在同一个程序中,其他文件中的函数可以调用gamma()和delta(),但是不能调用beta(),因为以static存储类别说明符创建的函数属于特定模块私有。

随机数

rand()函数生成随机数字。这个函数取值范围是0RNAD_MAX之间,RAND_MAX被定义在stdlib.h中,其值通常是INT_MAX。

下面实现一个自己的随机数程序: myrand.c 文件:

static unsigned long int next = 1;

unsigned int myrand(void) {
    // 生成伪随机数的魔法公示
    next = next * 1103515245 + 12345;
    return (unsigned int)(next / 65536) % 32768;

}

main.c 文件:

#include <stdio.h>
#include "myrand.c"
extern unsigned int myrand(void);
int main() {
    for (int i = 0; i < 10; ++i) {
        printf("%d\n", myrand());
    }
}

分配内存

malloc()

malloc() 函数可以在程序运行时分配更多的内存,该函数接受一个参数:所需的内存字节数。malloc()函数会找到合适的空闲内存块,这样的内存是匿名的,它会返回动态分配内存块的首字节地址。

如果malloc()分配内存失败,将返回空指针。

double * ptd;
ptd = (double *) malloc(30*sizeof(double));

上面代码为30个double类型的值请求内存空间,并设置ptd指向该位置。注意,指针ptd被声明为指向一个double类型,而不是指向内含30个double类型值的块。 动态分配的内存数量只会增加,除非用free()进行释放。 free() 函数的参数是指向malloc() 分配的内存的指针,用来释放这块内存。

calloc()

long * newmem;
newmem = (long *)calloc(100, sizeof(long));

calloc()malloc()类似, 返回指向void的指针。calloc()函数接受两个无符号整数作为参数(ANSI规定是size_t类型)。第1个参数是所需的存储单元数量,第2个参数是存储单元的大小(以字节为单位)。它会把分配的块所有位都设为0.

也需要用 free() 函数进行释放。

存储类别和动态内存分配有何联系?

静态存储类别所用的内存数量在编译时确定,该类别的变量在程序开始执行时被创建,在程序结束时被销毁。

自动存储类别的变量在程序进入变量定义所在块时存在,在程序离开块时消失。

动态分配的内存在调用malloc()或相关函数时存在,在调用free()后释放。使用动态内存通常比使用栈内存慢。

ANSIC类型限定符

const

声明对象的值可以初始化但是不能被修改。

volatile

volatile限定符告知计算机,代理(而不是变量所在的程序)可以改变该变量的值。通常,它被用于硬件地址以及在其他程序或同时运行的线程中共享数据。例如,一个地址上可能储存着当前的时钟时间,无论程序做什么,地址上的值都随时间的变化而改变。

restrict

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

_Atomic

并发程序设计把程序执行分成可以同时执行的多个线程,。当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。_Atomic 用来声明原子类型的变量。