C語言 變數的儲存類

2022/06/22閱讀時間約 12 分鐘
● auto(自動)
● register(暫存器)
● static(靜態)
● extern(外部)
定義
作用範圍(scope)、儲存時期(life time)連結(linkage)的不同作為區別。

●作用範圍:分為區域變數(只能作用於其函數內)全域變數(作用於不同文件中)
接著我們來看看以下兩種程式碼哪個是可執行的。
#include "stdio.h"

void text(){
  printf("%d\n",a);
}

int main(void){
  int a = 0;
  text();
}
--------------------------------結果---------------------------------
’a’ was not declared in this scope
沒錯,這段程式碼是無法執行的,它會跑出一條’a’ was not declared in this scope的訊息,也就是a變數不存在於該區塊中。為什麼呢?我們可以發現,a的定義是在main這個主函數裡面,它是無法作用在其他函數內的,相反的我們在也可以發現,a也只會作用在main裡面,這就是所謂的區域變數。
#include "stdio.h"
int a = 1;
int main(void){
  printf("%d\n",a);
}
--------------------------------結果---------------------------------
1
而這段程式碼中,a是定義在任何函數外,就結果來說,我們可以正常看到它輸出了1。因此a的定義為全域變數,也就是在它定義開始一直到文件的最後一行,任何函式都可以使用a這個變數。
那如果,同時使用呢?
#include "stdio.h"

int a = 1;

int main(void){
  int a = 2;
  printf("%d\n",a);
}
--------------------------------結果---------------------------------
2
結果很明顯,輸出的會是2。由這點可知,局部變數在該函數中,作用的優先會大於全局變數,因此main函式中,a的變數會對應到函數內部,定義為2的a。

●存儲時期(life time):分為靜態存儲(直到整個程式結束前,該變數的值會持續保留,不受二次定義賦值影響)以及自動存儲(一旦該函式結束後,其變數就會消失)兩種。
一樣來看看這兩種的差別吧!
#include "stdio.h"

void text(){
  int a = 0;
  a++;
  printf("%d\n",a);
}

int main(void){
  text();
  text();
  text();
}
--------------------------------結果---------------------------------
1
1
1
結果輸出了不同行的三個1,這是為什麼呢?當main函數跑第一個text()的時候,text內部會創建一個a,並賦值0給a。接著第六行會讓a的值再加上1,也就是0+1=1。最後輸出a的值,並結束這個函數,同時刪掉a這個變數,注意喔!當函數結束後,裡面所有的變數都是會刪除的。因此,我們可以理解到,main使用text函式的三次中,a都會初始定義並賦值為0,所以這也是為什麼輸出的結果都是相同的,而這就是所謂的自動存儲
#include "stdio.h"

void text(){
  static int a = 0;
  a++;
  printf("%d\n",a);
}

int main(void){
  text();
  text();
  text();
}
--------------------------------結果---------------------------------
1
2
3
而這一次的執行可以得到不同行的1、2、3,為什麼加上static之後,就會這樣呢?當main在跑text第一次的時候,流程都跟前面講左方程式碼時一樣,但在最後輸出a的值之後,結束函數的同時並不會刪除掉a的值。並且在第二次跑text函數時,第五行不會重新賦值0給a,a變數仍舊保持著上一次結果的值,也就是1。因此在這次的text中,它會再次加1並輸出。最後我們可以理解到,函數在結束後,不會刪除掉有static描述的變數,並保持其值,而這就是所謂的靜態存儲

●連結(linkage):分為空連結(只能於函數內使用,區域變數)內部連結(可於定義並賦值位置一直到文件最後一行都可使用,類似全域變數)以及外部連結(可跨文件做使用)
讓我們測驗看看外部連結吧~
(text.c)
int b = 10;
(main.c)
#include "stdio.h"
int main(void){
  extern int b;
  printf("%d",b);
}
--------------------------------結果---------------------------------
10
當我在text.c檔案中定義一個變數b,只要在另外一個文件main.c當中,在定義類型前面再加一個extern修飾,就能跨文件使用變數了。在main.c執行後,即可成功輸出10。而這種方法,就是所謂的外部連結。
但要特別注意的是,外部連結必須是在同個資料夾,否則無法透過外部連結使用喔。

儲存類

●auto(自動)

auto擁有自動存儲區域變數空連結的特性,簡單來說就是auto只能修飾在函數內的變數,只能在函數內使用,且函數執行結束後就會消失。事實上,任何在函數內定義的變數,只要沒有特別修飾,直接定義類型、名稱及值後,就會自動修飾為auto。簡單來說就是auto在修飾上面,其實沒有特別用處。
#include "stdio.h"
int main(void){
  auto int b = 0;
  printf("%d",b);
}
--------------------------------結果---------------------------------
0
另外auto還有一個作用是,自動定義變數的類型,如下。
#include "stdio.h"

int main(void){
  auto b = 0;
  printf("%d",b);
}
--------------------------------結果---------------------------------
0
如果使用auto當定義類型時,它會自動幫b搜尋它適合的類型,也就是int。但如果沒有特別指定值的時候,變數則會默認為int。

●register(暫存器)

register擁有自動存儲區域變數空連結的特性,一樣是只能修飾在函數內的變數,它的特點在於,經過register修飾的變數,會存放在暫存器當中,而不是平常的記憶體內。因此它的執行速度會大幅提升,如下圖採用CR Ferreira的時間計算程式碼,計算有無register修飾的時間差。
#include "stdio.h"
#include "sys/time.h"
int main(void){
  double sum = 0;
  double add = 1;
  struct timeval begin, end;
  gettimeofday(&begin, 0);
  register int a,i;//暫存器變數
  for (i=0; i<1000000000; i++){//主要執行內容
    a++;
  }
  gettimeofday(&end, 0);
  long seconds = end.tv_sec - begin.tv_sec;
  long microseconds = end.tv_usec - begin.tv_usec;
  double elapsed = seconds + microseconds*1e-6;
  printf("Time measured: %.3f seconds.\n", elapsed);
  return 0;
}
--------------------------------結果---------------------------------
Time measured: 0.681 seconds.
-------------------------------分隔線--------------------------------
#include "stdio.h"
#include "sys/time.h"

int main(void){
  double sum = 0;
  double add = 1;
  struct timeval begin, end;

  gettimeofday(&begin, 0);
  int a,i;//一般變數

  for (i=0; i<1000000000; i++){//主要執行內容
  a++;
  }

  gettimeofday(&end, 0);
  long seconds = end.tv_sec - begin.tv_sec;
  long microseconds = end.tv_usec - begin.tv_usec;
  double elapsed = seconds + microseconds*1e-6;
  printf("Time measured: %.3f seconds.\n", elapsed);
  return 0;
}
--------------------------------結果---------------------------------
Time measured: 2.215 seconds.
可以清楚看出,有register修飾的文件,執行速度大幅領先無修飾的文件。

●static(靜態)

static擁有靜態區域變數、靜態全域變數兩種方式。前者只能在函數內修飾,並擁有靜態存儲、區域變數、空連結的特性後者只能在函數外修飾,擁有靜態存儲、全域變數、內部連結的特性
#include "stdio.h"
static int i;//靜態全域變數
void text(){
  static int j;//靜態區域變數
}
靜態區域變數的特性,在前面有說過,能夠在整個程式執行結束前,一直保有其值不會消失。這時候你可能會想,全域變數本來就是到程式執行結束才會消失,那還有必要寫static來修飾嗎?
確實,是如此沒錯,但全域變數跟靜態全域變數的最大差別在於連結性。一般沒做修飾的全域變數是可以跨文件利用extern做連結的,但經過static修飾的變數,則是無法跨文件取得,也就是內部連結,僅供該文件使用。
(text.c)
static int i = 5;
(main.c)
#include "stdio.h"
extern int i;
int main(void){
  printf("%d",i);
}
--------------------------------結果---------------------------------
當在main.c執行後,就無法取得i這個變數,也因此無法取得i的值,這就是static的內部連結性質。

● extern(外部)

一般在函數外未特別修飾的變數,擁有靜態存儲、全域變數、外部連結的特性,而extern則可以引用這些變數。相關實作在前方有示範過了,這邊就不多加掩飾。
參考內容
贊助創作者百元,就抽 $8,888 紅包與好禮!
caffeine咖啡因
caffeine咖啡因
早安,這裡是咖啡因,我是一個準備升大一的快樂賣肝員,在這一個長達四個月的暑假,我沒有打算去打工,因此為了好好利用它,我打算認真學習多程式語言,甚至做成一篇篇教學,讓以後的我忘記時,還能回來快速回顧。所以,這裡就是我的學習紀錄,希望能我能持之以恆。
如果要發表留言,請先登入註冊會員
享受沈浸的閱讀體驗
徜徉在不受干擾的簡約介面,瀏覽數百萬篇原創內容。
領取見面禮
只要設定追蹤作者,即可享有 48小時
Premium 閱讀權限