您好,登錄后才能下訂單哦!
前言
static是 c++ 的關鍵字,顧名思義是表示靜態(tài)的含義。它在 c++ 中既可以修飾變量也可以修飾函數(shù)。那當我們使用 static 時,編譯器究竟做了哪些事情呢?
早先面試中被問到 static 關鍵字,感覺既熟悉又陌生。熟悉是都知道如何去使用它,陌生又來自不知道它究竟對我們程序做了什么。今天就來好好復習下這個關鍵字,本文的重點也在第三部分。
先看一下示例代碼:
test1.cpp
#include <iostream> extern int a_int; extern void func2(); static char c_array[10000]; void func1() { static int a_tmp = 0; std::cout << a_tmp++ << std::endl; return; } int main(int argc, char **argv) { a_int = 1; //靜態(tài)局部變量示例 for (auto i = 0; i < 5; i++) { func1(); } //比較靜態(tài)全局變量的地址示例 std::cout << static_cast<const void *>(c_array) << std::endl; func2(); return 0; }
test2.cpp
#include <iostream> int a_int; static char c_array[1000]; void func2() { std::cout << static_cast<const void *>(c_array) << std::endl; return 0; }
1 先說說 extern
extern 關鍵字用于告訴編譯器,在其他的模塊中尋找相應的定義
為什么 static 前要先說 extern 呢?因為他們就像相互對立的一對關鍵字,所以 extern 與 static 一起用時編譯器會報錯~
1.1 extern 用于修飾變量
以示例代碼中的 a_int 變量為例,假設其他的變量和函數(shù)不存在
我們先 將 extern 關鍵字去掉(test1.cpp:2) ,然后執(zhí)行步驟:
00000000000000d0 S _a_int
可以看到 a_int 為一個未初始化的符號。說明符號在 test1.o 中已經(jīng)被定義了。此時直接編譯( g++ -o test1 test1.cpp )是不會報錯的。
然后我們再 將 extern 關鍵字加上(test1.cpp:2) ,并重復上面步驟觀察符號
nm test1.o
會發(fā)現(xiàn) test1.o 中沒有該符號的定義。并且再編譯會報錯:
Undefined symbols for architecture x86_64:
"_a_int", referenced from:
_main in test1-ed3c01.o
ld: symbol(s) not found for architecture x86_64
很明顯,鏈接器沒有找到 a_int 的定義。此時只需要將 test2.o 加入再編譯(g++ -o test test1.cpp test2.cpp)就可以啦
注:此時如果去掉 main 函數(shù)中對 a_int 變量的引用,也可以編譯通過,畢竟 a_int 在程序中實際沒有用到
1.2 extern 用于修飾函數(shù)
以示例代碼中的 func2() 函數(shù)為例
因為在 main 函數(shù)中調(diào)用了 func2(),所以需要在 main 之前進行函數(shù)聲明。 但此時的函數(shù)聲明無論加不加 extern 其實 并無多少區(qū)別
1.3 extern 用于指定編譯類型
因為 C++ 編譯時會進行 name mangling[wiki] ,導致所看到的函數(shù)與實際編譯后的符號差距很大。在某些情況下會導致鏈接時找不到符號的問題
此時可以使用
extern "C" { ... }
這樣在范圍內(nèi)的代碼都將按照 C 的格式進行編譯
static 關鍵字在我看來的作用是
1.能夠改變變量的存儲方式
2.能夠改變變量與函數(shù)的訪問范圍
2.1 static 用于修飾變量
我們都知道當程序經(jīng)過編譯后:
對于局部變量,當我們在變量前加上 static 時,就是告訴了編譯器將該變量放入靜態(tài)數(shù)據(jù)區(qū)。既函數(shù)退出時不會將該變量析構掉,當我們下次再調(diào)用改函數(shù)依然可以取得內(nèi)存中的這個變量。
例如 test1.cpp 中,每次調(diào)用 func1() 時 a_tmp 變量都不會被銷毀,最后輸出
0
1
2
3
4
對于全局變量,加上 static 關鍵字后該變量只能用于當前的文件。
例如 test1.cpp 中的 c_array,加上 static 后只能在當前源文件使用。
此時如果我們再在 test2.cpp 中定義一個同名的全局靜態(tài)數(shù)組進行編譯(g++ -o test test1.cpp test2.cpp)并且輸出他們的地址
test1.cpp[c_array]0x10bb5e100 test2.cpp[c_array]0x10bb60810
可以看到兩個地址是不同的,所以雖說是同名的兩個全局變量。但都經(jīng)過 static 修飾后,他們實際還是兩個地址不同相互獨立的變量。
那么再試一下,將 test1.cpp 中的
static char c_array[10000];
修改為
extern char c_array[10000];
然后再編譯(g++ -o test test1.cpp test2.cpp)可以看到
Undefined symbols for architecture x86_64:
"_c_array", referenced from:
_main in test1-5d6201.o
ld: symbol(s) not found for architecture x86_64
這當然是因為 test2.cpp 中的 c_array 還是有 static 進行修飾的,導致我們無法在 test1.cpp 文件中訪問到。那就將 static 去掉,看到結果
test1.cpp[c_array]0x10b1e2100
test2.cpp[c_array]0x10b1e2100
它們的地址相同對應的同一塊內(nèi)存,是同一個變量!
2.2 STATIC 用于修飾函數(shù)
static 對于函數(shù)于變量其實比較類似,它限定了函數(shù)只能在當前的模塊中使用。
假如我們將 test2.cpp 中的 func2() 函數(shù)加上 static 關鍵字,那么編譯(g++ -o test test1.cpp test2.cpp)也會報錯找不到符號
Undefined symbols for architecture x86_64:
"func2()", referenced from:
_main in test1-80a5c0.o
ld: symbol(s) not found for architecture x86_64
3 關于 text、bss 與 data 段
關于數(shù)據(jù)段、編譯、鏈接方面的知識非常推薦看看<<程序員的自我修養(yǎng):鏈接、裝載與庫>>
3.1 局部變量的編譯
是否曾經(jīng)好奇函數(shù)內(nèi)的臨時變量經(jīng)過編譯會變成什么樣子?
假設我們寫了如下代碼,并編譯成名為 test 的可執(zhí)行文件
int main() { char s1[11] = "helloworld"; char s2[11] = "helloworld"; return 0; }
那么可以通過 objdump -DS test觀察到 main 函數(shù)中有如下片段(有省略)
Disassembly of section .text:
.....
00000000004005b0 <main>:
4005b4: 48 b8 68 65 6c 6c 6f movabs $0x726f776f6c6c6568,%rax
4005bb: 77 6f 72
4005be: 48 89 45 f0 mov %rax,-0x10(%rbp)
4005c2: 66 c7 45 f8 6c 64 movw $0x646c,-0x8(%rbp)
4005c8: c6 45 fa 00 movb $0x0,-0x6(%rbp)
4005cc: 48 b8 68 65 6c 6c 6f movabs $0x726f776f6c6c6568,%rax
4005d3: 77 6f 72
4005d6: 48 89 45 e0 mov %rax,-0x20(%rbp)
4005da: 66 c7 45 e8 6c 64 movw $0x646c,-0x18(%rbp)
4005e0: c6 45 ea 00 movb $0x0,-0x16(%rbp)
......
觀察下 0x646c 和 0x726f776f6c6c6568,轉(zhuǎn)化成 ascii 就是
100 108 114 111 119 111 108 108 101 104
對應的字符
‘d' ‘l' ‘r' ‘o' ‘w' ‘o' ‘l' ‘l' ‘e' ‘h',看出來了吧,編譯器將 “helloworld” 以立即數(shù)的方式寫到了 text 段內(nèi)。
然后通過 readelf -a test會發(fā)現(xiàn)并沒有 s1 與 s2 的符號。
現(xiàn)在將代碼改為這樣又會如何?
static char s1[11] = "helloworld"; static char s2[11] = "helloworld";
繼續(xù)通過 objdump -DS test觀察發(fā)現(xiàn) main 中發(fā)生了改變
Disassembly of section .text:
00000000004005b0 <main>:
4005b0: 55 push %rbp
4005b1: 48 89 e5 mov %rsp,%rbp
4005b4: b8 00 00 00 00 mov $0x0,%eax
4005b9: 5d pop %rbp
4005ba: c3 retq
4005bb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
通過 readelf -a test可以看到新增了兩個地址不同的符號,由此可見 static 確實改變了變量的存儲方式
Symbol table '.symtab' contains 66 entries:
Num: Value Size Type Bind Vis Ndx Name
37: 000000000060102c 11 OBJECT LOCAL DEFAULT 24 _ZZ4mainE2s2
38: 0000000000601037 11 OBJECT LOCAL DEFAULT 24 _ZZ4mainE2s1
那么如果指向常量呢?稍微改下
const char *s1 = "helloworld"; const char *s2 = "helloworld";
繼續(xù)通過 objdump 觀察到 main 中有這兩代碼,很明顯了 0x400660存儲著我們的 “helloworld”的字符串常量
00000000004005b0 <main>:
4005b4: 48 c7 45 f8 60 06 40 movq $0x400660,-0x8(%rbp)
4005bb: 00
4005bc: 48 c7 45 f0 60 06 40 movq $0x400660,-0x10(%rbp)
找到這個地址,發(fā)現(xiàn)這個地址屬于 .rodata 段。這就是我們常說用來保存字面值常量的數(shù)據(jù)段。
Disassembly of section .rodata:
0000000000400658 <__dso_handle>:
...
400660: 68 65 6c 6c 6f pushq $0x6f6c6c65
400665: 77 6f ja 4006d6 <__dso_handle+0x7e>
400667: 72 6c jb 4006d5 <__dso_handle+0x7d>
400669: 64 fs
觀察下十六進制的值,就是我們的 “helloworld” 沒錯啦。
3.2 全局變量的編譯
那么對于全局變量又應該是如何存儲的呢?
首先我們知道無論靜態(tài)還是非靜態(tài)的變量都應該存儲在靜態(tài)數(shù)據(jù)區(qū)。我們熟悉的靜態(tài)數(shù)據(jù)區(qū)就有 .bss 和 .data。
.bss 在編譯時實際上不占據(jù)空間,只有在運行時才會由被分配空間。那么還是來驗證下
char a_array[10000]; static char b_array[10000]; int main() { return 0; }
編譯一下(g++ -o test test.cpp),然后通過 size 命令觀察(size test)
text data bss dec hex filename
1320 588 20048 21956 55c4 test
可以看出 a_array 和 b_array 都實際記錄在 .bss 段,并且 .data段的大小顯然不符合我們定義的數(shù)組大小。通過 ll test會發(fā)現(xiàn)文件大小不足10000 字節(jié),所以可以肯定的是申請的這兩個數(shù)組在編譯時并為被分配內(nèi)存。
那么繼續(xù)改一下看看
char a_array[10000] = "helloworld"; static char b_array[10000];
繼續(xù)使用 size test看下
text data bss dec hex filename
1320 10616 10032 21968 55d0 test
data 段和文件都多出了 10000 多字節(jié)?。?!
這就是因為 a_array 進行了初始化,所以編譯器為其分配了內(nèi)存。同理如果 b_array 也進行了初始化,那么大小還會增加。
tips:
如果進行了初始化,但是內(nèi)存中還是 0 值的話,編譯器依舊不會為其分配內(nèi)存的,例如
int a_array[10000] = {0};
總結
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。