C語言中的Const常量和優化
本文由碼農網 – 唐李川原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃!
這裡有一個關於C語言程式設計中Const優化效果的問題。這個問題延伸的很多問題在過去的二十年都已經被回答了很多次了。從我個人角度來說,我認為都是const的錯誤。
看下面的程式碼:
void foo(const int *); int bar(void) { int x = 0; int y = 0; for (int i = 0; i < 10; i++) { foo(&x); y += x; // this load not optimized out } return y; }
程式碼裡的foo函式有一個const(常量)型別的指標,該指標是foo函式定義者的一個承諾,那就是它不能修改x變數的值。根據這個程式碼資訊,編譯器會假設x變數的值一直是0,同時y變數的值也是0。
然而,在不同的編譯器裡執行程式碼後,檢查編譯器的集合輸出,我們就會發現,x變數在迴圈裡每一次都會載入。下面是gcc 4.9.2的最佳優化提示資訊:
Gcc 4.9.2介紹連結地址: https://linux.cn/lfs/LFS-BOOK-7.7-systemd/chapter06/gcc.html
-O3介紹連結地址: http://blog.chinaunix.net/uid-20662363-id-3036581.html
bar: push rbp push rbx xor ebp, ebp ; y = 0 mov ebx, 0xa ; loop variable i sub rsp, 0x18 ; allocate x mov dword [rsp+0xc], 0 ; x = 0 .L0: lea rdi, [rsp+0xc] ; compute &x call foo add ebp, dword [rsp+0xc] ; y += x (not optmized?) sub ebx, 1 jne .L0 add rsp, 0x18 ; deallocate x mov eax, ebp ; return y pop rbx pop rbp ret
Clang 3.5的輸出(用-fno-unroll-loops命令)是一樣的,除了ebp和ebx做了交換,並且&x的計算已經超出了迴圈的範圍,變成了r14。
-fno-unroll-loops命令介紹連結地址: http://www.cnblogs.com/dxz/archive/2007/06/29/gcc_option_and_env_var.html
是否這兩種編譯器都沒有用到這個有用的資訊?還是對於foo函式來說,有未定義的行為修改了x變數的值?可令人吃驚的是,問題的答案是都沒有。在這種情況下,這裡將會定義一個完整合法的foo函式。
void foo(const int *readonly_x) { int *x = (int *)readonly_x; // cast away const (*x)++; }
關鍵的事情是,記住const修飾型別的變數並不意味著不變。這裡把它作為一個用詞不當的定義。它不是一個優化的工具。它在這裡告訴程式設計者-而不是編譯器-在編譯時間內作為一個工具來catch(捕獲)一個類的具體錯誤資訊。我喜歡它的API,因為它能告訴我們一個function函式如何使用具體的引數,或者如何得到呼叫者期望處理返回的指標。通常的編譯器不足以強大到能夠改變它的行為。
儘管我剛才那麼說了,但有時編譯器也可以利用const來實現程式碼的優化。在C99說明書的§6.7.3裡,有如下的定義:
C99介紹連結地址: http://baike.sogou.com/v469198.htm?fromTitle=c99
如果有人試圖用一個const限制型別修飾通過一個不用const限制型別修飾的有效值修改一個物件的定義,這就好比沒有定義。
最初的x變數是沒有用const限制修飾的,因此這個規則無效。而沒有任何的規則可以無視const而修改一個物件,這不是物件本身的const。這也就是說,以上的foo函式的行為(不當的行為)是沒有為這次呼叫定義行為的。
Bar函式有一個微小的調整,我可以讓這條規則得到應用,同時允許優化器在它上面做一些工作。
const int x = 0;
編譯器現在也許假設foo函式修改了x變數的值,這種行為是沒有定義的,因此這種行為永遠不會發生。不管怎樣,這是一個C語言優化器優化你程式碼的原因的一個重要部分。編譯器不受約束地假設x變數從來沒有改變過,並且允許它優化每個迭代的載入和y變數。
bar: push rbx mov ebx, 0xa ; loop variable i sub rsp, 0x10 ; allocate x mov dword [rsp+0xc], 0 ; x = 0 .L0: lea rdi, [rsp+0xc] ; compute &x call foo sub ebx, 1 jne .L0 add rsp, 0x10 ; deallocate x xor eax, eax ; return 0 pop rbx ret
本次載入的結果消失了,y變數不見了,而且函式的返回值一直是0。
可令人好奇的是,規格說明書上允許編譯器繼續允許。它允許不在棧中的某個位置來分配x變數值,甚至是在read-only(只讀儲存器裡)也可以。例如,它可能像下面這樣執行一次轉換:
static int __x = 0; int bar(void) { for (int i = 0; i < 10; i++) foo(&__x); return 0; }
或者在x86-64(-fPIC引數,小型的程式碼模型)上,我們可以看到更多的指令消失了:
X86-64介紹連結地址: http://baike.sogou.com/v617198.htm?fromTitle=X86-64
-fPIC,small code model的連結地址: http://eli.thegreenplace.net/2012/01/03/understanding-the-x64-code-models
-fPIC介紹連結地址: http://blog.sina.com.cn/s/blog_54f82cc201011op1.html
section .rodata x: dd 0 section .text bar: push rbx mov ebx, 0xa ; loop variable i .L0: lea rdi, [rel x] ; compute &x call foo sub ebx, 1 jne .L0 xor eax, eax ; return 0 pop rbx ret
無論是clang還是gcc都做不到,也許是因為它對混亂不好的程式碼來說更具破壞性。
即使有明確具體的const規則,但只有你自己用const,並且給和你一樣是程式設計師的同伴用,你才明白什麼是const。讓優化器自己就什麼是常量什麼不是常量給自己個解釋理由。
譯文連結:http://www.codeceo.com/article/c-const-optimization.html
英文原文:Const and Optimization in C
翻譯作者:碼農網 – 唐李川
[ 轉載必須在正文中標註並保留原文連結、譯文連結和譯者等資訊。]
相關文章
- C語言中const和#define的區別C語言
- C語言中容易混淆的const關鍵字C語言
- C語言中的#和##C語言
- C++ const常量的理解C++
- C語言中的*和&符號C語言符號
- c語言中的getchar()和EOFC語言
- C語言中 * 和 &的實際理解C語言
- C語言中,&和&&都是做什麼的?C語言
- Golang 學習——常量 const 和 iotaGolang
- c語言中的&的用法C語言
- C語言中“陣列名”和“&陣列名”C語言陣列
- C語言中的getchar和putchar詳解C語言
- C語言中的置0和置1操作C語言
- C語言中sync()C語言
- C語言中有C語言
- C語言 關鍵字const的作用 const int* 和int *const 的區別C語言
- c語言中資料的格式化輸出C語言
- c語言中陣列的宣告與初始化C語言陣列
- PHP 定義常量 define 和 const的區別PHP
- 解析C語言中的sizeofC語言
- C語言中extern的用法C語言
- C語言中&&,||,&,| 的區別C語言
- 優化C++程式碼(3):常量合併優化C++
- 【C】 30_C語言中的字串C語言字串
- C語言中pi=&j和*pi=j的區別C語言
- c語言中陣列的宣告喝初始化的區別和聯絡C語言陣列
- 關於C語言的常量C語言
- C 語言中的 time 函式函式
- c語言中的關鍵字C語言
- C語言中的檔案流C語言
- C語言中的abort函式C語言函式
- 解析C語言中的sizeof (轉)C語言
- C 語言中的 sscanf 詳解
- static在C語言中的作用C語言
- c語言中 *p++ 和 (*p)++ 有什麼區別?以及C語言運算子的優先順序。整理。C語言
- 常量引用或指向常量的指標,其所指物件可以是非const物件(C++)指標物件C++
- c語言中文和ascii碼字元分離C語言ASCII字元
- C語言中函式printf()和函式scanf()的用法C語言函式