C之 const 和 volatile(九)

        在  C 語言中,我們經常會見到 const volatile 這兩個關鍵字,那麼我們今天就來介紹下這兩個關鍵字。

        先來介紹 const 關鍵字。提起 const 關鍵字,我們可能首先想到的是經過它修飾的變量便是常量了。其實我們這種想法是錯誤的,其實 const 修飾的變量是隻讀的,其本質還是變量。它修飾的局部變量是在棧上分配空間的,它修飾的全局變量在全局數據區分配空間(也就是我們平時所說的 read-only data 段),const 只在編譯期有用,在運行期無效。注意const 修飾的變量不是真的常量,它只是告訴編譯器該變量不能出現在賦值符號的左邊。

        在現代的 C 語言編譯器中,修改 const 全局變量將導致程序崩潰。但是得注意標準 C 語言編譯器不會將 const 修飾的全局變量存儲於只讀存儲區,而是存儲於可修改的全局數據區,其值依然可以改變。

        下來我們來做個示例代碼,驗證下我們所說的,代碼如下:

#include <stdio.h>

const int g_cc = 2;

int main()
{
    const int cc = 1;
    
    int* p = (int*)&cc;
    
    printf("cc = %d\n", cc);
    
    *p = 3;
    
    printf("cc = %d\n", cc);
    
    p = (int*)&g_cc;
    
    printf("g_cc = %d\n", g_cc);
    
    *p = 4;
    
    printf("g_cc = %d\n", g_cc);
    
    return 0;
}

        我們來分析下這個示例代碼,cc 是 const 修飾的局部變量,是分配在棧上的。也就意味着它的屬性是只讀的,其本質還是變量。因而我們可以通過第13行的指針操作來改變它的值,所以第11行和第15行打印出的值分被爲 1 和 3;而 g_cc 不同,因爲它是個 const 修飾的全局變量,因而會被放在只讀數據段。所以它的值不能被改變。以上的分析是基於現代的 C 語言編譯器,如果是以前的 C 語言編譯器(如 BCC)則不會這樣處理,會將全局變量存儲於可修改的全局數據區,其值依然可以改變。我們分別用現代編譯器和 BCC 編譯器編譯,圖一爲現代 C 語言編譯器gcc編譯的結果,圖二爲 BCC 編譯的結果。

圖片.png

                                        圖一

圖片.png

                                                            圖二

        我們可以看到同一份代碼在兩個編譯器上竟然出現了兩個結果,在 gcc 中出現了段錯誤,而在 BCC 中則通過指針順利的改變了由 const 修飾的 g_cc 全局變量變量。由此證明了我們的分析是對的。

        C語言中的 const 使得變量具有隻讀屬性,現代 C 編譯器中的 const 將具有全局生命週期的變量存儲於只讀存儲區,const 不能定義真正意義上的常量!!!

        下面我們來分析這個示例代碼,代碼如下:

#include <stdio.h>

const int g_array[5] = {0};

void modify(int* p, int v)
{
    *p = v;
}

int main()
{
    int const i = 0;
    const static int j = 0;
    int const array[5] = {0};
    
    modify((int*)&i, 1);           // ok
    modify((int*)&j, 2);           // error
    modify((int*)&array[0], 3);    // ok
    modify((int*)&g_array[0], 4);  // error
    
    printf("i = %d\n", i);
    printf("j = %d\n", j);
    printf("array[0] = %d\n", array[0]);
    printf("g_array[0] = %d\n", g_array[0]);
    
    return 0;
}

         在這份代碼中,我們發現變量 i 的定義竟然是 int const 的,其實這樣也就相當於 const int i;由於它是局部變量,分配在棧上。因而在被 const 修飾後,就具有隻讀屬性,所以還是可以通過指針來改變的。但是變量 j 就不一樣啦,它是由 static 修飾的,其具有全局的生命週期,所以是不能通過指針來改變的,因而第17行代碼將會報錯。第18行我們可以通過指針來改變 array 數組第一個元素,而 g_array 數組是全局的,所以就不能通過指針來改變它的值,而已第19行會報錯。同樣的上面的分析我們是基於現代的編譯器,在 BCC 中不適用。下來我們就來編譯下,看看是否如我們所分析的那樣。

        我們用 gcc 編譯直接報段錯誤,那麼我們來把第17和19行註釋掉,再次編譯。

圖片.png

        下來我們再用 BCC 來編譯下,結果如下

圖片.png

        那麼我們發現代碼直接通過,而且值也都改變了。由此可以看出現代的編譯器越來越強大了,已經將 const 的意思表達的更加完善了。下面這張圖更好的闡釋了 const 的意思

圖片.png

        那麼 const 修飾函數參數又是什麼意思呢?它表示在函數體內不希望改變參數的值,const 修飾函數返回值表示返回值不可改變,多用於返回指針的情形。C 語言中的字符串字面量存儲於只讀存儲區,在程序中需要使用 const char* 指針。下來我們來分析個示例,代碼如下:

#include <stdio.h>

const char* f(const int i)
{
    i = 5;
    
    return "hello world";
}

int main()
{
    const char* pc = f(0);
    
    printf("%s\n", pc);
    
    pc[6] = '_';
    
    printf("%s\n", pc);
    
    return 0;
}

        我們可以看出變量 i 是由 const 修飾的,因而第5行的代碼肯定會報錯。在第16行我們想要將中間的那個空格改變成“_”,但是返回的字符字面量是 const 型的,因而不能改變,在這塊也會報錯。我們編譯下看看結果是否如此

圖片.png

        我們可以看出這兩行代碼已經報錯。我們註釋掉這兩行代碼,再次編譯,結果如下

圖片.png

        我們接下來再來分析下 volatile 關鍵字。它可理解爲“編譯器警告指示字”,告訴編譯器必須每次去內存中取變量值,主要修飾可能被多個線程訪問的變量同時也可以修飾可能被未知因數更改的變量。我們在嵌入式的開發中,常常會用的多線程。那麼在這個線程裏一個變量的值被改變,我們在另一個線程裏需要用到它原來的值,但是已經被改變了,這時怎麼辦呢?只能去內存中讀取這個變量的值,這時用 volatile 修飾就OK了。在每次需要用到這個變量時,便會去內存中讀取,雖然比較耗時一點,但是是安全的!

        那麼我們今天學習了 constvolatile 的相關知識,總結如下:1、const 使得變量具有隻讀屬性;2、它不能定義真正意義上的常量;3、const 將具有全局生命期的變量存儲於只讀存儲區;4、volatile 強制編譯器減少優化,必須每次從內存中取值。後面我們會繼續對 C 語言的學習。


         歡迎大家一起來學習 C 語言,可以加我QQ:243343083        

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章