標籤:

指針可以修改const修飾的變數么?

比如:

int const a = 5;

int* p = a;

*p = 100;

請問這樣可以么?

突然想起的問題,身邊又沒有電腦沒辦法敲上去。。。


const在C語言中是表示道義上保證變數的值不會被修改,並不能實際阻止修改,通過指針可以修改常變數的值,但是會出現一些不可知的結果。幾種情況不同,我們一個一個來看。

1、直接賦值

const int a = 3;
a = 5;
// const.c:6:2: error: assignment of read-only variable 『a』

這種情況不用多說,編譯錯。

2、使用指針賦值,@孫健波提到的方法,在gcc中的warning,g++中出現error,是因為代碼寫得不對,由非const的變成const不用顯式的轉換,const變為非const需要顯式轉換,這種情況應當使用顯式的類型轉換。

const int a = 3;
int* b = (int*) a;

printf("a = %d, *b = %d
", a, *b);
*b = 5;
printf("a = %d, *b = %d
", a, *b);

運行結果(註:使用msvc編譯的結果一致):

$ gcc const.c
$ ./a.out
a = 3, *b = 3
a = 5, *b = 5

$ g++ const.cpp
$ ./a.out
a = 3, *b = 3
a = 3, *b = 5

這裡使用g++編譯時,a的值之所以沒有改變,是因為編譯時a是常量,然後被編譯器編譯為立即數了。因此對使用b指針修改不會更改a的值。

值得注意的是,如果a被定義為全局常變數,使用指針修改會引發segment fault。

在函數的原型中,我們也常用const修飾指針,表示函數的實現者在道義上不會去修改這個指針所指向的空間。例如我們熟知的strcpy函數,原型如下:

char* strcpy(char* dst, const char* src);

傳入的參數src類型是const char*,表示函數內部實現不會修改src所指向的空間。之所以說是道義上,是因為在內部通過上述指針強制類型轉換的方式可以修改該空間的值。另外,如果我們聲明了我們不會修改傳入指針的所指向的空間,那麼我們也不應當去修改這塊空間,因為這個傳入的指針可能會是一個不可寫的內存,然後出現段錯誤。


const只是對變數名進行修飾,也就是說你不可以通過這個變數名修改它所在的內存

而並不是說這塊內存不可修改,如果你可以通過其他形式訪問到這塊內存,同樣可以進行修改

const int a = 1;

int* p = (int*)a; // 通過強制類型轉換得到a所在的內存地址

*p = 2; // 通過指針指針直接寫入新數據

printf("a = %d, *p = %d",a,*p);

// 在gcc下輸出為a = 2,*p = 2,也就意味著通過指針p修改了const修飾的變數a

// 在g++下,如@余天升 老師所言,雖然輸出是a = 1,*p = 2,但實際上變數a所在的內存還是被修改了


未定義行為(undefined behavior),也就是說編譯器給你實現成什麼樣都是允許的。慈悲一點說就是非正常非不正常,可正常可不正常。總之,不要這麼用。


個人理解,歡迎挑錯。

這裡不討論編程規範。

1. 作者的代碼理論上是無法乾淨地編譯的,即使不報錯也應該有警告。

2. 關於 const 的語義(Semantic)我認為@余天升說的是不完全對,const 不是強調不會改變,而是強調只讀,但可能會被硬體或其他別名改變,比如下面的定義是對的:

const volatile int a;

有些編譯器(比如Keil for 51)甚至將 const 放入只讀區域,但這改變了 const 的語義,參見 Data in Program Space 的 A Note On const 一節。所以為了程序在符合標準的編譯器下正確編譯運行,應該這麼些:

#include &

int main(void) {
volatile int const a = 5;
int *p = (int *)a;
printf("a = %d, *p = %d
", a, *p);

*p = 100;
printf("a = %d, *p = %d
", a, *p);
return 0;
}

運行結果:

pi@raspberrypi ~/project/test/c
$ gcc test-const.c
pi@raspberrypi ~/project/test/c
$ ./a.out
a = 5, *p = 5
a = 100, *p = 100

用 g++ 運行的結果也是一樣的。


嵌入式裡面const會被編譯器放在flash裡面,這個是硬體決定不可更改的(flash可以擦寫但是需要一些特定的操作,不是cpu簡單的mov指令),但是有一些嵌入式,或者pc代碼都是運行再ram中,只要系統或者編譯器不報錯,理論上你可以用指針修改所有。

所以這個問題是有區別的,不規範的用法可以測試玩玩,實際中還是老老實實規範操作,const本身就是告訴編譯器變數不可更改,修改時報錯,減少失誤的。


在gcc中,如樓上所述。

在g++中:

在int* p = a;這行就會出現編譯錯誤。

a的類型是const int 而不是 int,所以a的值類型是const int *,不能轉換到int *

如果你int* p = a;改成const int* p = a;

那麼接下來的一行賦值就是錯誤的。

因為const類型不能賦值。


答案大部分情況下都是「可以」。

但是這個問題對於做系統開發的人,其實是很難有定論的。

語言的種類。

編譯器是否會在編譯時檢查。

編譯器是否會在編譯時優化。

程序最終鏈接後的分段和布局。

操作系統如何執行最終的二進位程序。

都有關係,所以如果確切要討論一個如此的問題。

最好有預置的場景。

通常來說,沒有一門語言在一個平台上能保證所有的奇怪的,罕見的語法和使用習慣用法都會符合程序員的預期(不管是否是刻意為之)。

這些東西可以從探究底層的的角度來滿足工程師的好奇心。

但一個好的工程師,在項目裡面是絕對不會,也不應該使用過於奇怪的語法的。


推薦閱讀:

int (*pf)(1024)為什麼是函數調用?
為什麼 Windows API 使用 stdcall 調用約定?
c++中的字元串常量為什麼可以賦值給char*?
我想自己用C/C++做一個腳本語言解釋器,但是不知道需要什麼知識?
c語言中實際上不存在賦值語句?

TAG:C編程語言 |