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;

 

posted @ 2017-09-10 23:10  elewei  阅读(366)  评论(0编辑  收藏  举报