歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

C語言之存儲類的相關的關鍵字

不同的數據在內存中的存儲位置是不同的,總體來說內存中存儲數據的地方主要有四部分:棧、堆、數據段、bss段,這些地方分別存放著不同的數據,比如棧一般用來存儲局部變量,堆內存需要程序員字自己申請以及釋放,主要用來存放比較大的數據;數據段主要用來存放顯示初始化的全局變量和static關鍵字修飾的靜態局部變量;bss段一般用來存放未顯式初始化的全局變量或顯式初始化為0的全局變量(C語言中,默認全局變量初始化為0)。C語言還提供了一些關鍵字來修飾變量,使其附有其他的屬性,這些關鍵字主要有:auto、static、const、register、extern、volatile、restrict。

1:auto

  auto關鍵字的作用只有一個:修飾局部變量,auto關鍵字修飾後的局部變量表示是自動局部變量,自動局部變量是分配在棧上面的,C語言中默認局部變量就是自動局部變量。

2:static

  static關鍵字的作用有兩種:

  第一種:修飾局部變量,得到靜態局部變量,靜態局部變量是被分配在數據段或者bss段上面的,而非靜態局部變量是被分配在棧上面的。

  第二種:修飾全局變量,得到靜態全局變量,靜態全局變量和非靜態全局變量的比較如下:

(1)靜態全局變量和全局變量的存儲類一樣,都是被分配在數據段或者bss段

(2)靜態全局變量和全局變量的生命周期一樣,

(3)靜態局部變量的作用域是代碼塊作用域({}:為一個代碼塊,普通局部變量一

樣),鏈接屬性是無連接;全局變量的作用域是文件作用域(和函數一樣),鏈接屬性是外連接。

3:const

  const常被稱為常量修飾符,也就是說const修飾後的“變量”變成了常量,(所以在函數傳參時常用const來修飾輸入型參數,不用const修飾的參數我們常認為是輸出型參數。)但是在C語言中,這種常量也不算是完全不可變的,因為const修飾後的“變量”並不是被放到代碼段(代碼段是只讀的,數據被放到代碼段就無法修改),const關鍵字修飾的“變量”通過指針的方式還是可以修改的,具體實現是通過指針指向這個“變量”,然後解引用就可以修改const修飾後的“變量”。

4:register

 register關鍵字不常用,它的作用也是唯一的:register修飾的變量,將來在編譯的時候編譯器會盡量將它分配到寄存器中(平時一般的變量都是被分配到內存中的),變量被分配到寄存器中其使用和被分配到內存中是一樣的,但是其速寫的效率會高很多。但是由於寄存器的數量是有限的,所以這裡只是盡量放到寄存器中,而不是一定。我們平時需要用register修飾的變量很少,所以需要慎用。

5:extern

  extern關鍵字是用來聲明全局變量的,原因是C語言中編譯的時候是以單個.c文件為單位來進行編譯的,但是一個大的工程中不可能只有一個.c文件,這個時候就需要extern將變量,函數等聲明要.c文件外部,這樣編譯的時候a.c中的函數和變量就可以被b.c所調用。

6:volatile

 volatile修飾的變量表示這個變量隨時可以被修改,因此編譯後的程序每次需要存儲或者讀取這個變量時都會直接從變量地址中去讀取數據,而沒有volatile修飾的變量,則編譯器可能會優化讀取和存儲,可能暫時使用的就是寄存器中的值,如果這個變量被其他程序修改了的話,編譯器就有可能認為這個變量仍然沒有被修改(原因是讀取的可能是寄存器裡面的那個副的變量值,而程序修改的是內存中變量的值)

int flag; 

void test(void)

{   

    do1();

    while(flag==0);

    do2();

}

只有當flag不為0時,d02()函數才能被被執行,假設現在我們通過按下按鍵來產生中斷,然後再中斷處理函數中將flag賦值為1,這樣d02()函數就能被執行了,但是編譯器並不知道flag的值被其他程序所修改了,原因是編譯器在編譯的時候會對程序進行優化,這樣的優化會可能會將flag放入寄存器,下次讀取的時候就可能的是寄存器中的flag的那個值。

一般來說,volatile主要用在下面幾個地方

(1)中斷服務程序中修改的供其他程序檢測的變量

(2)多任務環境下各任務間共享的標志應該加volatile

(3)存儲器映射的硬件寄存器通常也要加volatile進行說明,因為每次對他們的讀寫可能不同。

簡單來說,volatile總是和優化有關,編譯器有一種技術叫做數據流分析,分析程序中的變量在哪賦值、在哪使用、在哪失效,分析結果可以用於常量合並,常量傳播優化,進一步可以死代碼消除,但是有時候程序不需要這些優化,這時候就可以通過volatile來禁止這些優化。

7:restrict

restrict關鍵字只能用來修飾指針,不能用來修飾變量,具體用法可參照下文

c99中新增加了一個類型定義,就是restrict。

看了下網上的相關貼子,但還是問題解決的不夠。下面是相關一個文章,我將在後面再加相關說明:
那麼restrict的意義是什麼呢?

概括的說,關鍵字restrict只用於限定指針;該關鍵字用於告知編譯器,所有修改該指針所指向內容的操作全部都是基於(base on)該指針的,即不存在其它進行修改操作的途徑;這樣的後果是幫助編譯器進行更好的代碼優化,生成更有效率的匯編代碼。

舉個簡單的例子

int foo (int* x, int* y)
...{
*x = 0;
*y = 1;
return *x;
}

很顯然函數foo()的返回值是0,除非參數x和y的值相同。可以想象,99%的情況下該函數都會返回0而不是1。然而編譯起必須保證生成100%正確的代碼,因此,編譯器不能將原有代碼替換成下面的更優版本

int f (int* x, int* y)
...{
*x = 0;
*y = 1;
return 0;
}

啊哈,現在我們有了restrict這個關鍵字,就可以利用它來幫助編譯器安全的進行代碼優化了

int f (int *restrict x, int *restrict y)
...{
*x = 0;
*y = 1;
return *x;
}

此時,由於指針 x 是修改 *x的唯一途徑,編譯起可以確認 “*y=1; ”這行代碼不會修改 *x的內容,因此可以安全的優化為

int f (int *restrict x, int *restrict y)
...{
*x = 0;
*y = 1;
return 0;
}

最後注意一點,restrict是C99中定義的關鍵字,C++目前並未引入;在GCC可通過使用參數" -std=c99"
來開啟對C99的支持

下面是我從C語言核心技術一書上摘的:
void *memcpy( void * restrict dest ,const void * restrict src,sizi_t n) 這是一個很有用的內存復制函數,由於兩個參數都加了restrict限定,所以兩塊區域不能重疊,即 dest指針所指的區域,不能讓別的指針來修改,即src的指針不能修改. 相對應的別一個函數 memmove(void *dest,const void * src,size_t)則可以重疊。

Copyright © Linux教程網 All Rights Reserved