一文講解 C 語言存儲類型

在 C 語言中,存儲類型 storage class 是指存儲變量的內存類型。存儲類型決定了變量的創建、銷燬和保存的生命週期,稱爲存儲期。存儲類型分爲自動存儲類型、外部存儲類型、靜態存儲類型和寄存器存儲類型四種類型。

C 語言使用 autoexternstaticregister 四個關鍵字來標識變量所在的存儲類型。

auto

使用關鍵字 auto 定義的變量屬於自動存儲類型,稱爲自動變量,具有自動存儲期、塊作用域、無鏈接,存儲於堆棧中。

自動存儲期指的是程序執行到聲明自動變量的代碼塊時,自動變量才被創建,程序執行流離開代碼塊時,自動變量便自行銷燬。該代碼塊數次執行時,自動變量也會跟着創建銷燬。

int sum(int n) {
    auto int sum = 0;    // auto 可以省略
    while (n > 0) {
        sum += n;
        n--;
    }
    return sum;
}

代碼塊內部聲明的變量在缺省情況下就是自動變量,因此可以省略 auto 關鍵字。

extern

使用關鍵字 extern 定義的變量屬於外部存儲類型的變量,稱爲外部變量,具有文件作用域、外部鏈接和靜態存儲期。

靜態存儲期指的是變量在程序運行時創建,在執行期間始終存在,並在程序結束時銷燬。文件作用域變量具有靜態存儲期。

// extdef.c
#include "extdef.h"
#include <stdio.h>

extern int exvalues;  // 聲明變量,但並未分配內存

void def_val() {
    printf("%d\n", exvalues);
}

// main.c

int exvalues;  // 定義變量,分配內存

extern void def_val();  

int main() {
    exvalues = 16;
    def_val();
    return 0;
}

編寫完代碼後,編譯源文件,如下所示。

gcc main.c extdef.c -o main

編譯後,運行可執行文件即可。從上面可以看出,如果源文件中使用的外部變量定義在另一個源文件中,則必須用 extern 在該文件中聲明變量。

static

使用關鍵字 static 定義的變量屬於靜態存儲類型,稱爲靜態變量,具有文件作用域、內部鏈接和靜態存儲期。

變量的缺省存儲類型取決於它的聲明位置。凡是在任何代碼塊之外聲明的變量總是存儲於靜態內存中,也就是不屬於堆棧的內存,這類變量稱爲靜態 static 變量。

// main.c
static int sval;    // 不顯式指定初始值,靜態變量將初始化爲 0

int main() {
    exvalues = 16;
    
    static int ival = 20;
    return 0;
}

如上所示,在代碼塊外聲明的 sval 變量是靜態變量,而代碼塊內部聲明的 ival 變量被 static 修飾,使它的存儲類型從自動變爲靜態。變爲靜態變量的 ival 會在整個程序執行過程中一直存在,而不僅僅在聲明它的代碼塊的執行時存在。

修改變量的存儲類型並不表示修改變量的作用域。如上所示的 ival 變量只能在代碼塊內部訪問。函數的形參不能聲明爲靜態,因爲實參總是在堆棧中傳遞給函數,用於支持遞歸。

register

使用關鍵字 register 定義的變量屬於寄存器存儲類型,稱爲寄存器變量,具有塊作用域、無鏈接和自動存儲期。

使用 register 關鍵字用於自動變量的聲明,將變量變量存儲於 CPU 的寄存器中,而不是內存中。

int sum(int n) {
    register int sum = 0;
    while (n > 0) {
        sum += n;
        n--;
    }
    return sum;
}

如上所示,被 register 修飾的 sum 變量爲寄存器變量,它比存儲於內存中的變量訪問起來效率更高。編譯器會根據寄存器可用內存的數量衡量,可能只選取前幾個存儲於寄存器中,其餘的按自動變量處理,或者編譯器有優化寄存器的方法,由編譯器選取哪些變量存儲於寄存器。

機器並不提供寄存器變量的地址。

寄存器變量具有塊作用域,創建和銷燬也與自動變量相同,但需要做些額外工作。使用寄存器變量的函數返回之前,這些寄存器必須恢復先前存儲的值,確保調用者的寄存器變量未被破壞。許多機器使用運行時堆棧來完成這個任務。當函數開始執行時,它把需要使用的所有寄存器的內部都保存到堆棧中,當函數返回時,這些值再複製回寄存器中。

鏈接屬性

標識符的鏈接屬性用於程序中各個源文件編譯並鏈接成可執行文件後,處理不同文件中出現的同名標識符。標識符的作用域與它的鏈接屬性有關。

鏈接屬性一共有外部鏈接 external、內部鏈接 internal 和無鏈接 none 三種鏈接屬性。

static int sval;
int n = 10;
int sum(int n) {
    int sum = 0;
    while (n > 0) {
        sum += n;
        n--;
    }
    return sum;
}

如上所示,缺省的 n 的鏈接屬性爲 externalsval 的鏈接屬性爲 internalsum 的鏈接屬性爲 none

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/bf4bC2bupQgWae9kIXXtBw