欧美性猛交xxxx免费看_牛牛在线视频国产免费_天堂草原电视剧在线观看免费_国产粉嫩高清在线观看_国产欧美日本亚洲精品一5区

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入理解C++ “static”關(guān)鍵字

CPP開發(fā)者 ? 來源:CPP開發(fā)者 ? 2023-08-14 12:25 ? 次閱讀

大多數(shù) C++ 關(guān)鍵字使用起來都是比較簡單的,但也有少數(shù)相對(duì)復(fù)雜,static 便是其中的一個(gè)代表。

標(biāo)準(zhǔn)往往會(huì)避免為語言增加新的關(guān)鍵字,而是復(fù)用已有的。這使得 static 如今已存在十幾種不同的意思,可以修飾全局,也可以修飾局部;可以修飾函數(shù),也可以修飾變量;還可以和 inline、const、constexpr、constinit 等關(guān)鍵字組合起來使用。

許多 C++ devs 對(duì)其都只處于一個(gè)淺層次的理解,不全面也不深入,用來明所以。通過本文能夠彌補(bǔ)此部分知識(shí)點(diǎn)。

1

內(nèi)存布局

程序由指令和數(shù)據(jù)構(gòu)成,指令和數(shù)據(jù)又存儲(chǔ)在不同的段內(nèi),我們首先要了解執(zhí)行程序的內(nèi)存布局,才能從一個(gè)更底層的視角來理解 static。

常見的程序分段如表格所示:

Sections Meaning
.code/.text 代碼段,存儲(chǔ)編譯后的機(jī)器指令
.data 數(shù)據(jù)段,通常存儲(chǔ)全局變量、靜態(tài)局部變量和常量
.bss Block Started by Symbol的縮寫,用來存儲(chǔ)未初始化的全局變量和靜態(tài)局部變量,實(shí)際并不占據(jù)空間,僅是請求加載器在程序加載到內(nèi)存時(shí),預(yù)留一些空間
.rodata read-only data的縮寫,通常存儲(chǔ)靜態(tài)常量

程序分段有利于區(qū)分指令和數(shù)據(jù),指令通常是只讀的,數(shù)據(jù)是可讀寫或只讀的,分到不同的段內(nèi),設(shè)置不同的權(quán)限,可以防止程序指令被無意修改。

一個(gè)程序編譯后的內(nèi)存布局圖可能如下圖所示:

89815cb8-3a56-11ee-9e74-dac502259ad0.jpg

可以看到,程序被分成了六個(gè)主要的內(nèi)存區(qū)域。因?yàn)榇a指令、數(shù)據(jù)段等往往是固定大小的,所以處于低地址;堆棧可能會(huì)在程序執(zhí)行時(shí)動(dòng)態(tài)增長,所以處于高地址。

同時(shí),不同段的數(shù)據(jù)還代表著不同的存儲(chǔ)時(shí)期。

2

存儲(chǔ)時(shí)期

C++中不同的數(shù)據(jù)存在四個(gè)存儲(chǔ)時(shí)期,分別為 automatic, static, thread 和 dynamic。

automatic 主要指的就是棧上的數(shù)據(jù),它能夠在進(jìn)入某個(gè)作用域時(shí)自動(dòng)申請內(nèi)存,并在離開時(shí)自動(dòng)釋放內(nèi)存。


static 指的主要是 .data/.bss/.rodata 段的數(shù)據(jù),這些數(shù)據(jù)在程序執(zhí)行時(shí)就申請內(nèi)存,等到程序結(jié)束時(shí)才釋放。


而 thread 存儲(chǔ)時(shí)期是 C++11才有的,只有 thread_local 修飾的數(shù)據(jù)才屬此類,它們在線程開始時(shí)申請內(nèi)存,線程結(jié)束時(shí)釋放內(nèi)存。


dynamic 則表示堆上的數(shù)據(jù),也就是使用 new/malloc 申請的內(nèi)存,這些內(nèi)存必須手動(dòng)釋放。當(dāng)然,通過智能指針這些數(shù)據(jù)的生命周期也能夠自動(dòng)管理。

不要把這些存儲(chǔ)時(shí)期與編譯期/運(yùn)行期混淆,它們是編譯原理的概念,按那種尺度來劃分,則存在編譯期-加載期-運(yùn)行期三個(gè)不同的時(shí)期。

編譯期指的是編譯器處理代碼的時(shí)期,該時(shí)期的數(shù)據(jù)符號(hào)地址被翻譯成絕對(duì)地址,是最早就確定的數(shù)據(jù)。constexpr/constint 所修飾的數(shù)據(jù),一般就是在這一時(shí)期分配的內(nèi)存。

編譯后的程序存儲(chǔ)在硬盤上,準(zhǔn)備執(zhí)行時(shí)操作系統(tǒng)需要將它們讀取到 RAM 中,這個(gè)時(shí)期就叫加載期。.data/.rodata 段的數(shù)據(jù)就是在這一時(shí)期分配內(nèi)存的,一個(gè)常見的誤區(qū)就是認(rèn)為 static 數(shù)據(jù)是處于編譯期。

運(yùn)行期是程序已經(jīng)運(yùn)行,指令已經(jīng)開始被CPU處理了。一些額外的內(nèi)存需要分配給現(xiàn)在才存在的數(shù)據(jù),比如 .bss 和堆棧數(shù)據(jù)就屬于這一時(shí)期。

內(nèi)存布局和存儲(chǔ)時(shí)期搞清楚了,下面需要理解鏈接的基本概念。

3

鏈接

不同的數(shù)據(jù)作用范圍也不盡相同,如何調(diào)整數(shù)據(jù)的作用范圍?就是通過那些變量修飾符,它們能改變數(shù)據(jù)的鏈接方式。

每個(gè) .cpp/.cc/.cxx... 文件稱為一個(gè) TU(Translation Units,翻譯單元),有些數(shù)據(jù)只在當(dāng)前 TU 使用,有些數(shù)據(jù)還需要在其他 TUs 使用,前者稱為內(nèi)部鏈接,后者稱為外部鏈接。

每個(gè) TU 就是一個(gè)模塊,鏈接就是將這些模塊組合起來,同時(shí)保證其中所引用的各種符號(hào)都在正確的位置上。

只有在當(dāng)前 TU 中使用的數(shù)據(jù)才需要內(nèi)部鏈接,局部的那些數(shù)據(jù)屬于無鏈接。我們需要關(guān)注的是一個(gè)變量的內(nèi)部鏈接和外部鏈接是如何指定的。

先說內(nèi)部鏈接,這些名稱能夠在整個(gè) TU 使用,以下是其規(guī)則:

  • 命名空間下以 static 修飾的變量、變量模板、函數(shù)和函數(shù)模板;

  • 命名空間下以 const 修飾的變量;

  • 匿名 union 的數(shù)據(jù)成員;

  • 匿名空間下的所有名稱。

再說外部鏈接,這些名稱能夠在不同 TUs 間使用,外部鏈接的名稱甚至能夠和其他語言生成的 TUs 鏈接。規(guī)則如下:

  • 命名空間下沒有以 static 修飾的函數(shù),沒有額外修飾的變量和以 extern 修飾的變量;

  • 命名空間下的枚舉名稱;

  • 命名空間下的類名稱,包含它們的成員函數(shù)、(const) static 數(shù)據(jù)成員、嵌套類/枚舉、首次引入的友元函數(shù);

  • 命名空間下以 static 修飾的非函數(shù)模板;

  • 首次在block scope 下的函數(shù)名、以 extern 修飾的變量名。

暫時(shí)先留個(gè)大概印象,后面還會(huì)再次以具體例子介紹有些規(guī)則。

4

以 static修飾變量

前置概念介紹完了,下面從不同方面來進(jìn)行討論 static 關(guān)鍵字。本節(jié)關(guān)注于修飾變量,這又有全局和局部之分。

4.1

以static修飾全局變量

全局變量處于 static存儲(chǔ)時(shí)期,也對(duì)應(yīng)于加載期,在 main()執(zhí)行之前就已為這些變量分配了內(nèi)存。

如果一個(gè)全局變量被 extern 修飾,則它具有外部鏈接,能夠被其他 TUs 使用。相反,如果一個(gè)全局變量被 static 修飾,它具有內(nèi)部鏈接,只能在當(dāng)前 TU 使用。

一個(gè)例子:

//tu-one.cpp
externintvar_1=42;//externallinkage
staticintvar_2=24;//internallinkage

//tu-two.cpp
#include

//referstothevar_1definedinthetu-one.cpp
externintvar_1;

intmain(){
std::cout<"
";//prints42
}

若是再考慮組合 const 進(jìn)行修飾,情況則又不相同。

如果一個(gè)全局變量沒有使用const 修飾,那么它默認(rèn)就有 extern 鏈接,無需多此一舉再加上 extern 修飾。而對(duì)于這樣一個(gè)變量,如何改變它的外部鏈接方式?只需使用 static 修飾,就可以將它變成內(nèi)部鏈接。

如果一個(gè)全局變量使用了 const/constexpr 修飾,則它默認(rèn)就有了 static 鏈接。此時(shí)如果再加上 static 修飾,也是多此一舉。其他文件此時(shí)無法訪問該全局變量,如何改變呢?前面加上 extern 修飾,就可以讓它變成外部鏈接。

以上內(nèi)容的一個(gè)例子:

//tu-one.cpp
intvar_1=42;//externallinkage by default
externintvar_2=42;//sameasvar_1,butit'sredundant.
staticintvar_3=42;//internallinkage
constintvar_4=42;//internallinkage by default
staticconstintvar_5=42;//sameasvar_4,butit'sredundant.
externconstintvar_6=42;//externallinkage
constexprintvar_7=42;//internallinkagebydefault
staticconstexprintvar_8=42;//sameasvar_7,butit'sredundant.

4.2

以static修飾局部變量

局部變量也要分情況討論一下,先說函數(shù)中的局部變量。

函數(shù)中局部變量的存儲(chǔ)時(shí)期為 automatic,此類變量無鏈接,使用時(shí)在棧上自動(dòng)分配內(nèi)存,離開作用域時(shí)自動(dòng)釋放,只能在當(dāng)前作用域使用。

如果為這樣的局部變量加上 static,就將其存儲(chǔ)時(shí)期由 automatic 改變成了 static,生命周期遍及整個(gè)程序的生命周期。這種變量實(shí)際是先在 .bss 段預(yù)留了空間,等到首次進(jìn)入該函數(shù),才真正為其分配內(nèi)存,此時(shí)初始化的時(shí)機(jī)就不是加載期,而且延遲到了運(yùn)行期,所以這種方式也叫惰性初始化。

這種局部靜態(tài)變量就相當(dāng)于全局變量,不同之處在于它是無鏈接,可見性僅在當(dāng)前函數(shù),而且可以延遲初始化。

4.3

以static修飾成員變量

如果一個(gè)類成員變量以 static修飾,那么該變量只是一個(gè)聲明,需要額外提供定義,類的所有對(duì)象共享此類變量。

classS{
staticintx;//declaration
};

intS::x=0;//definition,initialize outside the class

為什么需要在外部定義呢?

因?yàn)?static 對(duì)象必須滿足 ODR(One Definition Rule),而類一般是在頭文件中聲明,該頭文件可能會(huì)被多個(gè) TUs 包含,每個(gè)對(duì)象必須具備唯一的定義,否則在編譯鏈接時(shí)會(huì)出現(xiàn)問題。所以將它作為一個(gè)聲明,定義在類外部單獨(dú)指定。

但是在某些時(shí)候也可以不用定義,比如:

//Examplefromcppref

structS
{
staticconstintx=0;//staticdatamember
//adefinitionoutsideofclassisrequiredifitisodr-used
};

constint&f(constint&r);

intn=b?(1,S::x)//S::xisnotodr-usedhere
:f(S::x);//S::xisodr-usedhere:adefinitionisrequired

只有在 ODR-used 時(shí)才必須要提供定義,在不需要 lvalue 的表達(dá)式中,它可以直接使用 S::x 的值,此時(shí)經(jīng)歷了 lvalue-to-rvalue 的隱式轉(zhuǎn)換。相反,在需要 lvalue 的表達(dá)式中,則必須提供定義。

注:ODR-used 是標(biāo)準(zhǔn)用來指必須為實(shí)體提供定義的術(shù)語,因?yàn)樗皇潜仨毜模枰蕾嚽榫秤懻?,所以不單?dú)使用 used 來描述。比如一個(gè)虛函數(shù)是非 ODR-used,而一個(gè)純虛函數(shù)是 ODR-used,使用時(shí)必須提供定義。模板只在使用時(shí)才實(shí)例化,這里的使用準(zhǔn)確的描述也應(yīng)該是 ODR-used。

如果嫌在外部定義麻煩,在 C++17 可以采用 inline 來光明正大地違背 ODR,它能夠告訴鏈接器,我想在多個(gè) TUs 之間擁有相同的定義。

classS{
//since C++17
inlinestaticintx=42;
};

在 C++20,由于 constexpr 會(huì)隱式 inline,所以還可以這么寫:

classS{
//since C++20
staticconstexprintx=42;
};

另外,在 C++98,如果以 static const 修飾一個(gè)整型成員數(shù)據(jù),那么也可以在類內(nèi)直接初始化,并且可以保證初始化是在編譯期完成的。

//C++98
structS{
staticconstintx=42;//OK
constinty=42;//since C++11, default member initializer
};

對(duì)于非 static 數(shù)據(jù)成員,自 C++11 開始支持 default member initializer,于是也可以直接在類內(nèi)直接初始化。

5

以static修飾函數(shù)

函數(shù)也分全局函數(shù)和成員函數(shù),以 static 修飾時(shí)也要分別討論。

5.1

以static修飾全局函數(shù)

情況下,全局函數(shù)默認(rèn)是外部鏈接,可以通過前置聲明在多個(gè) TUs 使用。

如果以 static 修飾全局函數(shù),則將其鏈接方式變?yōu)閮?nèi)部鏈接,只能在當(dāng)前 TU 使用。

一個(gè)小例子:

//tu-one.cpp
#include

staticvoidfoo(){
std::cout<"internallinkage
";
};

voidbar(){
std::cout<"externallinkage
";
}

//tu-two.cpp
externvoidfoo();
externvoidbar();//referstothebar()definedinthetu-one.cpp

intmain(){
foo();//Error,undefinedreferenceto'foo()'
bar();//OK
}

5.2

以static修飾成員函數(shù)

之前在【洞悉C++函數(shù)重載決議】里已經(jīng)講解過本節(jié)內(nèi)容。

以 static 修飾的成員函數(shù)增加了一個(gè)隱式對(duì)象參數(shù),它并不是 this 指針,而是為了重載決議能夠正常運(yùn)行所定義的一個(gè)可以匹配任何參數(shù)的對(duì)象參數(shù)。這樣的成員函數(shù)無法訪問其他的非靜態(tài)成員名稱,因?yàn)槟切┟Q都與對(duì)象綁定。

當(dāng)時(shí)編寫的一個(gè)示例:

structS{
voidf(long){
std::cout<"memberversion
";
}

staticvoidf(int){
std::cout<"staticmemberversion
";
}
};

intmain(){
Ss;
s.f(1);//staticmemberversion
}

6

static修飾變量對(duì) Lambdas捕獲參數(shù)的影響

如果是全局變量,那么 Lambdas 無需捕獲便可以直接使用:

intx=42;

intmain(){
//youdon'tneed tocapturea globalvariable
[]{returnx;}();
}

但如果是局部變量,由于它的存儲(chǔ)時(shí)期為 automatic,就必須捕獲才能使用:

intmain(){
intx=42;
//youhavetocapturealocalvariable
[&x]{returnx;}();
}

但如果使用 static 修飾該局部變量,就無需再進(jìn)行捕獲

intmain(){
staticintx=42;

//OK
[]{returnx;}();
}

同理,const/constexpr/constinit 修飾的變量在某些時(shí)候也無需再進(jìn)行捕獲:

constinitintm=42;

intmain(){
constexprintx=42;
constintn=42;

//OK
[]{returnm+x+n;}();
}

7

static constexpr, static constinit

請大家注意我上節(jié)最后一句的用詞,是在某些時(shí)候」也無需進(jìn)行捕獲,使用那些詞修飾并非一定是可以無需捕獲。

準(zhǔn)確地說,非 ODR-used 的數(shù)據(jù)無需捕獲,它可以直接把那個(gè)常量隱式捕獲。

intmain(){
constexprstd::string_viewx="foo";
[]{x;}();  //OK,xisnotodr-used
[]{x.data();}();//error:x.data()isodr-used
}

此時(shí)就可以借助 static constexpr,就可以強(qiáng)保證 Lambdas 可以隱式捕獲該數(shù)據(jù):

intmain(){
staticconstexprstd::string_viewx="foo";
[]{x;}();//OK,xisnotodr-used
[]{x.data();};//OK,x.data()isnot odr-used
}

可以理解為此時(shí)捕獲的不是 lvalue,而是經(jīng)由 lvalue-to-rvalue 的那個(gè)值。

static constinit 也是同理,事實(shí)上 constinit 在局部使用必須添加 static,它只能修飾靜態(tài)存儲(chǔ)期或是線程存儲(chǔ)期的數(shù)據(jù)。

在類中使用 static constexpr 修飾數(shù)據(jù),可以保持?jǐn)?shù)據(jù)既是編譯期,又能夠所有對(duì)象共享一份數(shù)據(jù)。

template<intN>
structS{
staticconstexprintx=N;
};

constinit 和 constexpr 大多時(shí)候都是同理,只是前者是可讀可寫,后者是只讀,除非有不同的意義,否則討論的 constexpr 用法也適用于 constinit。后文不再提及。

static constexpr 的另一個(gè)用處是強(qiáng)保證」發(fā)生于編譯期。constexpr 本身只是「弱保證」,它并不一定發(fā)生于編譯期。

它們的其他用處見第9節(jié)。

8

static const vsconstexpr

前面講過,C++ 以 const 修飾全局變量會(huì)默認(rèn)為內(nèi)部鏈接,所以 static 可以省略不寫。但是局部變量不可省,因?yàn)?static 修飾局部變量時(shí)的意義是改變局部變量的存儲(chǔ)時(shí)期,此時(shí)的 static const必須完整寫出。

全局變量本身的存儲(chǔ)時(shí)期就是 static,加上 const 表示只讀,此時(shí)以 static 修飾的意義是指定其鏈接方式。

局部變量本身的存儲(chǔ)時(shí)期是 automatic,無鏈接,加上 const 依舊表示只讀,此時(shí)以 static 修飾的意義是指定其存儲(chǔ)時(shí)期。

所以對(duì)于全局 (static) const 數(shù)據(jù)來說,是在加載期就分配內(nèi)存了(如存儲(chǔ)時(shí)期那節(jié)所強(qiáng)調(diào),不要誤以為它發(fā)生在編譯期)。而對(duì)于局部 static const 數(shù)據(jù)來說,它實(shí)際分配內(nèi)存是在首次使用時(shí),實(shí)際可能發(fā)生于運(yùn)行期。

constexpr 修飾的變量則不同,它們發(fā)生于編譯期,這其實(shí)是要早于 static const 修飾的變量。

但是經(jīng)過優(yōu)化,它們展現(xiàn)的效果是差不多的,相對(duì)來說,更推薦使用 constexpr。

intmain(){
staticconstintm=42;//sinceC++98
constexprintn=42;//sinceC++11

returnm+n;
}

9

Solvingthe"Static Initialization Order Fiasco"

SIOF是存儲(chǔ)時(shí)期為 static 的這類數(shù)據(jù)在跨 TUs 時(shí)相互依賴所導(dǎo)致的問題,因?yàn)椴煌?TUs 中的這些數(shù)據(jù)初始化順序沒有規(guī)定,引用的數(shù)據(jù)可能還沒初始化。

因此全局變量、靜態(tài)變量、靜態(tài)成員變量都可能會(huì)引起這個(gè)問題。

看如下例子:

//tu-one.cpp
autoget_value(intval)->int{
returnval*2;
}

autotu_one_x=get_value(42);

//tu-two.cpp
#include

externinttu_one_x;
autotu_two_x=tu_one_x;

intmain(){
std::cout<"tu_two_x:"<"
";
}

tu_one_x 跨 TUs 使用,tu_two_x 依賴于它。在編譯時(shí)初始化 tu_two_x,如果此時(shí) tu_one_x 還未初始化,那么結(jié)果就偏離預(yù)期。

這依賴于編譯順序,你只有50%的幾率獲得預(yù)期結(jié)果。

89a16a76-3a56-11ee-9e74-dac502259ad0.png

解決策略一是使用局部靜態(tài)變量替代全局變量。前面講過,局部靜態(tài)變量相當(dāng)于全局變量,而且可以延遲初始化。

//tu-one.cpp
autoget_value(intval)->int{
returnval*2;
}

//autotu_one_x=get_value(42);
autoget_x()->int&{
staticautox=get_value(42);
returnx;
}

//tu-two.cpp
#include

autoget_x()->int&;
autotu_two_x=get_x();

intmain(){
std::cout<"tu_two_x:"<"
";
}

局部靜態(tài)靜態(tài)會(huì)在首次訪問時(shí)初始化,因此在初始化 tu_two_x 之前,就先把 tu_one_x 初始化了。于是不會(huì)再有 SOIF:

89b8a81c-3a56-11ee-9e74-dac502259ad0.png

解決策略二是借助 constinit。前面依舊講過,constinit 發(fā)生于編譯期,而存儲(chǔ)時(shí)期為 static 的數(shù)據(jù)實(shí)際發(fā)生于加載期,SOIF 只是加載期的問題,只要將初始化時(shí)期錯(cuò)開,體現(xiàn)一前一后,就能夠指定順序,從而解決該問題。

//tu-one.cpp
constexprautoget_value(intval)->int{
returnval*2;
}

constinitautotu_one_x=get_value(42);

//tu-two.cpp
#include

externconstinitinttu_one_x;
autotu_two_x=tu_one_x;

intmain(){
std::cout<"tu_two_x:"<"
";
}

此時(shí) tu_one_x 初始化于編譯期,而 tu_two_x 初始化于加載期,所以也不會(huì)存在SOIF。

但是你無法使用 extern constexpr,像如下這樣寫會(huì)編譯失?。?/span>

//tu-two.cpp
externconstexprinttu_one_x;//error:notadefinition
autotu_two_x=tu_one_x;

因?yàn)?constinit 修飾的數(shù)據(jù)是可讀可寫的,而 constexpr 修飾的數(shù)據(jù)是只讀的,定義時(shí)必須要給初值。這里這種寫法被視為只是一個(gè)聲明。

雖然無法使用 extern constexpr,但也是可以借助 constexpr 來解決 SOIF 的,只不過要把所有的實(shí)現(xiàn)全部放到頭文件,然后在另一個(gè)實(shí)現(xiàn)文件中包含該頭文件。本節(jié)最后有一個(gè)相關(guān)例子。

使用 static 修飾的變量與全局變量同理,也提供一個(gè)例子:

//tu-one.h
structS{
staticinttu_one_x;//declaration
};

//tu-one.cpp
#include"tu-one.h"

autoget_value(intval)->int{
returnval*2;
}

//definition
intS::tu_one_x=get_value(42);

//tu-two.cpp
#include"tu-one.h"
#include

staticautotu_two_x=S::tu_one_x;

intmain(){
std::cout<"tu_two_x:"<"
";
}

它們的存儲(chǔ)時(shí)期也是 static,所以也會(huì)產(chǎn)生 SOIF。tu_two_x 的初始化依賴于 S::tu_one_x,因此你也有 50% 的幾率得到正確結(jié)果。

89dbcba8-3a56-11ee-9e74-dac502259ad0.png

通過使用 static constinit,也得以解決此問題。

//tu-one.h
structS{
staticconstinitinttu_one_x;//declaration
};

//tu-one.cpp
#include"tu-one.h"

constexprautoget_value(intval)->int{
returnval*2;
}

//definition
intS::tu_one_x=get_value(42);

//tu-two.cpp
#include"tu-one.h"
#include

staticautotu_two_x=S::tu_one_x;

intmain(){
std::cout<"tu_two_x:"<"
";
}

使用 constinit,所有相關(guān)操作也都得是編譯期完成,所以 get_value() 也加上了 constexpr 修飾。那么 static 在此時(shí)主要指的是所修飾數(shù)據(jù)在所有對(duì)象之間共享,constinit 將它的初始化時(shí)間提前到了編譯期。

89f06126-3a56-11ee-9e74-dac502259ad0.png

但是你不能把 tu_two_x 也以 static constinit 修飾,因?yàn)榫幾g期的值發(fā)生在鏈接之間,在編譯期就得確定,而 tu_two_x 的值又來自于另一個(gè)文件,編譯時(shí)根本就不知道所依賴的那個(gè)常量值。

同理這里也可以使用 static constexpr,但是 constexpr 沒有 constinit 靈活,它是 const 的,所以定義時(shí)就必須跟著初始化。

//tu-one.h
constexprautoget_value(intval)->int{
returnval*2;
}

structS{
staticconstexprinttu_one_x=get_value(42);//definition
};

//tu-two.cpp
#include"tu-one.h"
#include

staticautotu_two_x=S::tu_one_x;

intmain(){
std::cout<"tu_two_x:"<"
";
}

結(jié)果和使用 static constinit 完全相同。

10

static inline

static 能夠指示編譯器數(shù)據(jù)只在單個(gè) TU 使用,即內(nèi)部鏈接;與之相反,inline 能夠指示編譯器數(shù)據(jù)需要在多個(gè) TUs 使用,此時(shí)即使違背了 ODR,也不應(yīng)該報(bào)錯(cuò),屬于外部鏈接。

那如果它們組合使用,會(huì)有怎樣的效果?

讓我們寫個(gè)例子來對(duì)比一下不同的情況編譯之后到底產(chǎn)生了什么內(nèi)容。

////Thisexampleisadaptedfromhttps://gist.github.com/htfy96/50308afc11678d2e3766a36aa60d5f75

//header.hpp
inlineintonly_inline(){return42;}
staticintonly_static(){return42;}
staticinlineintstatic_inline(){return42;}

//tu-one.cpp
#include"header.hpp"

autoget_value_one()->int{
returnstatic_inline()+only_inline()+only_static();
}

//tu-two.cpp
#include"header.hpp"

autoget_value_one()->int;
autoget_value_two()->int{
returnstatic_inline()+only_inline()+only_static();
}

automain()->int{
returnget_value_one()+get_value_two();
}

先編譯 tu-one.cpp,并查看生成的目標(biāo)文件的符號(hào)表:

lkimuk@cppmore:~/Desktop/demo$g++-ctu-one.cpp
lkimuk@cppmore:~/Desktop/demo$readelf-sWtu-one.o|c++filt-t

Symboltable'.symtab'contains8entries:
Num:ValueSizeTypeBindVisNdxName
0:00000000000000000NOTYPELOCALDEFAULTUND
1:00000000000000000FILELOCALDEFAULTABStu-one.cpp
2:00000000000000000SECTIONLOCALDEFAULT2.text
3:00000000000000000SECTIONLOCALDEFAULT6.text._Z11only_inlinev
4:000000000000000011FUNCLOCALDEFAULT2only_static()
5:000000000000000b11FUNCLOCALDEFAULT2static_inline()
6:000000000000000011FUNCWEAKDEFAULT6only_inline()
7:000000000000001636FUNCGLOBALDEFAULT2get_value_one()

readelf 用來查看 Linux 上可執(zhí)行文件的結(jié)構(gòu),-s 表示顯示符號(hào)表,-W 表示以寬格式顯示。c++filt 是 gnu 提供的反 Name Demangling 工具,可以顯示未經(jīng)修飾的函數(shù)名稱。

可以看到,only_static() 和 static_inline() 的綁定方式都是 LOCAL,表示僅在當(dāng)前文件可見;only_inline() 的綁定方式為 WEAK,表示該符號(hào)可被覆蓋,所以在其他文件中也是可見的;get_value_one() 的綁定方式是 GLOBAL,也表示在所有文件中可見。

再來編譯 tu-two.cpp,也查看其目標(biāo)文件的符號(hào)表:

lkimuk@cppmore:~/Desktop/demo$g++-ctu-two.cpp
lkimuk@cppmore:~/Desktop/demo$readelf-sWtu-two.o|c++filt-t

Symboltable'.symtab'contains10entries:
Num:ValueSizeTypeBindVisNdxName
0:00000000000000000NOTYPELOCALDEFAULTUND
1:00000000000000000FILELOCALDEFAULTABStu-two.cpp
2:00000000000000000SECTIONLOCALDEFAULT2.text
3:00000000000000000SECTIONLOCALDEFAULT6.text._Z11only_inlinev
4:000000000000000011FUNCLOCALDEFAULT2only_static()
5:000000000000000b11FUNCLOCALDEFAULT2static_inline()
6:000000000000000011FUNCWEAKDEFAULT6only_inline()
7:000000000000001636FUNCGLOBALDEFAULT2get_value_two()
8:000000000000003a29FUNCGLOBALDEFAULT2main
9:00000000000000000NOTYPEGLOBALDEFAULTUNDget_value_one()

和 tu-two.o 的情況相似,這里不再贅述。

具體來看鏈接之后的情況:

lkimuk@cppmore:~/Desktop/demo$g++tu-one.otu-two.o-omain
lkimuk@cppmore:~/Desktop/demo$readelf-sWmain|c++filt-t

Symboltable'.dynsym'contains6entries:
Num:ValueSizeTypeBindVisNdxName
0:00000000000000000NOTYPELOCALDEFAULTUND
1:00000000000000000FUNCGLOBALDEFAULTUND__libc_start_main@GLIBC_2.34(3)
2:00000000000000000NOTYPEWEAKDEFAULTUND_ITM_deregisterTMCloneTable
3:00000000000000000NOTYPEWEAKDEFAULTUND__gmon_start__
4:00000000000000000NOTYPEWEAKDEFAULTUND_ITM_registerTMCloneTable
5:00000000000000000FUNCWEAKDEFAULTUND__cxa_finalize@GLIBC_2.2.5(2)

Symboltable'.symtab'contains43entries:
Num:ValueSizeTypeBindVisNdxName
0:00000000000000000NOTYPELOCALDEFAULTUND
1:00000000000000000FILELOCALDEFAULTABSScrt1.o
2:000000000000035832OBJECTLOCALDEFAULT3__abi_tag
3:00000000000000000FILELOCALDEFAULTABScrtstuff.c
4:00000000000010700FUNCLOCALDEFAULT14deregister_tm_clones
5:00000000000010a00FUNCLOCALDEFAULT14register_tm_clones
6:00000000000010e00FUNCLOCALDEFAULT14__do_global_dtors_aux
7:00000000000040281OBJECTLOCALDEFAULT25completed.0
8:de00OBJECTLOCALDEFAULT20__do_global_dtors_aux_fini_array_entry
9:00000000000011200FUNCLOCALDEFAULT14frame_dummy
10:dd80OBJECTLOCALDEFAULT19__frame_dummy_init_array_entry
11:00000000000000000FILELOCALDEFAULTABStu-one.cpp
12:000000000000112911FUNCLOCALDEFAULT14only_static()
13:000000000000113411FUNCLOCALDEFAULT14static_inline()
14:00000000000000000FILELOCALDEFAULTABStu-two.cpp
15:000000000000116e11FUNCLOCALDEFAULT14only_static()
16:000000000000117911FUNCLOCALDEFAULT14static_inline()
17:00000000000000000FILELOCALDEFAULTABScrtstuff.c
18:00000000000021d80OBJECTLOCALDEFAULT18__FRAME_END__
19:00000000000000000FILELOCALDEFAULTABS
20:00000000000020040NOTYPELOCALDEFAULT17__GNU_EH_FRAME_HDR
21:de80OBJECTLOCALDEFAULT21_DYNAMIC
22:00000000000040000OBJECTLOCALDEFAULT23_GLOBAL_OFFSET_TABLE_
23:00000000000040280NOTYPEGLOBALDEFAULT24_edata
24:00000000000040180NOTYPEWEAKDEFAULT24data_start
25:00000000000020004OBJECTGLOBALDEFAULT16_IO_stdin_used
26:00000000000000000FUNCWEAKDEFAULTUND__cxa_finalize@GLIBC_2.2.5
27:00000000000011a829FUNCGLOBALDEFAULT14main
28:00000000000040200OBJECTGLOBALHIDDEN24__dso_handle
29:00000000000011c80FUNCGLOBALHIDDEN15_fini
30:00000000000000000FUNCGLOBALDEFAULTUND__libc_start_main@GLIBC_2.34
31:000000000000116311FUNCWEAKDEFAULT14only_inline()
32:000000000000104038FUNCGLOBALDEFAULT14_start
33:00000000000010000FUNCGLOBALHIDDEN11_init
34:00000000000040280OBJECTGLOBALHIDDEN24__TMC_END__
35:000000000000113f36FUNCGLOBALDEFAULT14get_value_one()
36:000000000000118436FUNCGLOBALDEFAULT14get_value_two()
37:00000000000040180NOTYPEGLOBALDEFAULT24__data_start
38:00000000000040300NOTYPEGLOBALDEFAULT25_end
39:00000000000040280NOTYPEGLOBALDEFAULT25__bss_start
40:00000000000000000NOTYPEWEAKDEFAULTUND_ITM_deregisterTMCloneTable
41:00000000000000000NOTYPEWEAKDEFAULTUND__gmon_start__
42:00000000000000000NOTYPEWEAKDEFAULTUND_ITM_registerTMCloneTable

請注意觀察,鏈接之后 only_static() 和 static_inline() 存在兩份拷貝,說明它在每個(gè) TU 都存在一份拷貝。而 only_inline() 只存在一份拷貝,說明 inline 的名稱的確能夠跨 TUs。

全局變量也只存在一份,說明外部鏈接是起作用的。

于是能夠得出結(jié)論,static 是內(nèi)部鏈接,inline 是外部鏈接,static inline 和 static 效果一樣,此時(shí)加上 inline,僅僅是告訴編譯器,可以嘗試內(nèi)聯(lián)一下代碼。

11

總結(jié)

本文深入全面系統(tǒng)地介紹了 static 關(guān)鍵字的方方面面,涉及內(nèi)容又多又雜,又廣又深。

static 是 C++ 中最復(fù)雜的關(guān)鍵字之一,有多達(dá)十幾種不同的意思,而且涉及編譯知識(shí),許多使用形式意思非常細(xì)微。

所有相關(guān)內(nèi)容幾乎都包含在本文當(dāng)中,具體總結(jié)大家就得自己歸納一下了。


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10911

    瀏覽量

    213152
  • 存儲(chǔ)
    +關(guān)注

    關(guān)注

    13

    文章

    4359

    瀏覽量

    86213
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2114

    瀏覽量

    73895
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1642

    瀏覽量

    49318

原文標(biāo)題:深入理解 C++ “static” 關(guān)鍵字

文章出處:【微信號(hào):CPP開發(fā)者,微信公眾號(hào):CPP開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    深入理解C語言:循環(huán)語句的應(yīng)用與優(yōu)化技巧

    能讓你的代碼更加簡潔明了,還能顯著提升程序執(zhí)行效率。本文將詳細(xì)介紹C語言中的三種常見循環(huán)結(jié)構(gòu)——while循環(huán)、for循環(huán)和do...while循環(huán),帶你深入理解
    的頭像 發(fā)表于 12-07 01:11 ?261次閱讀
    <b class='flag-5'>深入理解</b><b class='flag-5'>C</b>語言:循環(huán)語句的應(yīng)用與優(yōu)化技巧

    C語言關(guān)鍵字分別發(fā)生在哪個(gè)階段

    以下C語言關(guān)鍵字,分別發(fā)生在哪個(gè)階段? 第一個(gè),define。 首先得糾正一下,define 并不是C語言里面的關(guān)鍵字,即使加了井號(hào),也不是。 define 屬于
    的頭像 發(fā)表于 11-24 10:31 ?244次閱讀

    C++新手容易犯的十個(gè)編程錯(cuò)誤

    簡單的總結(jié)一下?C++ 新手容易犯的一些編程錯(cuò)誤,給新人們提供一個(gè)參考。 1 有些關(guān)鍵字在 cpp 文件中多寫了 對(duì)于 C++ 類,一些關(guān)鍵字只要寫在 .h 中就好,cpp 中就不用再
    的頭像 發(fā)表于 11-15 12:42 ?502次閱讀

    C語言關(guān)鍵字--typedef

    C語言關(guān)鍵字使用方法學(xué)習(xí)指南!
    的頭像 發(fā)表于 10-07 12:44 ?350次閱讀

    深入理解FPD-link III ADAS解串器HUB產(chǎn)品

    電子發(fā)燒友網(wǎng)站提供《深入理解FPD-link III ADAS解串器HUB產(chǎn)品.pdf》資料免費(fèi)下載
    發(fā)表于 09-06 09:58 ?1次下載
    <b class='flag-5'>深入理解</b>FPD-link III ADAS解串器HUB產(chǎn)品

    使用邊緣AI和Sitara處理器進(jìn)行關(guān)鍵字檢測

    電子發(fā)燒友網(wǎng)站提供《使用邊緣AI和Sitara處理器進(jìn)行關(guān)鍵字檢測.pdf》資料免費(fèi)下載
    發(fā)表于 09-02 11:30 ?0次下載
    使用邊緣AI和Sitara處理器進(jìn)行<b class='flag-5'>關(guān)鍵字</b>檢測

    技術(shù)干貨驛站 ▏深入理解C語言:基本數(shù)據(jù)類型和變量

    C語言中,數(shù)據(jù)類型和變量是編程的基礎(chǔ),也是理解更復(fù)雜概念的關(guān)鍵。數(shù)據(jù)類型決定了變量的內(nèi)存分配、存儲(chǔ)范圍和操作方式,而變量則是存儲(chǔ)數(shù)據(jù)的容器。本篇文章將從基本數(shù)據(jù)類型和變量兩個(gè)方面,帶你深入
    的頭像 發(fā)表于 07-26 17:53 ?2282次閱讀
    技術(shù)干貨驛站 ▏<b class='flag-5'>深入理解</b><b class='flag-5'>C</b>語言:基本數(shù)據(jù)類型和變量

    快速掌握C語言關(guān)鍵字

    C語言中的32個(gè)關(guān)鍵字你知道多少個(gè)呢?根據(jù)關(guān)鍵字的作用分為四類:數(shù)據(jù)類型關(guān)鍵字、控制語句關(guān)鍵字、存儲(chǔ)類型
    的頭像 發(fā)表于 07-06 08:04 ?430次閱讀
    快速掌握<b class='flag-5'>C</b>語言<b class='flag-5'>關(guān)鍵字</b>

    深入理解渲染引擎:打造逼真圖像的關(guān)鍵

    在數(shù)字世界中,圖像渲染是創(chuàng)造逼真視覺效果的核心技術(shù)。渲染引擎,作為這一過程中的關(guān)鍵組件,負(fù)責(zé)將二維或三維的模型、紋理、光照等數(shù)據(jù)轉(zhuǎn)化為人們?nèi)庋劭梢姷亩S圖像。本文將深入探討渲染引擎的工作原理及其在打
    的頭像 發(fā)表于 06-29 08:28 ?399次閱讀
    <b class='flag-5'>深入理解</b>渲染引擎:打造逼真圖像的<b class='flag-5'>關(guān)鍵</b>

    inline關(guān)鍵字被優(yōu)化導(dǎo)致此類函數(shù)被布局在flash內(nèi),怎么處理?

    問題解決。 但是,希望樂鑫官方把idf內(nèi)源碼中的inline關(guān)鍵詞都替換為__attribute((always_inline)),因?yàn)閕nline關(guān)鍵字不可靠! 期待下次idf版本更新能解決此bug,自己修改idf源碼真的是權(quán)宜之計(jì)、臨時(shí)之策。
    發(fā)表于 06-21 12:03

    深入理解FFmpeg閱讀體驗(yàn)》

    : ./configure --host=aarch64-linux --prefix=/home/x264 --enable-shared --disable-asm --enable-static
    發(fā)表于 04-16 22:54

    深入理解 FPGA 的基礎(chǔ)結(jié)構(gòu)

    轉(zhuǎn)載地址:https://zhuanlan.zhihu.com/p/506828648 文章很詳細(xì)的介紹了FPGA的基礎(chǔ)結(jié)構(gòu),能更直觀的理解內(nèi)部結(jié)構(gòu)原理。對(duì)深入學(xué)習(xí)很有幫助。 以下是正文: 這一段
    發(fā)表于 04-03 17:39

    深入理解數(shù)據(jù)備份的關(guān)鍵原則:應(yīng)用一致性與崩潰一致性的區(qū)別

    深入理解數(shù)據(jù)備份的關(guān)鍵原則:應(yīng)用一致性與崩潰一致性的區(qū)別 在數(shù)字化時(shí)代,數(shù)據(jù)備份成為了企業(yè)信息安全的核心環(huán)節(jié)。但在備份過程中,兩個(gè)關(guān)鍵概念——應(yīng)用一致性和崩潰一致性,常常被誤解或混淆。本文旨在闡明
    的頭像 發(fā)表于 03-11 11:29 ?1036次閱讀
    <b class='flag-5'>深入理解</b>數(shù)據(jù)備份的<b class='flag-5'>關(guān)鍵</b>原則:應(yīng)用一致性與崩潰一致性的區(qū)別

    嵌入式C語言面試大挑戰(zhàn)

    C++static關(guān)鍵字除了具有C中的作用還有在類中的使用在類中,static可以用來修飾靜態(tài)數(shù)據(jù)成員和靜態(tài)成員方法靜態(tài)數(shù)據(jù)成員
    發(fā)表于 03-05 14:18 ?389次閱讀
    嵌入式<b class='flag-5'>C</b>語言面試大挑戰(zhàn)

    簡單總結(jié)一下嵌入式C++中常見的錯(cuò)誤形式

    對(duì)于 C++ 類,一些關(guān)鍵字只要寫在 .h 中就好,cpp 中就不用再加上了,比如 virtual、static關(guān)鍵字,如果在 cpp 中多寫,編譯器會(huì)報(bào)錯(cuò)。
    的頭像 發(fā)表于 02-23 09:40 ?588次閱讀