天天看點

C語言之——關鍵字

一、相關概念

主要解釋存儲類關鍵字(auto  static  extern  register)和類型限定、修飾關鍵字(const volatile restrict inline)等。在介紹之前,闡述幾個相關概念:

1、存儲類

(1)存儲類就是就是描述C語言變量在何種地方存儲,即棧、堆、資料段、bss段、.text段等。

2、作用域

(1)作用域是描述這個變量起作用的代碼範圍。

(2)基本來說,C語言變量的作用域規則是代碼塊作用域。意思就是這個變量起作用的範圍是目前的代碼塊。代碼塊就是一對大括号{}括起來的範圍,是以一個變量的作用域是:這個變量定義所在的{}範圍内從這個變量定義開始往後的部分。(這就解釋了為什麼變量定義總是在一個函數的最前面)

3、生命周期

生命周期是描述這個變量什麼時候誕生(運作時配置設定記憶體空間給這個變量)及什麼時候死亡(運作時收回這個記憶體空間,此後再不能通路這個記憶體位址,或者通路這個記憶體位址已經和這個變量無關了)的。

4、連結屬性

(1)程式從源代碼到最終可執行程式,經曆的過程:編譯、連結。

(2)編譯階段就是把源代碼搞成.o目标檔案,目标檔案裡面有很多符号和代碼段、資料段、bss段等分段。符号就是程式設計中的變量名、函數名等。運作時變量名、函數名能夠和相應的記憶體對應起來,靠符号來做連結的。

(3).o的目标檔案連結生成最終可執行程式的時候,其實就是把符号和相對應的段給連結起來。

(4)編譯以檔案為機關、連結以工程為機關。編譯器工作時是将所有源檔案依次讀進來,單個為機關進行編譯的。連結的時候實際上是把第一步編譯生成個單個的.o檔案整體的輸入,然後處理連結成一個可執行程式。

C語言中的符号有三種連結屬性:外連接配接屬性、内連結屬性、無連接配接屬性。

  • 外連接配接的意思就是外部連結屬性,也就是說這家夥可以在整個程式範圍内(言下之意就是可以跨檔案)進行連結,譬如普通的函數和全局變量屬于外連接配接。
  • 内連結的意思就是(c檔案内部)内部連結屬性,也就是說這家夥可以在目前c檔案内部範圍内進行連結(言下之意就是不能在目前c檔案外面的其他c檔案中進行通路、連結)。static修飾的函數/全局變量屬于内連結。
  • 無連接配接的意思就是這個符号本身不參與連結,它跟連結沒關系。所有的局部變量(auto的、static的)都是無連接配接的,宏和inline函數的連結屬性為無連接配接。

函數和全局變量的同名沖突

(1)因為函數和全局變量是外部連結屬性,就是說每一個函數和全局變量将來在整個程式中所有的c檔案都能被通路,是以在一個程式中的所有c檔案中不能出現同名的函數/同名的全局變量。

(2)最簡單的解決方案就是起名字不要重複,但是在一個多人協作的大工程裡很難做到。現代進階語言中完美解決這個問題的方法是命名空間namespace(其實就是給一個變量帶上各個級别的字首)。但是C語言不是這麼解決的。就是三種連結屬性的方法。即:将明顯不會在其他c檔案中引用(隻在目前c檔案中引用)的函數/全局變量,使用static修飾使其成為内連結屬性,這樣在将來連接配接時即使2個c檔案中有重名的函數/全局變量,隻要其中一個或2個為内連結屬性就沒事。是以寫程式盡量避免使用全局變量,尤其是非static類型的全局變量。能确定不會被其他檔案引用的全局變量一定要static修飾。

二、關鍵字解析

1、auto

(1)auto關鍵字在C語言中修飾局部變量。表示這個局部變量是自動局部變量,自動局部變量配置設定在棧上。(既然在棧上,說明它如果不初始化那麼值就是随機的)

(2)平時定義局部變量時就是定義的auto的,隻是省略了auto關鍵字而已。可見,auto的局部變量其實就是預設定義的普通的局部變量。

2、static

(1)static關鍵字在C語言中有2種用法,而且這兩種用法彼此沒有任何關聯、完全是獨立的。

(2)static的第一種用法是:用來修飾局部變量,形成靜态局部變量。要搞清楚靜态局部變量和非靜态局部變量的差別。本質差別是存儲類不同(存儲類不同就衍生出很多不同):非靜态局部變量配置設定在棧上,而靜态局部變量配置設定在資料段/bss段上。

(3)static的第二種用法是:用來修飾全局變量,形成靜态全局變量。靜态全局變量和非靜态全局變量的差別。差別是在連結屬性上不同。

1、靜态局部變量在存儲類方面和全局變量一樣。

2、靜态局部變量在生命周期方面和全局變量一樣。

3、靜态局部變量和全局變量的差別是:作用域、連接配接屬性。靜态局部變量作用域是代碼塊作用域(和普通局部變量是一樣的)、連結屬性是無連接配接;全局變量作用域是檔案作用域(和函數是一樣的)、連結屬性方面是外連接配接。

3、register

(1)register關鍵字修飾的變量,編譯器會盡量(不保證一定放在寄存器中。主要原因是因為寄存器數量有限,不一定有空用)将它配置設定在寄存器中。(平時配置設定的一般的變量都是在記憶體中的)。配置設定在寄存器中一樣的用,但是讀寫效率會高很多。是以register修飾的變量用在那種變量被反複高頻率的使用,通過改善這個變量的通路效率可以極大的提升程式運作效率時。是以register是一種極緻提升程式運作效率的手段。

(2)uboot中用到了一個register類型的變量,gd這個變量是用來存uboot的全局變量(gd就是global data)。因為這個全局變量在整個uboot中到處都被通路,是以定義成register的。

4、extern

(1)extern可以置于變量或者函數前,以标示變量或者函數的定義在别的檔案中,提示編譯器遇到此變量和函數時在其他子產品中尋找其定義。

(2)在b.c中引用a.c中定義的全局變量/函數有2種方法:一是在a.h中聲明該函數/全局變量,然後在b.c中#include <a.h>;二是在b.c中使用extern顯式聲明要引用的函數/全局變量。其中第一種方法比較正式。

5、volatile

(1)volatile的字面意思:可變的、易變的。編譯器在遇到volatile修飾的變量時就不會對改變量的通路進行優化。

在本線程内,當讀取一個變量時,為了提高讀取速度,編譯器進行優化時有時會先把變量讀取到一個寄存器中;以後,再讀取變量值時,就直接從寄存器中讀取;當變量值在本線程裡改變時,會同時把變量的新值copy到該寄存器中,以保持一緻。

編譯器優化有時會帶來好多意想不到的問題,在次不一一舉例。

一般說來,volatile關鍵字用在如下的幾個地方。

(1)中斷服務程式中修改的供其他程式檢測的變量需要加volatile。

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

(3)存儲器映射的硬體寄存器通常也要加volatile說明,因為每次對它的讀寫都可能有不同意義。以STM32為例,寄存器中的資料也是時刻在變化的,我們也不想編譯器優化這一點,是以在庫函數中我們可以看到這樣的代碼。

typedef struct
{
  __IO uint32_t CSR;    /*!< ADC Common status register,                  Address offset: ADC1 base address + 0x300 */
  __IO uint32_t CCR;    /*!< ADC common control register,                 Address offset: ADC1 base address + 0x304 */
  __IO uint32_t CDR;    /*!< ADC common regular data register for dual
                             AND triple modes,                            Address offset: ADC1 base address + 0x308 */
} ADC_Common_TypeDef;
           

 這是寄存器的結構體,我們檢視字首__I 和__IO到底是什麼?

/* IO definitions (access restrictions to peripheral registers) */
/**
    \defgroup CMSIS_glob_defs CMSIS Global Defines

    <strong>IO Type Qualifiers</strong> are used
    \li to specify the access to peripheral variables.
    \li for automatic generation of peripheral register debug information.
*/
#ifdef __cplusplus
  #define   __I     volatile             /*!< Defines 'read only' permissions */
#else
  #define   __I     volatile const       /*!< Defines 'read only' permissions */
#endif
#define     __O     volatile             /*!< Defines 'write only' permissions */
#define     __IO    volatile             /*!< Defines 'read / write' permissions */

/* following defines should be used for structure members */
#define     __IM     volatile const      /*! Defines 'read only' structure member permissions */
#define     __OM     volatile            /*! Defines 'write only' structure member permissions */
#define     __IOM    volatile            /*! Defines 'read / write' structure member permissions */

/*@} end of group Cortex_M4 */
           

6、restrict

(1)restrict是c99标準引入的,它隻可以用于限定和限制指針,并表明指針是通路一個資料對象的唯一且初始的方式.即它告訴編譯器,所有修改該指針所指向記憶體中内容的操作都必須通過該指針來修改,而不能通過其它途徑(其它變量或指針)來修改,如 int *restrict ptr, ptr 指向的記憶體單元隻能被 ptr 通路到,任何同樣指向這個記憶體單元的其他指針都是未定義的,直白點就是無效指針。

ref:

https://www.cnblogs.com/yangguang-it/p/6719261.html?utm_source=itdadao&utm_medium=referral

https://www.cnblogs.com/god-of-death/p/7852394.html

https://www.jb51.net/article/141042.htm

https://www.runoob.com/w3cnote/c-volatile-keyword.html

http://blog.chinaunix.net/uid-22197900-id-359209.html

繼續閱讀