(給CPP開發者加星標,提升C/C++技能)
https://blog.csdn.net/songguangfan/article/details/108309181
一次難忘的面試經歷多年前,一次網際網路某廠實習生的面試題,題目的代碼片段很簡單,如下:
1 #include <stdlb.h>
2 int main()
3 {
4 int *restrict pInt = (int*)malloc(4);
5 int *pNewInt = pInt;
6 return 0;
7 }
12345678
面試官問我運行結果是什麼,我居然當時不加思索的回答,編譯器會報錯。當時個人理解是這樣的,首先用malloc函數分配了一個4Bytes大小的內存,並讓pInt指針指向它。同時pInt指針,也被restrict關鍵字修飾。而restrict就是用來表明是訪問一個數據對象的唯一且初始的方式。因此在第5行中,將pInt的值賦值給了另一個指針pNewInt就是錯誤的。但是遺憾的是,這種錯誤編譯器是不會報錯的。下面我們就這個問題來聊一聊restrict關鍵字。
不使用restrict關鍵字restrict關鍵字實際上在C99標準出來以前,編譯器就已經開始支持類似restrict的語句了,如編譯器定義了__restrict。restrict關鍵字,實際上是用來指示編譯器對代碼進行優化的。在分析具體含義以前,我們先來看一個沒有用restrict的例子。
在main函數中定義了三個參數p、q、r。這之後調用了f函數,並把p和q的地址傳遞進去。在f中根據這兩個地址,將p和q分別設置為2和3,最後把p的值2當做函數返回值返回,並賦值給整形變量r。這個代碼很簡單,不過重點不是看它,而是它的反彙編代碼,下面我們通過objdump工具執行objdump -j .text -l -C -S test來生成彙編代碼。 首先來看下main函數的彙編代碼。如下圖所示
在反彙編代碼中的可以看到,分別將p和q的地址分別賦值給了rdi和 rsi寄存器。即rdi保存了p的地址,rsi保存了q的地址。之後調用了f函數。而在f函數的反彙編代碼中,通過如下圖所示能確認rdi確實保存著p的地址
接著,如下圖所示,可以看到通過調用了puts函數向屏幕列印信息
最後,f函數最後用rax所指向區域的值,賦給了eax寄存器,即將p的值放入eax作為返回值返回。
好了,現在有一個疑問,代碼其對應的反彙編代碼幹嘛不直接把立即數2直接賦值給eax,反而多此一舉從rax所指向的內存中把2取出來給eax呢?因為編譯器之所以不直接把2賦值給eax,而非要從內存中獲取,就是擔心代碼中會通過其他指針訪問p所佔的區域。在test.c的代碼中我們通過指針a訪問了p所佔的區域,但是編譯器不知道指針b是否也是指向p所佔的區域,通過指針b是否也對該區域做了修改。因此編譯器只能根據return *a,從內存中將2取出來賦值給eax。
使用restrict關鍵字好了,怎麼樣才能讓編譯器變得聰明一點呢?這個時候restrict就派上用場了。修改上面代碼
運行編譯命令gcc -g -O2 -std=c99 -o test test.c進行編譯,使用反彙編工具objdump查看發現
可以看到直接把2賦給了eax寄存器,作為f的返回值返回了。看明白了吧,restrict關鍵字起作用了,編譯器檢測到指針a和b都使用了restrict關鍵字,也就是說,它們所指向的空間都只能通過指針a和b訪問,不會有其他途徑。這個時候,編譯器就可以放心大膽地進行優化了,直接把2賦給了eax,而不用再從內存中獲取了。這樣,f的執行效率也提高了。這裡還有一點需要注意,C++程序並不支持restrict關鍵字,但是可以使用「__restrict」關鍵字。
- EOF -
關注『CPP開發者』
看精選C++技術文章 . 加C++開發者專屬圈子
↓↓↓
點讚和在看就是最大的支持❤️