第12章:存儲類別、鏈接和內存管理

  • #1. 存儲類別
    • 1.1 作用域
    • 1.2 鏈接
    • 1.3 存儲期
    • 1.4 自動變量
    • 1.5 寄存器變量
    • 1.6 塊作用域的靜態(tài)變量
    • 1.7 外部鏈接的靜態(tài)變量
    • 1.8 內部鏈接的靜態(tài)變量
    • 1.9 多文件
    • 1.10 存儲類別說明符
    • 1.11 存儲類別和函數(shù)
    • 1.12 存儲類別的選擇
  • #2. 擲骰子
  • #3. 分配內存:malloc()和free()

#1. 存儲類別

C提供了多種不同的模型或存儲類別在內存中儲存數(shù)據(jù)。從硬件方面來看,被存儲的每個值都占用一定的物理內存,C語言把這樣的一塊內存稱為對象。

1.1 作用域

作用域描述程序中可訪問標識符的區(qū)域。一個C變量的作用域可以是塊作用域、函數(shù)作用域、函數(shù)原型作用域文件作用域

塊作用域

塊是一對花括號括起來的代碼區(qū)域。定義在塊中的變量具有塊作用域(block scope),塊作用域變量的可見范圍是從定義初到包含該定義的塊的末尾。

//變量cleo和patrick均具有塊作用域
double blocky(double cleo) {
    double patrick = 0.0;
    ...
    return patrick;
}
函數(shù)作用域

函數(shù)作用域(function scope)僅用于goto語句的標簽。這意味著即使一個標簽首次出現(xiàn)在函數(shù)的內層塊中,它的作用域也延伸至整個函數(shù)。

函數(shù)原型作用域

函數(shù)原型作用域用于函數(shù)原型中的形參名(變量名),如下所示:

int mighty(int mouse,double large);

函數(shù)原型作用域(function prototype scope)的范圍從形參定義處到原型聲明結束。這意味著,編譯器在處理函數(shù)原型中的形參時只關心它的類型,而形參名(如果有的話)通常無關緊要。

文件作用域

變量的定義在函數(shù)的外面,具有文件作用域(file scope)。具有文件作用域的變量,從它的定義處到該定義所在的文件末尾均可見。

#include <stdio.h>

int units = 0;/*該變量具有文件作用域*/
int main(void) {
    ...
}
void critic(void) {
    ...
}

變量units具有文件作用域,main()和critic()函數(shù)都可以使用它(更準確的說,units具有外部鏈接文件作用域)。由于這樣的變量可作用于多個函數(shù),所以文件作用域變量也稱為全局變量。

標識符作用域示例:
1——>int a;/*文件作用域*/
/*2具有文件作用域*/
2——>int b(3——>int c/*原型作用域*/);
4——>int d(5——>int e/*塊作用域*/);/*函數(shù)定義的形式參數(shù)e在函數(shù)體內部也具有代碼塊作用域*/
{
    6——>int f;/*塊作用域*/
    7——>int g(8——>int h/*原型作用域*/);
    ...
    {
        //如果內層代碼塊有一個標識符的名字與外層代碼塊的一個標識符同名,內層的那個標識
        //符就將隱藏外層的標識符——外層的那個標識符無法在內層代碼塊中通過名字訪問。
        9——>int f,g,i;/*塊作用域*/ 
        //聲明9的f和聲明6的f是不同的變量,后者無法在內層代碼塊中通過名字來訪問。
        ...
    }
    {
        10——>int i;/*塊作用域*/
        ...
    }
}

1.2 鏈接

C變量有3種鏈接屬性:外部鏈接,內部鏈接無鏈接。具有塊作用域、函數(shù)作用域和函數(shù)原型作用域的變量都是無鏈接變量。這意味著這些變量屬于定義它們的塊、函數(shù)或原型私有。具有文件作用域的變量可以是外部鏈接或內部鏈接。外部鏈接變量可以在多文件程序中使用,內部鏈接變量只能在一個翻譯單元中使用。

==正式和非正式術語==

C標準用“內部鏈接的文件作用域”描述僅限于一個翻譯單元(即一個源代碼文件和它所包含的頭文件)的作用域,用“外部鏈接的文件作用域”描述可延伸至其他翻譯單元的作用域。但是,對程序員而言,“內部鏈接的文件作用域”可以簡稱為“文件作用域”,把“外部鏈接的文件作用域”簡稱為“全局作用域”或“程序作用域”。

1.3 存儲期

作用域和鏈接描述了標識符的可見性。存儲期描述了通過這些標識符訪問的對象的生命周期。C對象有4種存儲期:靜態(tài)存儲期線程存儲期、自動存儲期動態(tài)分配存儲期。

靜態(tài)存儲期

如果對象具有靜態(tài)存儲期,那么它在程序的執(zhí)行期間一直存在。文件作用域變量具有靜態(tài)存儲期。注意,對于文件作用域變量,關鍵字static表明了其鏈接屬性,而非存儲期。以static聲明的文件作用域變量具有內部鏈接屬性。但是無論是內部鏈接還是外部鏈接,所有的文件作用域變量具有靜態(tài)存儲期。

線程存儲期

線程存儲期用于并發(fā)程序設計,程序執(zhí)行可被分為多個線程。具有線程存儲期的對象,從被聲明到線程結束一直存在。

自動存儲期

塊作用域的變量通常都具有自動存儲期。當程序進入定義這些變量的塊時,為這些變量分配內存;當退出這個塊時,釋放剛才為變量分配的內存。

存儲類別 存儲期 作用域 鏈接 聲明方式
自動 自動 塊內
寄存器 自動 塊內,使用關鍵字register
靜態(tài)外部鏈接 靜態(tài) 文件 外部 所有函數(shù)外
靜態(tài)內部鏈接 靜態(tài) 文件 內部 所有函數(shù)外,使用關鍵字static
靜態(tài)無鏈接 靜態(tài) 塊內,使用關鍵字static

1.4 自動變量

屬于自動存儲類別的變量具有自動存儲期、塊作用域且無鏈接。默認情況下,聲明在塊或函數(shù)頭中的任何變量都屬于自動存儲類別。

int main(void) {
    auto int plox;//關鍵字auto是存儲類別說明符。
}

auto關鍵字在C++中的用法完全不同, 如果編寫C/C++兼容的程序,最好不要使用auto作為存儲類別說明符。

塊作用域和無鏈接意味著只有在變量定義所在塊中才能通過變量名訪問改變變量。

#include <stdio.h>

int main(void) {
    int x = 30;//原始的x
    printf("x in outer block: %d at %p\n",x,&x);
    {
        int x = 77;//新的x,隱藏了原始的x
        printf("x in inner block: %d at %p\n",x,&x);
    }
    printf("x in outer block: %d at %p\n",x,&x);
    while (x++<33)//原始的x
    {
        int x = 100;//新的x,隱藏了原始的x
        x++;
        printf("x in while loop: %d at %p\n",x,&x);
    }
    printf("x in outer block :%d at %p\n",x,&x);

    getchar();
    return 0;
}

執(zhí)行結果如下:
x in outer block: 30 at 0044FD90
x in inner block: 77 at 0044FD84
x in outer block: 30 at 0044FD90
x in while loop: 101 at 0044FD78
x in while loop: 101 at 0044FD78
x in while loop: 101 at 0044FD78
x in outer block :34 at 0044FD90
1. 沒有花括號的塊

C99特性:作為循環(huán)或if語句的一部分,即使使用花括號({}),也是一個塊。

int main(void) {
    int n = 8;
    printf("Initially,n = %d at %p\n", n, &n);
    for (int n = 1; n < 3; n++)
        printf("    loop1 : n = %d at %p\n", n, &n);
    printf("After loop1,n = %d at %p\n", n, &n);
    for (int n = 1; n < 3; n++)
    {
        printf("loop2 index n = %d at %p\n", n, &n);
        int n = 6;
        printf("    loop2:n = %d at %p\n", n, &n);
        n++;
    }
    printf("After loop2,n = %d at %p\n", n, &n);

    getchar();
    return 0;
}
//執(zhí)行結果
Initially,n = 8 at 0044F9D0
        loop1 : n = 1 at 0044F9C4
        loop1 : n = 2 at 0044F9C4
After loop1,n = 8 at 0044F9D0
loop2 index n = 1 at 0044F9B8
        loop2:n = 6 at 0044F9AC
loop2 index n = 2 at 0044F9B8
        loop2:n = 6 at 0044F9AC
After loop2,n = 8 at 0044F9D0
2. 自動變量的初始化

自動變量不會初始化,除非顯示初始化它。

int main(void) {
    int rapid; //rapid變量的值是之前分配給rapid的空間中的任意值(如果有的話),別指望這個值是0。
    int tents = 5;
    printf("rapid = %d at %p\n",rapid,&rapid);
    printf("tents = %d at %p\n",tents,&tents);

    getchar();
    return 0;
}

執(zhí)行結果:    
rapid = -858993460 at 003CFBDC
tents = 5 at 003CFBD0

1.5 寄存器變量

變量通常儲存在計算機的內存中。如果幸運的話,寄存器變量存儲在cpu的寄存器中。==不能對寄存器變量使用地址運算符。==

void macho(register int n);

int main(void) {
    register int a = 5;
    printf("register var a = %d at %p\n",a,&a);

    getchar();
    return 0;
}
1>c:\users\hhu\source\repos\cpptest\cpptest\test.c(54): error C2103: '&' on register variable

1.6 塊作用域的靜態(tài)變量

靜態(tài)變量中靜態(tài)的意思是該變量在內存中原地不動,并不是說它的值不變。具有文件作用域的變量自動具有(也必須是)靜態(tài)存儲期??梢詣?chuàng)建靜態(tài)存儲期、塊作用域的局部變量。這些變量和自動變量一樣,具有相同的作用域,但是程序離開它們所在的函數(shù)后,這些變量不會消失。這種變量具有塊作用域、無鏈接,但是具有靜態(tài)存儲期?!?strong>靜態(tài)局部變量”是描述具有塊作用域的靜態(tài)變量的另一個術語。

1.7 外部鏈接的靜態(tài)變量

外部鏈接的靜態(tài)變量具有文件作用域、外部鏈接和靜態(tài)存儲期。該類別有時稱為外部存儲類別,屬于該類別的變量稱為外部變量。

int errupt;/*外部定義的變量*/
double up[100];/*外部定義的數(shù)組*/
extern char coal;/*如果coal被定義在另一個文件,則必須這樣聲明*/

void next(void);

int main(void) {
    extern int errupt; /*可選的聲明*/
    extern double up[]; /*可選的聲明*/
    ...
}

void next(void) {
    ...
}

在main()中聲明up數(shù)組時不用指明數(shù)組的大小,因為第1次聲明已經(jīng)提供了數(shù)組大小信息。main()中的兩條extern聲明完全可以省略,因為外部變量具有文件作用域。

1.初始化外部變量

外部變量和自動變量類似,也可以被顯示初始化。與自動變量不同的是,如果未初始化外部變量,它們會被自動初始化為0。與自動變量的情況不同,只能使用常量表達式初始化文件作用域變量:

int x = 10;
int y = 3 + 20;
size_t z = sizeof(int);
int x2 = 2 * x;//x是變量
2.使用外部變量
#include <stdio.h>

int units = 0;
void critic(void);

int main(void) {
    extern int units;
    printf("How many pounds to a firkin of butter?\n");
    scanf("%d",&units);
    while(units != 56) {
        critic();
    }
    printf("You must have looked it up!\n");
    return 0;
}

void critic(void) {
    printf("No luck, my friend.Try again.\n");
    scanf("%d",&units);
}
3. 外部名稱

C99和C11標準都要求編譯器識別局部標識符的前63個字符和外部標識符的前31個字符。這修訂了以前的標準,即編譯器識別局部變量標識符前31個字符和外部標識符前6個字符。

4.定義和聲明
int tern = 1;/*ternb被定義*/
void main(void) {
    extern int tern;/*使用在別處定義的tern*/
}

tern被聲明了兩次。第1次聲明為變量預留了存儲空間,該聲明構成了變量的定義。第2次聲明只告訴編譯器使用之前已創(chuàng)建的tern變量,所以這不是定義。第1次聲明被稱為定義式聲明,第2次聲明被稱為引用式聲明。

1.8 內部鏈接的靜態(tài)變量

該存儲類別的變量具有靜態(tài)存儲期,文件作用域和內部鏈接。在所有函數(shù)外部,用存儲類別說明符static定義的變量具有這種存儲類別:

static int svil = 1;/*靜態(tài)變量,內部鏈接*/
int main(void) {
    ...
    return 0;
}

普通的外部變量可用于同一程序中任意文件中的函數(shù),但是內部鏈接的靜態(tài)變量只能用于同一個文件中的函數(shù)。

int traveler = 1;//外部鏈接
static int stayhome = 1;//內部鏈接

int main(void) {
    extern int traveler;//使用定義在別處的traveler
    extern int stayhome;//使用定義在別處的stayhome
    ...
    return 0;
}

1.9 多文件

只有當程序由多個翻譯單元組成時,才體現(xiàn)區(qū)別內部鏈接和外部鏈接的重要性。

復雜的C程序通常由多個單獨的源代碼文件組成。有時,這些文件可能共享一個外部變量。C通過在一個文件中進行定義式聲明,然后在其他文件中進行引用式聲明來實現(xiàn)共享。也就是說,除了一個定義聲明外,其他聲明都要使用extern關鍵字。而且,只有定義式聲明才能初始化變量。

1.10 存儲類別說明符

C語言有6個關鍵字作為存儲類型說明符:auto、register、static、extern、_Thread_local和typedef。

  • auto說明符表明變量是自動存儲期,只能用于塊作用域的變量聲明中。
  • register說明符也只用于塊作用域的變量,它把變量歸為寄存器存儲類別,請求最快速度訪問該變量。同時,還保護了該變量的地址不被獲取。
  • static說明符創(chuàng)建的對象具有靜態(tài)存儲期,載入程序時創(chuàng)建對象,當程序結束時對象消失。如果static用于文件作用域聲明,作用域受限于該文件。如果static用于塊作用域聲明,作用域則受限于該塊。
  • extern說明符表明聲明的變量定義在別處。如果包含extern的聲明具有文件作用域,則引用的變量必須具有外部鏈接。

PartA.c

#define _CRT_SECURE_NO_WARNINGS
#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);
        }
        printf("Enter a positive integer (0 to quit): ");
    }
    report_count();
    return 0;
}

void report_count() {
    printf("Loop executed %d times\n",count);
}

PartB.c

#include<stdio.h>

extern int count;//引用式聲明

static int total = 0;//靜態(tài)定義,內部鏈接
void accumulate(int k);//函數(shù)原型

void accumulate(int k) {//k具有塊作用域,無連接
    static int subtotal = 0;//靜態(tài),無鏈接
    if (k <= 0)
    {
        printf("loop cycle: %d\n",count);
        printf("subtotal: %d;total: %d\n",subtotal,total);
        subtotal = 0;
    }
    else {
        subtotal += k;
    }
    total += k;
}

1.11 存儲類別和函數(shù)

函數(shù)也有存儲類別,可以是外部函數(shù)(默認)或靜態(tài)函數(shù)。C99新增了第3種類別——內聯(lián)函數(shù)。外部函數(shù)可以被其他文件的函數(shù)訪問,但是靜態(tài)函數(shù)只能用于其定義所在的文件。

double gamma(double);/*該函數(shù)默認為外部函數(shù)*/
static double beta(int,int);/*該函數(shù)為模塊私有*/
extern double delta(double,int);/*該函數(shù)定義在其他文件中*/

同一個程序中,其他文件中的函數(shù)可以調用gamma()和delta(),但是不能調用beta(),因為以static存儲類別說明符創(chuàng)建的函數(shù)屬于特定模塊私有。這樣做避免了名字沖突問題,由于beta()受限于所在文件,所以其他文件中可以定義與之同名的文件。

1.12 存儲類別的選擇

保護性程序設計的黃金法則是:“按需知道”原則。盡量在函數(shù)內部解決該函數(shù)的任務,只共享那些需要共享的變量。


#2. 擲骰子

dicetoll.h

#pragma once
extern int roll_count;
int roll_n_dice(int dice,int sides);

dicetoll.c

#include<stdio.h>
#include<stdlib.h>
#include "dicetoll.h"

int roll_count = 0;

static int rollem(int sides) {
    int roll;
    roll = rand() % sides + 1;
    ++roll_count;
    return roll;
}

int roll_n_dice(int dice, int sides) {
    int d;
    int total = 0;
    if (sides < 2)
    {
        printf("Need at least 2 sides.\n");
        return -2;
    }
    if (dice < 1)
    {
        printf("Need at least 1 die.\n");
        return -1;
    }
    for (d = 0; d < dice; d++)
    {
        total += rollem(sides);
    }
    return total;
}

manydice.c

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <stdlib.h>/*為庫函數(shù)srand()提供原型*/
#include <time.h>/*為time()提供原型*/
#include "dicetoll.h"/*為roll_n_dice()提供原型,為roll_count變量提供聲明*/

int main(void) {
    int dice, roll;
    int sides;
    int status;
    srand(time(0));/*隨機種子*/
    printf("Enter the number of sides per die,0 to top.\n");
    while (scanf("%d", &sides) == 1 && sides > 0)
    {
        printf("How many dice?\n");
        if ((status = scanf("%d", &dice)) != 1)
        {
            if (status = EOF)
            {
                break;
            }
            else {
                printf("Yout should have entered an integer.");
                printf("Let's begin again.\n");
                while (getchar() != '\n')
                {
                    continue;
                }
                printf("How many sides?Enter 0 to stop.\n");
                continue;
            }
        }
        roll = roll_n_dice(dice, sides);
        printf("You have rolled a %d using %d %d-sided dice.\n", roll, dice, sides);
        printf("How many sides?Enter 0 to stop.\n");
    }
    printf("The rollem() function was called %d times.\n", roll_count);
    printf("Good fortune to u.");
}

#3 分配內存:malloc()和free()

C函數(shù)庫提供了兩個函數(shù),malloc和free,分別用于執(zhí)行動態(tài)內存的分配和釋放。這些函數(shù)維護一個可用內存池。

void *malloc(size_t size);
void free(void *pointer);
  • malloc的參數(shù)就是需要分配的字節(jié)數(shù)。如果內存池中的可用內存可用滿足這個需求,malloc就返回一個指向被分配的內存塊起始位置的指針。如果操作系統(tǒng)無法向malloc提供更多的內存,malloc就返回一個NULL指針。
  • free的參數(shù)必須要么是NULL,要么是一個先前從malloc、calloc或realloc返回的值。向free傳遞一個NULL參數(shù)不產(chǎn)生任何效果。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *ptd;
    int max;
    int number;
    int i = 0;
    srand(time(0));

    puts("What is the maximum number of type double entries?");
    if (scanf("%d",&max) != 1)
    {
        puts("Number not correctly entered--bye.");
        exit(EXIT_FAILURE);
    }
    ptd = (int *)malloc(max * sizeof(int));
    if (ptd = NULL)
    {
        puts("Memory allocation failed.Goodbye.");
        exit(EXIT_FAILURE);
    }
    puts("Enter the values (q to quit):");
    while (i < max && scanf("%d",&ptd[i])) {
        ++i;
    }
    printf("Here are your %d entries:\n",number = i);
    for (i = 0; i < number; i++)
    {
        printf("%d ", ptd[i]);
        if (i % 7 == 6)
            putchar('\n');
    }
    if (i % 7 != 0)
        putchar('\n');
    puts("Done.");

    free(ptd);
    return 0;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容