C 基础 - 存储类别、链接与内存管理
一、存储类别
C语言提供几种存储方法,来存放在内存中变量的值。
从硬件方面去看,被存储的每一个值都会占用一定的物理内存,C语言把这样的一块内存叫对象(Object)。
从软件方面去看,程序需要一种方法访问对象。声明变量是一种方法。
一个变量具有不同的存储类别,存储类别是指具有不同的 存储期(Storage duration)、作用域(scope)、 链接(linkage)。
有三个地方可以用于存储变量:普通内存、运行时堆栈、硬件寄存器。
基本概念: 作用域, 链接, 存储周期
1. 作用域: 描述程序中可访问标识符的区域。一个C变量作用域
* 块作用域
* 函数作用域
* 函数原型作用域:指 int add(a, b); 中 a 与 b 的形参。
* 文件作用域(全局变量):变量定义在函数的外面。
2. C变量有3种链接属性:
* 外部链接(external): 可以在多个文件程序中使用。
* 内部链接(static): 只能在一个翻译单元中使用。
* 无链接: 具有块作用域、函数作用域、函数原型作用域的变量都是无链接变量,表示变量的可见范围属于块、函数私有。
int giants = 5; // 文件作用域,外部链接, 其他文件可以使用 static int dog = 4; // 文件作用域, 内部链接, 文件私有 // static 关键字 描述了文件作用域的 链接特性与存储周期无关 int main(void) { ... static int max(int x, int y); ... }
3. 存储期:作用域与链接描述了标识符的可见性,存储期描述了通过这些标识符访问的对象的生存期。
C对象有四种存储期:
* 静态存储期: 程序执行期间一直存在,无论是内部链接还是外部链接的文件作用域变量都是静态存储期。
* 线程存储期: 用于并发程序设计,线程结束之前一直存在。
* 自动存储期: 块作用域变量具有自动存储期,为了让块作用域变量具有静态存储期,在变量前加 static 关键字
* 动态分配存储期:
4. 五种存储类别
存储类别 | 存储期 | 作用域 | 链接 | 声明方式 |
自动(automatic) | 自动 | 块 | 无 | 块内 |
寄存器(register) | 自动 | 块 | 无 | 块内,关键字register |
静态外部链接(static with external linkage) |
静态 | 文件 | 外部 | 所有函数外 |
静态内部链接(static with interanl linkage) |
静态 | 文件 | 内部 | 所有函数外,关键字static |
静态块作用域(static with no linkage) |
静态 | 块 | 无 | 块内,傅关键字 static |
例如如下
#include <stdio.h> int a; // 静态外部链接 (全局变量,文件作用域,外部链接,其他文件都可以使用) static int d; // 静态内部链接 (文件作用域,内部链接,仅限本文件内函数使用) extern char str; // 字符str是定义在其他文件中的全局变量
main() { int b; // 自动变量 (main函数结束后,消失) static int c; // 静态块作用域 (main函数结束后,消失) retister int e; // 寄存器变量,无法获取寄存器变量的地址 ...... } print_r() { int r; // 自动变量(Print_r函数调用时分配,结束后,消失) static int e; // 静态块作用域,该变量的地址在内存中不会改变 }
程序案例:
/* * 此程序的功能用来说明变量的5种存储类别 * storage.c */ #include <stdio.h> void report_count(); // 函数原型 void accumulate(int k); // 函数原型 int count = 0; // 全局变量, 外部链接 int main(void) { int value; // 自动变量 register int i; // 寄存器变量 printf("Enter a positive integer (0 to quit): "); while (scanf("%d", &value) == 1 && value > 0) { ++count; // 引用全局变量 for (i = value; i >= 0; i--) accumulate(i); // 变量i是块变量,调用accumulate函数 printf("Enter a positive integer (0 to quit): "); } report_count(); return 0; } void report_count() { printf("Loop executed %d times\n", count); }
问题:关键字static作用是什么?
在C语言中,关键字 static 有三个明显的作用:
首先:一旦声明为静态变量,在编译时刻开始永远存在,不受作用域范围约束
* 如果是局部静态变量,则此静态变量只能在局部作用域内使用,超出范围不能使用,但是它确实还占用内存,还存在.
* 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
* 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
/* * 此程序的功能用来说明变量的5种存储类别 * storage_accumulate.c */ #include <stdio.h> extern int count; // 引用全局变量 static int total = 0; // 全局静态变量,本文件内有效 void accumulate(int k); // 函数原型 void accumulate(int k) // k是块作用域 { static int subtotal = 0; // 块静态变量 if (k <= 0) { printf("loop cycle: %d\n", count); // 引用全局变量 printf("subtotal: %d; total: %d\n", subtotal, total); subtotal = 0; } else { subtotal += k; total += k; } }
编译命令:
gcc -o storage storage.c storage_acculate.c
关键字const有什么含意?
1) 只读。
2)使用关键字const也许能产生更紧凑的代码。
3)使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。
Dan Saks在他的文章里完全概括了const的所有用法,(译者:Embedded Systems Programming)
下面的声明都是什么意思?
const int a; // 定义常整形数 a int const a; // 定义常整形数 a
const int *a; // a是一个指向常整形数的指针,值不可改,指针a可改 // const 在*之前,表示其值不可以改
int * const a; // a是一个指向整型数的常指针, 指针指向的整形数可以修改,指针不可改 // const 在*之后, 表示指针不可改
int const *a const; // a是一个指向常整型数的常指针, 指针与值都不可改
关键字volatile有什么含意? 并给出三个不同的例子?
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。
精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
下面是volatile变量的几个例子:
* 并行设备的硬件寄存器(如:状态寄存器)
* 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
* 多线程应用中被几个任务共享的变量
一个参数既可以是const还可以是volatile吗?解释为什么。
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
一个指针可以是volatile 吗?解释为什么。
是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时。
下面的函数有什么错误:
int square(volatile int *ptr) { return *ptr * *ptr; }
这段代码的目的是用来返回指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; }
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!
正确的代码如下:
long square(volatile int *ptr) { int a; a = *ptr; return a * a; }
二、分配内存 malloc ()与 free()
#include <stdlib.h> void *malloc(size_t size); void free(void *ptr); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size);
1. malloc()函数
malloc自动分配内存,需要一个内存字节数作为参数,返回动态分配内存块的首字节地址。
如果malloc内存返回失败,则会返回NULL。
例如用malloc创建一个数组: 为30个double型的值请求内存空间,并设置ptd指向该位置。
double *ptd; ptd = (double *) malloc(30 * sizeof(double));
1). 定义double型指针变量ptd
2). 30 * sizeof(double) 计算出需要的字节数
3). malloc()函数请求内存空间
4). (double*) 进程强制类型转换.
2. free()函数
free() 的参数是malloc参数返回的地址,释放malloc分配的内存。
因此,动态分配内存的周期从调用malloc到free为止。
一个动态分配内存与释放内存的例子
/* dyn_arr.c -- 动态分配一个数组 */ #include <stdio.h> #include <stdlib.h> /* for malloc(), free() */ int main(void) { double * ptd; /* 存储动态分配内存的首地址 */ int max; /* 输入数组的最大个数 */ int number; int i = 0; puts("What is the maximum number of type double entries?"); scanf("%d", &max); /* 输入数组的最大个数 */ ptd = (double *) malloc(max * sizeof (double)); /* 分配内存 */ if (ptd == NULL) /* 检查是否分配成功 */ { puts("Memory allocation failed. Goodbye."); exit(EXIT_FAILURE); } /* ptd now points to an array of max elements */ puts("Enter the values (q to quit):"); while (i < max && scanf("%lf", &ptd[i]) == 1) ++i; printf("Here are your %d entries:\n", number = i); for (i = 0; i < number; i++) { printf("%7.2f ", ptd[i]); if (i % 7 == 6) putchar('\n'); } if (i % 7 != 0) putchar('\n'); puts("Done."); free(ptd); return 0; }
3. calloc() 函数
calloc 与 malloc的区别是calloc对申请分配的内存空间进行初始化为0,然后返回分配内存空间的首地址。
long * newmem; newmem = (long *) calloc(100, sizeof(long));
有两个参数:
第一个参数是所需的存储单元数量
第二个参数是存储单元的大小,以字节为单位
4. realloc()函数
realloc()函数用于修改一个原先已经分配的内存块大小。
经典问题:new delete 与malloc free 的联系与区别?
1. 都是在堆(heap)上进行动态的内存操作。 自动分配的变量都在栈上
2. malloc 与 free 是C库函数, new delete 是C++中运算符
问题:要求设置一绝对地址为0x67a9的整型变量的值为0xaa55。
int *ptr; ptr = (int *)0x67a9; *ptr = 0xaa55;