對(duì)于C++這樣的語(yǔ)言來(lái)說(shuō)璃哟,除了具有普適、通用等特性外绢慢,在一些實(shí)際應(yīng)用方面也是不容忽視的灿渴,
對(duì)齊支持
數(shù)據(jù)對(duì)齊
在了解為什么數(shù)據(jù)需要對(duì)齊之前,我們可以回顧一下打印結(jié)構(gòu)體的大小這個(gè)C/C++中的經(jīng)典案例胰舆。先看代碼:
#include <iostream>
using namespace std;
struct HowManyBytes{
char a;
int b;
};
int main(){
cout<<"sizeof(char):"<<sizeof(char)>>endl;
cout<<"sizeof(int):"<<sizeof(char)>>endl;
cout<<"sizeof(HowManyBytes):"<<sizeof(HowManyBytes)>>endl;
cout<<endl;
cout<<"offset of char a:"<<offsetof(HowManyBytes,a)>>endl;
cout<<"offset of int b:"<<offsetof(HowManyBytes,b)>>endl;
return 0;
}
結(jié)構(gòu)體HowManyBytes由一個(gè)char類型成員a及一個(gè)int類型成員b組成骚露。編譯上述的代碼,我們可以得到如下結(jié)果:
sizeof(char):1
sizeof(int):4
sizeof(HowManyBytes):8
offset of char a:0
offset of int b:4
很明顯缚窿,a和b兩個(gè)數(shù)據(jù)的長(zhǎng)度分別是1字節(jié)和4字節(jié)棘幸,不過(guò)當(dāng)我們使用sizeof來(lái)計(jì)算HowManyBytes這個(gè)結(jié)構(gòu)體所占用的內(nèi)存空間時(shí),看到其值為8字節(jié)倦零。其中似乎多出來(lái)了3字節(jié)沒(méi)有使用的空間误续。
通常情況下,C/C++結(jié)構(gòu)體中的數(shù)據(jù)會(huì)有一定的對(duì)齊要求扫茅。這里成員b的偏移是4字節(jié)蹋嵌,而成員a只占用了1字節(jié)內(nèi)存空間,這意味著b并非緊鄰a排列葫隙。事實(shí)上栽烂,在我們的平臺(tái)定義上,C/C++的int類型要求對(duì)齊到4字節(jié),即要求int類型數(shù)據(jù)必須放在一個(gè)能夠整除4的地址上腺办;而char要求對(duì)齊到1字節(jié)焰手。這就造成了成員a之后的3字節(jié)空間被空出,通常我們也稱因?yàn)閷?duì)齊而造成的內(nèi)存留空為填充數(shù)據(jù)(padding data)怀喉。
在C++中书妻,每個(gè)類型的數(shù)據(jù)除去長(zhǎng)度等屬性外,都有一項(xiàng)“被隱藏”屬性躬拢,那就是對(duì)齊方式躲履。對(duì)于每個(gè)內(nèi)置或者自定義類型,都存在一個(gè)特定的對(duì)齊方式估灿。對(duì)齊方式通常是一個(gè)整數(shù)崇呵,它表示的是一個(gè)類型的對(duì)象存放的內(nèi)存地址應(yīng)滿足的條件。在這里馅袁,我們簡(jiǎn)單地將其稱為對(duì)齊值。
對(duì)齊的數(shù)據(jù)在讀寫上會(huì)有性能上的優(yōu)勢(shì)荒辕。比如頻繁使用的數(shù)據(jù)如果與處理器的高速緩存器大小對(duì)齊汗销,有可能提高緩存性能。而數(shù)據(jù)不對(duì)其可能造成一些不良的后果抵窒,比較嚴(yán)重的當(dāng)屬導(dǎo)致應(yīng)用程序退出弛针。典型的,如在有的平臺(tái)上李皇,硬件將無(wú)法讀取不按字對(duì)齊的某些類型數(shù)據(jù)削茁,這個(gè)時(shí)候硬件會(huì)拋出異常(如bus error) 來(lái)終止程序。更為普遍的掉房,在一些平臺(tái)上茧跋,不按照字對(duì)齊的數(shù)據(jù)會(huì)造成數(shù)據(jù)讀取效率低下。因此卓囚,在程序設(shè)計(jì)時(shí)瘾杭,保證數(shù)據(jù)對(duì)齊是保證正確有效讀寫數(shù)據(jù)的一個(gè)基本條件。
通常由于底層硬件的設(shè)計(jì)或用途不同哪亿,以及編程語(yǔ)言本身在基本(內(nèi)置)類型的定義上的不同粥烁,相同的類型定義在不同的平臺(tái)上會(huì)有不同的長(zhǎng)度,以及不同的對(duì)齊要求蝇棉。
在C++語(yǔ)言中讨阻,我們可以通過(guò)sizeof查詢數(shù)據(jù)長(zhǎng)度,但C++語(yǔ)言卻沒(méi)有對(duì)對(duì)齊方式有關(guān)的查詢或者設(shè)定進(jìn)行標(biāo)準(zhǔn)化篡殷,而語(yǔ)言本身又允許自定義類型钝吮、模板等諸多特性。編譯器無(wú)法完全找到正確的對(duì)齊方式,這會(huì)在使用時(shí)造成困難搀绣。代碼如下:
#include<iostream>
using namespace std;
//自定義的ColorVector,擁有32字節(jié)的數(shù)據(jù)
struct ColorVector{
double r;
double g;
double b;
double a;
};
int main(){
//使用C++11中的alignof來(lái)查詢ColorVector的對(duì)齊方式
cout<<"alignof(ColorVector):"<<alignof(ColorVector)<<endl;
return 1;
}
在如上代碼飞袋,我們使用了C++11標(biāo)準(zhǔn)定義的alignof函數(shù)來(lái)查看數(shù)據(jù)的對(duì)齊方式。從結(jié)果上看链患,可以看出ColorVector在實(shí)驗(yàn)機(jī)上依然是對(duì)齊到8字節(jié)的地址邊界上巧鸭。
c++ alignof(ColorVector):8
現(xiàn)在的計(jì)算機(jī)通常會(huì)支持許多向量指令,而ColorVector正好是4組8字節(jié)的浮點(diǎn)數(shù)數(shù)據(jù)麻捻,很有潛力改造為能直接操作的向量數(shù)據(jù)纲仍。這樣一來(lái),為了能夠高效地讀寫ColorVector大小的數(shù)據(jù)贸毕,我們最好能將其對(duì)齊到32字節(jié)的地址邊界上郑叠。
下面代碼中,我們利用C++11新提供的修飾符alignas來(lái)重新設(shè)定ColorVector的對(duì)齊方式明棍。
#include <iostream>
using namespace std;
//自定義的ColorVector乡革,對(duì)齊到32字節(jié)的邊界
struct alignas(32)ColorVector{
double r;
double g;
double b;
double a;
};
int main(){
//使用C++11中的alignof來(lái)查詢ColorVector的對(duì)齊方式
cout<<"alignof(ColorVector):"<<alignof(ColorVector)<<endl;
return 1;
}
運(yùn)行后,我們會(huì)得到如下結(jié)果:
c++ alignof(ColorVector): 32
正如我們所看到的摊腋,指定數(shù)據(jù)ColorVector對(duì)齊到32字節(jié)的地址邊界上沸版,只需要聲明alignas(32)即可。
C++11的alignof和alignas
C++11在新標(biāo)準(zhǔn)中為了支持對(duì)齊兴蒸,主要引入兩個(gè)關(guān)鍵字:操作符alignof视粮、對(duì)齊描述符(alignment-specifier) alignas。操作符alignof的操作數(shù)表示一個(gè)定義完整的自定義類型或者內(nèi)置類型或者變量橙凳,返回的值是一個(gè)std:: size_t類型的整型常量蕾殴。如同sizeof操作符一樣,alignof獲得的也是一個(gè)與平臺(tái)相關(guān)的值岛啸。
#include <iostream>
using namespace std;
class InComplete;
struct Completed{};
int main(){
int a;
long long b;
auto& c=b;
char d[1024];
//對(duì)內(nèi)置類型和完整類型使用alignof
cout<<alignof(int)<<endl<<alignof(Completed)<<endl; //4,1
//對(duì)變量钓觉、引用或者數(shù)組使用alignof
cout<<alignof(a)<<endl<<alignof(b)<<endl<<alignof(c)<<endl<<alignof(d)<<endl;//4,8,8,1
//本句無(wú)法通過(guò)編譯,Incomplete類型不完整
//cout<<alignof(Incomplete)<<endl;
}
使用alignof很簡(jiǎn)單值戳,基本上沒(méi)有什么特別的限制议谷。在上面代碼中,類型定義不完整的class InComplete是無(wú)法通過(guò)編譯的堕虹。其他的規(guī)則則基本跟大多數(shù)人想象的相同:引用c于其引用的數(shù)據(jù)b對(duì)齊值相同卧晓,數(shù)組的對(duì)齊值由其元素決定。
對(duì)齊描述符alignas赴捞,既可以接受常量表達(dá)式逼裆,也可以接受類型作為參數(shù),比如:c++ alignas(double) char c;
效果跟 c++ alignas(alignof(double)) char c;
是一樣的赦政。
注意 在C++11標(biāo)準(zhǔn)之前胜宇,我們也可以使用一些編譯器的擴(kuò)展來(lái)描述對(duì)齊方式耀怜,比如GNU格式的attribute((aligned(8))) 就是一個(gè)廣泛被接受的版本。
我們?cè)谑褂贸A勘磉_(dá)式作為alignas的操作符的時(shí)候桐愉,其結(jié)果必須是以2的自然數(shù)冪次作為對(duì)齊值财破。對(duì)齊值越大,我們稱其對(duì)齊要求越高从诲;而對(duì)齊值越小左痢,其對(duì)齊要求也越低。由于2的冪次的關(guān)系系洛,能夠滿足嚴(yán)格對(duì)齊要求的對(duì)齊方式也總是能夠滿足要求低的對(duì)齊值的俊性。
在C++11標(biāo)準(zhǔn)中規(guī)定了一個(gè)“基本對(duì)齊值”。一般情況下其值通常等于平臺(tái)上支持的最大標(biāo)量類型數(shù)據(jù)的對(duì)齊值(常常是long double)描扯。我們可以通過(guò)alignof(std::max_align_t)來(lái)查詢其值定页。而像我們?cè)诖a中設(shè)定ColorVector對(duì)齊值到32字節(jié)(超過(guò)標(biāo)準(zhǔn)對(duì)齊)的做法稱為擴(kuò)展對(duì)齊(extended alignment)。不過(guò)即使使用了擴(kuò)展對(duì)齊绽诚,也并非意味著程序員可以隨心所欲典徊。每個(gè)平臺(tái)上,系統(tǒng)能夠支持的對(duì)齊值總是有限的憔购,程序中如果聲明了超過(guò)平臺(tái)要求的對(duì)齊值宫峦,則按照C++標(biāo)準(zhǔn)該程序是不規(guī)范的,這可能會(huì)導(dǎo)致未知的編譯時(shí)或者運(yùn)行時(shí)錯(cuò)誤玫鸟。
對(duì)齊描述符可以作用于各種數(shù)據(jù)。具體來(lái)說(shuō)犀勒,可以修飾變量屎飘、類的數(shù)據(jù)成員等,而位域(field)以及用register聲明的變量則不可以贾费。代碼如下:
alignas(double)void f(); //錯(cuò)誤:alignas不能修飾函數(shù)
alignas(double) unsigned char c[sizeof(double)]; //正確
extern unsigned char c[sizeof(double)];
alignas(float)
extern unsigned char c[sizeof(double)]; //錯(cuò)誤:不同對(duì)齊方式的變量定義
C++11標(biāo)準(zhǔn)建議用戶在聲明同一個(gè)變量的時(shí)候使用同樣的對(duì)齊方式以免發(fā)生意外钦购。不過(guò)C++11并沒(méi)有規(guī)定聲明變量采用了不同的對(duì)齊方式就終止編譯器的編譯。
下面代碼實(shí)現(xiàn)了一個(gè)固定容量但是大小隨著所用的數(shù)據(jù)類型變化的容器類型褂萧,如代碼所示:
#include <iostream>
using namespace std;
struct alignas(alignof(double)*4) ColorVector{
double r;
double g;
double b;
double a;
};
//固定容量的模板數(shù)組
template<typename T>
class FixedCapacityArray{
public:
void push_back(T t){/*在data中加入t變量*/}
//...
//一些其他成員函數(shù)押桃、成員變量等
//...
char alignas(T) data[1024]={0};
//int length=1024/sizeof(T);
};
int main(){
FixedCapacityArray<char> arrCh;
cout<<"alignof(char):"<<alignof(char)<<endl;
cout<<"alignof(arrCh.data):"<<alignof(arrCh.data)<<endl;
FixedCapacityArray<ColorVector> arrCV;
cout<<"alignof(ColorVector):"<<alignof(ColorVector)<<endl;
cout<<"alignof(arrCV.data):"<<alignof(arrCV.data)<<endl;
return 1;
}
//編譯選項(xiàng):clang++8-1-6.cpp-std=c++11
在本例中,F(xiàn)ixedCapacityArray固定使用1024字節(jié)的空間导犹,但由于模板的存在唱凯,可以實(shí)例化為各種版本。這樣一來(lái)谎痢,我們可以在相同的內(nèi)存使用量的前提下磕昼,做出多種(內(nèi)置或者自定義)版本的數(shù)組。對(duì)于arrCh节猿,由于數(shù)組中的元素都是char類型票从,所以對(duì)齊到1就行了,而對(duì)于我們定義的arrCV, 必須使其符合ColorVector的擴(kuò)展對(duì)齊,即對(duì)齊到8字節(jié)的內(nèi)存邊界上峰鄙。在這個(gè)例子中浸间,起到關(guān)鍵作用的代碼是:
char alignas(T) data[1024]={0};
該句指示data[1024]這個(gè)char類型數(shù)組必須按照模板參數(shù)T的對(duì)齊方式進(jìn)行對(duì)齊。
alignof(char):1
alignof(arrCh.data):1
alignof(ColorVector):32
alignof(arrCV.data):32
由于char數(shù)組默認(rèn)對(duì)齊值為1吟榴,會(huì)導(dǎo)致data[1024]數(shù)組也對(duì)齊到1.這肯定不是編寫FixedCapacityArray的程序員愿意見(jiàn)到的魁蒜。
在C++11標(biāo)準(zhǔn)引入alignas修飾符之前,這樣的固定容量的泛型數(shù)組有時(shí)可能遇到因?yàn)閷?duì)齊不佳而導(dǎo)致的性能損失(甚至程序錯(cuò)誤)煤墙,這給庫(kù)的編寫者帶來(lái)了很大的困擾梅惯。而引入alignas能夠解決這些移植性的困難。
C++11對(duì)于對(duì)齊的支持并不限于alignof操作符及alignas操作符仿野。在STL庫(kù)中铣减,還內(nèi)建了std::align函數(shù)來(lái)動(dòng)態(tài)地根據(jù)指定的對(duì)齊方式調(diào)整數(shù)據(jù)塊的位置。該函數(shù)的原型如下:
void* align(std:: size_t alignment, std:: size_t size,void*&ptr,std:: size_t&space);
該函數(shù)在ptr指向的大小為space的內(nèi)存中進(jìn)行對(duì)齊方式的調(diào)整脚作,將ptr開始的size大小的數(shù)據(jù)調(diào)整為按alignment對(duì)齊葫哗。代碼如下:
#include <iostream>
#include <memory>
using namespace std;
struct ColorVector{
double r;
double g;
double b;
double a;
};
int main(){
size_t const size=100;
ColorVector* const vec=new ColorVector[size];
void*p=vec;
size_t sz=size;
void* aligned=align(alignof(double)*4,size,p,sz);
if(aligned!=nullptr)
cout<<alignof(p)<<endl;
}
嘗試將vec中的內(nèi)容按alignof(double)*4的對(duì)齊值進(jìn)行對(duì)齊(不過(guò)在編寫本書的時(shí)候,我們的編譯器還沒(méi)有支持std:: align這個(gè)新特性球涛,因此代碼僅供參考)
(.....剩下的這個(gè)部分不是特別懂劣针,放在以后把相關(guān)的知識(shí)學(xué)完后再補(bǔ))
通用屬性
語(yǔ)言擴(kuò)展到通用屬性
隨著C++語(yǔ)言的演化和編譯器的發(fā)展,人們常會(huì)發(fā)現(xiàn)標(biāo)準(zhǔn)提供的語(yǔ)言能力不能完全滿足要求亿扁。于是編譯器廠商或組織為了滿足編譯器客戶的需求捺典,設(shè)計(jì)出一系列的語(yǔ)言擴(kuò)展(language extension)來(lái)擴(kuò)展語(yǔ)法。這些擴(kuò)展語(yǔ)法并不存在于C++/C標(biāo)準(zhǔn)中从祝,卻有可能擁有較多的用戶襟己。
擴(kuò)展語(yǔ)法中比較常見(jiàn)的就是"屬性"。屬性是對(duì)語(yǔ)言中的實(shí)體對(duì)象(比如函數(shù)牍陌、變量擎浴、類型等)附加一些的額外注解信息,其用來(lái)實(shí)現(xiàn)一些語(yǔ)言及非語(yǔ)言層面的功能毒涧,或是實(shí)現(xiàn)優(yōu)化代碼等的一種手段贮预。不同編譯器有不同的屬性語(yǔ)法。比如對(duì)于g++契讲,屬性是通過(guò)GNU的關(guān)鍵字__attribute__來(lái)聲明的仿吞。程序員只需要簡(jiǎn)單地聲明:
__attribute__((attribute-list))
即可為程序中的函數(shù)、變量和類型設(shè)定一些額外信息怀泊,以便編譯器可以進(jìn)行錯(cuò)誤檢查和性能優(yōu)化等茫藏。代碼如下所示:
extern int area(int n) __attribute__((const));
int main(){
int i;
int areas=0;
for(i=0;i<10;i++){
areas+=area(3)*i;
}
}
//編譯選項(xiàng):g++ -c 8-2-1.cpp
這里的const屬性告訴編譯器:本函數(shù)返回值只依賴于輸入,不會(huì)改變?nèi)魏魏瘮?shù)外的數(shù)據(jù)霹琼,因此沒(méi)有任何副作用务傲。在此情況下凉当,編譯器可以對(duì)area函數(shù)進(jìn)行優(yōu)化處理。area(3)的值只需要計(jì)算一次售葡,編譯之后可以將area(3)視為循環(huán)中的常量而只使用其計(jì)算結(jié)果看杭,從而大大提高了程序的執(zhí)行性能。
事實(shí)上挟伙,在GNU對(duì)C/C++的擴(kuò)展中我們可以看到很多不同的attribute屬性玫芦。常見(jiàn)的如format愈捅、noreturn、const和aligned等,具體含義和用法讀者可以參考GNU的在線文檔忽刽。
在Windows平臺(tái)上稚补,我們會(huì)找到另外一種關(guān)鍵字__declspec讨韭。__declspec是微軟用于指定存儲(chǔ)類型的擴(kuò)展屬性關(guān)鍵字能扒。用戶只要簡(jiǎn)單地在聲明變量時(shí)加上:
c++ __declspec(extended-decl-modifier)
即可設(shè)定額外的功能。以對(duì)齊方式為例齿坷,在C++11之前桂肌,微軟平臺(tái)的程序員可以使用__declspec(align(x)) 來(lái)控制變量的對(duì)齊方式,代碼如下:
__declspec(align(32)) struct Struct32{
int i;
double d;
};
代碼中永淌,結(jié)構(gòu)體Struct32被對(duì)齊到32字節(jié)的地址邊界崎场,其起始地址必須是32的倍數(shù)。同樣的遂蛀,微軟也定義了很多__declspec屬性谭跨,如noreturn、oninline李滴、align饺蚊、dllimport、dllexport等悬嗓,具體含義和用法可以參考微軟網(wǎng)站上的介紹。
事實(shí)上裕坊,在擴(kuò)展語(yǔ)言能力的時(shí)候包竹,關(guān)鍵字往往會(huì)成為一種選擇。GNU和微軟只能選擇"屬性"這樣的方式籍凝,是為了盡可能避免與用戶自定義的名稱沖突周瞎。同樣,在C++11標(biāo)準(zhǔn)的設(shè)立過(guò)程中饵蒂,也面臨著關(guān)鍵字過(guò)多的問(wèn)題声诸。于是C++11語(yǔ)言制定者決定增加了通用屬性這個(gè)特性。
C++11的通用屬性
C++11語(yǔ)言中的通用屬性使用了左右雙中括號(hào)的形式:
c++ [[attribute-list]]
這樣設(shè)計(jì)得好處是:既不會(huì)消除語(yǔ)言添加或者重載關(guān)鍵字的能力退盯,又不會(huì)占用用戶空間的關(guān)鍵字的名字空間彼乌。
語(yǔ)法上泻肯,C++11的通用屬性可以作用于類型、變量慰照、名稱灶挟、代碼塊等。對(duì)于作用于聲明的通用屬性毒租,既可以寫在聲明的起始處稚铣,也可以寫在聲明的標(biāo)識(shí)符之后。而對(duì)于作用于整個(gè)語(yǔ)句的通用屬性墅垮,則應(yīng)該寫在語(yǔ)句起始處惕医。而出現(xiàn)在以上兩種規(guī)則描述的位置之外的通用屬性,作用于哪個(gè)實(shí)體跟編譯器具體的實(shí)現(xiàn)有關(guān)算色。
第一個(gè)例子是關(guān)于通用屬性應(yīng)用于函數(shù)的抬伺,具體如下:
[[attr1]] void func[[attr2]]();
這里,[[attr1]]出現(xiàn)在函數(shù)定義之前剃允,而[[attr2]]則位于函數(shù)名稱之后沛简,根據(jù)定義,[[attr1]]和[[attr2]]均可以作用于函數(shù)[func]斥废。
[[attr1]] int array[[attr2]][10];
這跟第一個(gè)例子很類似椒楣,根據(jù)定義,[[attr1]] 和 [[attr2]] 均可以作用于數(shù)組array牡肉。下面這個(gè)例子比較復(fù)雜:
[[attr1]] class C[[attr2]]{}[[attr3]] c1[[attr4]], c2[[attr5]];
這個(gè)例子聲明了類C及其類型的變量c1和c2捧灰。本語(yǔ)句中,一共有5個(gè)不同的屬性统锤。按照C++11的定義毛俏,[[attr1]] 和[[attr4]] 會(huì)作用于c1, [[attr1]]和[[attr5]] 會(huì)作用于c2,[[attr2]] 出現(xiàn)在聲明之后,僅作用于類C饲窿,而[[attr3]] 所作用的對(duì)象則跟具體實(shí)現(xiàn)有關(guān)煌寇。(其實(shí)這個(gè)地方?jīng)]看太明白,attr2和attr3具體是怎么作用的)
[[attr1]] L1:
switch(value){
[[attr2]] case1: //do something...
[[attr3]] case2: //do something...
[[attr4]] break;
[[attr5]] default: //do something...
}
[[attr6]] goto L1;
這里逾雄,[[attr1]] 作用于標(biāo)簽L1阀溶,[[attr2]] 和[[attr3]] 作用于case 1和case 2表達(dá)式,[[attr4]] 作用于break, [[attr5]] 作用于default表達(dá)式鸦泳,[[attr6]] 作用于goto語(yǔ)句银锻。下面的for語(yǔ)句也是類似的:
[[attr1]] for(int i=0;i<top;i++){
//do something...
}
[[attr2]] return top;
這里,[[attr1]] 作用于for表達(dá)式做鹰,[[attr2]] 作用于return击纬。下面是函數(shù)有參數(shù)的情況:
[[attr1]] int func([[attr2]] int i,[[attr3]] int j)
{
//do something
[[attr4]] return i+j;
}
[[attr1]] 作用于函數(shù)func,[[attr2]] 和[[attr3]] 分別作用于整型參數(shù)i和j,[[attr4]] 作用于return 語(yǔ)句。
事實(shí)上钾麸,在現(xiàn)有C++11標(biāo)準(zhǔn)中更振,只預(yù)定義了兩個(gè)通用屬性炕桨,分別是[[noreturn]] 和 [[carries_dependency]]。
預(yù)定義的通用屬性
C++11預(yù)定義的通用屬性包括[[noreturn]] 和 [[carries_dependency]] 兩種殃饿。
[[noreturn]] 是用于標(biāo)識(shí)不會(huì)返回的函數(shù)的谋作。這里必須注意,不會(huì)返回和沒(méi)有返回值的(void)函數(shù)的區(qū)別乎芳。
沒(méi)有返回值的void函數(shù)在調(diào)用完成后遵蚜,調(diào)用者會(huì)接著執(zhí)行函數(shù)后的代碼;而不會(huì)返回的函數(shù)在被調(diào)用完成后奈惑,后續(xù)代碼不會(huì)再被執(zhí)行吭净。
[[noreturn]] 主要用于標(biāo)識(shí)那些不會(huì)將控制流返回給原調(diào)用函數(shù)的函數(shù),典型的例子有:有終止應(yīng)用程序語(yǔ)句的函數(shù)肴甸、有無(wú)限循環(huán)語(yǔ)句的函數(shù)寂殉、有異常拋出的函數(shù)等。通過(guò)這個(gè)屬性原在,開發(fā)人員可以告知編譯器某些函數(shù)不會(huì)將控制流返回給調(diào)用函數(shù)友扰,這能幫助編譯器產(chǎn)生更好的警告信息,同時(shí)編譯器也可以做更多的諸如死代碼消除庶柿、免除為函數(shù)調(diào)用者保存一些特定寄存器等代碼優(yōu)化工作村怪。
下面代碼:
void DoSomething1();
void DoSomething2();
[[noreturn]] void ThrowAway(){
throw "expection"; //控制流跳轉(zhuǎn)到異常處理
}
void Func(){
DoSomething1();
ThrowAway();
DoSomething2(); // 該函數(shù)不可到達(dá)
}
由于ThrowAway 拋出了異常,DoSomething2永遠(yuǎn)不會(huì)被執(zhí)行浮庐。這個(gè)時(shí)候?qū)hrowAway標(biāo)記為noreturn的話甚负,編譯器會(huì)不再為ThrowAway之后生成調(diào)用DoSomething2的代碼。當(dāng)然审残,編譯器也可以選擇為Func函數(shù)中的DoSomething2做出一些警告以提示程序員這里有不可到達(dá)的代碼梭域。
不返回的函數(shù)除了是有異常拋出的函數(shù)外,還有可能是有終止應(yīng)用程序語(yǔ)句的函數(shù)搅轿,或是有無(wú)限循環(huán)語(yǔ)句的函數(shù)等病涨。事實(shí)上,在C++11的標(biāo)準(zhǔn)庫(kù)中璧坟,我們都能看到形如:
[[noreturn]] void abort(void) noexcept;
這樣的函數(shù)聲明没宾。最常見(jiàn)的是abort函數(shù)。abort總是會(huì)導(dǎo)致程序運(yùn)行的停止沸柔,甚至連自動(dòng)變量的析構(gòu)函數(shù)以及本該在atexit() 時(shí)調(diào)用的函數(shù)全都不調(diào)用就直接退出了。因此聲明為[[noreturn]] 是有利于編譯器優(yōu)化的铲敛。
盡量不要對(duì)可能會(huì)有返回值的函數(shù)使用[[noreturn]]褐澎。下面代碼就是一個(gè)錯(cuò)誤的例子:
#include<iostream>
using namespace std;
[[noreturn]] void Func(int i){
//當(dāng)參數(shù)i的值為0時(shí),該函數(shù)行為不可估計(jì)
if(i<0)
throw "negative";
else if(i>0)
throw "positive";
}
int main(){
Func(0);
cout<<"Returned"<<endl;//無(wú)法執(zhí)行該句
return 1;
}
代碼清單中伐蒋,F(xiàn)unc調(diào)用后的打印語(yǔ)句永遠(yuǎn)不會(huì)執(zhí)行工三,因?yàn)镕unc被聲明為[[noreturn]].不過(guò)由于函數(shù)作者的疏忽迁酸,忘記了i==0時(shí),F(xiàn)unc運(yùn)行結(jié)束時(shí)還是會(huì)返回mian的俭正。在我們的實(shí)驗(yàn)機(jī)上奸鬓,編譯運(yùn)行該例子會(huì)在運(yùn)行時(shí)發(fā)生"段錯(cuò)誤"。當(dāng)然掸读,具體的錯(cuò)誤情況可能會(huì)根據(jù)編譯器和運(yùn)行時(shí)環(huán)境的不同而有所不同串远。不過(guò)總的來(lái)說(shuō),程序員必須審慎使用[[noreturn]].
另外一個(gè)通用屬性[[carries_dependency]] 則跟并行情況下的編譯器優(yōu)化有關(guān)儿惫。事實(shí)上澡罚,[[carries_depency]] 主要是為了解決弱內(nèi)存模型平臺(tái)上使用memory_order_consume內(nèi)存順序枚舉問(wèn)題。
memory_order_consume的主要作用是保證對(duì)當(dāng)前 "原子類型數(shù)據(jù)" 的讀取操作先于所有之后關(guān)于該原子變量的操作完成肾请,但它不影響其他原子操作的順序留搔。要保證這樣的"先于發(fā)生" 的關(guān)系,編譯器往往需要根據(jù)memory_model枚舉值在原子操作間構(gòu)建一系列的依賴關(guān)系铛铁,以減少在弱一致性模型的平臺(tái)上產(chǎn)生內(nèi)存柵欄隔显。不過(guò)這樣的關(guān)系則往往會(huì)由于函數(shù)的存在而被破壞。比如下面的代碼:
tomic<int*> a;
...
int*p=(int*)a.load(memory_order_consume);
func(p);
上面的代碼中饵逐,編譯器在編譯時(shí)可能并不知道func函數(shù)的具體實(shí)現(xiàn)括眠,因此,如果要保證a.load先于任何關(guān)于a(或是p)的操作發(fā)生梳毙,編譯器往往會(huì)在func函數(shù)之前加入一條內(nèi)存柵欄哺窄。然而,如果func的實(shí)現(xiàn)是:
void func(int*p){
//... 假設(shè)p2是一個(gè)atomic<int*>的變量
p2.store(p,memory_order_release)
}
由于p2.store使用了memory_order_release的內(nèi)存順序账锹,因此萌业,p2.store對(duì)p的使用會(huì)被保證在任何關(guān)于p的使用之后完成。這樣一來(lái)奸柬,編譯器在func函數(shù)之前加入的內(nèi)存柵欄就變得毫無(wú)意義生年,且影響了性能。同樣的情況也會(huì)發(fā)生在函數(shù)返回的時(shí)候廓奕。
解決的方法就是使用[[carries_dependency]]抱婉。該通用屬性既可以標(biāo)識(shí)函數(shù)參數(shù),又可以標(biāo)識(shí)函數(shù)的返回值桌粉。
當(dāng)標(biāo)識(shí)函數(shù)的參數(shù)時(shí)蒸绩,它表示數(shù)據(jù)依賴隨著參數(shù)傳遞進(jìn)入函數(shù),即不需要產(chǎn)生內(nèi)存柵欄铃肯。
而當(dāng)標(biāo)識(shí)函數(shù)的返回值時(shí)患亿,它表示數(shù)據(jù)依賴隨著返回值傳遞出函數(shù),同樣也不需要產(chǎn)生內(nèi)存柵欄押逼。
下面是相關(guān)的例子:
#include <iostream>
#include <atomic>
using namespace std;
atomic<int*> p1;
atomic<int*> p2;
atomic<int*> p3;
atomic<int*> p4;
//定義了4個(gè)原子類型
void func_in1(int*val){
cout<<*val<<endl;
}
void func_in2(int*[[carries_dependency]] val){
p2.store(val,memory_order_release); //p2.store對(duì)p的使用會(huì)被保證在任何關(guān)于p的使用之后完成步藕。
cout<<*p2<<endl;
}
[[carries_dependency]] int*func_out(){
return(int*)p3.load(memory_order_consume); //p3.load對(duì)p的使用會(huì)被保證在任何關(guān)于p的使用之前完成惦界。
}
void Thread(){
int* p_ptr1=(int*)p1.load(memory_order_consume); //L1
cout<<*p_ptrl<<endl; //L2
func_in1(p_ptr1); //L3
func_in2(p_ptr1); //L4
int*p_ptr2=func_out(); //L5
p4.store(p_ptr2,memory_order_release); //L6
cout<<*p_ptr2<<endl;
}
在代碼中,L1句中咙冗,p1.load采用了memory_order_consume的內(nèi)存順序沾歪,因此任何關(guān)于p1或者p_ptr1的原子操作,必須發(fā)生在L1句之后雾消。
這樣一來(lái)灾搏,L2將由編譯器保證其執(zhí)行必須在L1之后(通過(guò)編譯器正確的指令排序和內(nèi)存柵欄)。
而當(dāng)編譯器在處理L3時(shí)仪或,由于func_in1對(duì)于編譯器而言并沒(méi)有聲明[[carries_dependency]]屬性确镊,編譯器則可能采用保守的方法,在func_in1調(diào)用表達(dá)式之前插入內(nèi)存柵欄范删。
而編譯器在處理L4句時(shí)蕾域,由于函數(shù)func_in2使用了[[carries_dependency]], 編譯器則會(huì)假設(shè)函數(shù)體內(nèi)部會(huì)正確地處理內(nèi)存順序,因此不再產(chǎn)生內(nèi)存柵欄指令到旦。
事實(shí)上func_in2中也由于p2.store使用內(nèi)存順序memory_order_release, 因而不會(huì)產(chǎn)生任何的問(wèn)題旨巷。
而當(dāng)編譯器處理L5句時(shí),由于func_out的返回值使用了[[carries_dependency]]添忘,編譯器也不會(huì)在返回前為p3.load(memory_order_consume) 插入內(nèi)存柵欄指令去保證正確的內(nèi)存順序采呐。
而在L6行中,我們看到p4.store使用了memory_order_release, 因此func_out不產(chǎn)生內(nèi)存柵欄也是毫無(wú)問(wèn)題的搁骑。
與[[noreturn]]相同的是斧吐,[[carries_dependency]] 只是幫助編譯器進(jìn)行優(yōu)化,這符合通用屬性設(shè)計(jì)的原則仲器。 當(dāng)讀者使用的平臺(tái)是弱內(nèi)存模型的時(shí)候煤率,并且很關(guān)心并行程序的執(zhí)行性能時(shí),可以考慮使用 [[carries_dependency]]乏冀。
Unicode 支持
字符集蝶糯、編碼和Unicode
無(wú)論是哪種狀態(tài),計(jì)算機(jī)總是使用兩種不同的狀態(tài)來(lái)作為基本信息辆沦,即二進(jìn)制信息昼捍。而要標(biāo)識(shí)現(xiàn)實(shí)生活中更為復(fù)雜的實(shí)體,則需要通過(guò)多個(gè)這樣的基本信息的組合來(lái)完成≈叮現(xiàn)在使用最為廣泛的ASCII字符編碼就出現(xiàn)了妒茬。
基本ASCII的字符使用了7個(gè)二進(jìn)制位進(jìn)行標(biāo)識(shí),這意味著總共可以標(biāo)識(shí)128種不同的字符蔚晨。這對(duì)英文字符(以為一些控制字符郊闯、標(biāo)點(diǎn)符號(hào)等)來(lái)說(shuō)綽綽有余,不過(guò)隨著計(jì)算機(jī)在全世界普及,非字符構(gòu)成的語(yǔ)言(如中文)也需要得到支持团赁,128個(gè)字符對(duì)于全世界眾多語(yǔ)言而言就顯得力不從心了。
通常情況下谨履,我們將一個(gè)標(biāo)準(zhǔn)中能夠表示的所有字符的集合稱為字符集欢摄。通常,我們稱ISO/Unicode所定義的字符集為Inicode笋粟。在Unicode中怀挠,每個(gè)字符占據(jù)一個(gè)碼位(Code point)。Unicode字符集總共定義了1 114 112個(gè)這樣的碼位害捕,使用從0到10FFFF的十六進(jìn)制數(shù)唯一地表示所有的字符绿淋。不過(guò)不得不提的是,雖然字符集中的碼位唯一尝盼,但由于計(jì)算機(jī)存儲(chǔ)數(shù)據(jù)通常是以字節(jié)為單位的吞滞,而且出于兼容之前的ASCII、大數(shù)小段數(shù)段盾沫、節(jié)省存儲(chǔ)空間等諸多原因裁赠,通常情況下,我們需要一種具體的編碼方式來(lái)對(duì)字符碼位進(jìn)行存儲(chǔ)赴精。比較常見(jiàn)的基于Unicode字符集的編碼方式有UTF-8佩捞、UTF-16及UTF-32。
注意蕾哟,事實(shí)上一忱,現(xiàn)行桌面系統(tǒng)中,Windows內(nèi)部采用了UTF-16的編碼方式谭确,而Mac OS帘营、Linux等則采用了UTF-8編碼方式。
C++11中的Unicode支持
在C++98標(biāo)準(zhǔn)中琼富,為了支持Unicode仪吧,定義了“寬字符”的內(nèi)置類型wchar_t. 不過(guò)不久程序員便發(fā)現(xiàn)C++標(biāo)準(zhǔn)對(duì)wchar_t的“寬度”顯然太過(guò)容忍,在Windows上鞠眉,多數(shù)wchar_t被實(shí)現(xiàn)為16位寬薯鼠,而在Linux上,則被實(shí)現(xiàn)為32位械蹋。事實(shí)上出皇,C++98標(biāo)準(zhǔn)定義中,wchar_t的寬度是由編譯器實(shí)現(xiàn)決定的哗戈。理論上郊艘,wchar_t的長(zhǎng)度可以是8位、16位或者32位。這樣帶來(lái)的最大的問(wèn)題是纱注,程序員寫出的包含wchar_t的代碼通常不可移植畏浆。
C++11引入以下兩種新的內(nèi)置數(shù)據(jù)類型來(lái)存儲(chǔ)不同編碼長(zhǎng)度的Unicode數(shù)據(jù)。
A char16_t: 用于存儲(chǔ)UTF-16編碼的Unicode數(shù)據(jù)狞贱。
B char32_t: 用于存儲(chǔ)UTF-32編碼的Unicode數(shù)據(jù)刻获。
至于UTF-8編碼的Unicode數(shù)據(jù),C++11還是使用8字節(jié)寬度的char類型的數(shù)組來(lái)保存瞎嬉。而char16_t和char32_t的長(zhǎng)度則猶如其名稱所顯示的那樣蝎毡,長(zhǎng)度分別為16字節(jié)和32字節(jié),對(duì)任何編譯器或者系統(tǒng)都是一樣的氧枣。此外沐兵,C++11還定義了一些常量字符串的前綴。在聲明常量字符串的時(shí)候便监,這些前綴聲明可以讓編譯器使字符串按照前綴類型產(chǎn)生數(shù)據(jù)扎谎。事實(shí)上,C++11一共定義了3種這樣的前綴:
u8表示UTF-8編碼
u表示為UTF-16編碼
U表示為UTF-32編碼
3種前綴對(duì)應(yīng)著3種不同的Unicode編碼茬贵。一旦聲明了這些前綴簿透,編譯器會(huì)在產(chǎn)生代碼的時(shí)候按照相應(yīng)的編碼方式存儲(chǔ)。以上3種前綴加上基于寬字符wchar_t的前綴“L”, 及不加前綴的普通字符串字面量解藻,算來(lái)在C++11中老充,一共有了5種方式來(lái)聲明字符串字面量,其中4種是前綴表達(dá)的螟左。
不要將各種前綴字符串字面量連續(xù)聲明啡浊,因?yàn)闃?biāo)準(zhǔn)定義除了UTF-8和寬字符字符串字面量同時(shí)聲明會(huì)沖突外,其他字符串字面量的組合最終會(huì)產(chǎn)生什么結(jié)果胶背,以及會(huì)按照什么類型解釋巷嚣,是由編譯器實(shí)現(xiàn)自行決定的。因此應(yīng)該盡量避免這種不可移植的字符串字面量聲明方式钳吟。
C++11中還規(guī)定了一些簡(jiǎn)明的方式廷粒,即在字符串中用'\u'加4個(gè)十六進(jìn)制數(shù)編碼的Unicode碼位(UTF-16)來(lái)標(biāo)識(shí)一個(gè)Unicode字符。比如'\u4F60' 表示的就是Unicode中的中文字符 "你"红且,而'\u597D' 則是Unicode中的 "好"坝茎。此外,也可以通過(guò)'\U' 后跟8個(gè)十六進(jìn)制數(shù)編碼的Unicode碼位(UTF-32)的方式來(lái)書寫Unicode字面常量暇番。
下面代碼例子如下:
#include <iostream>
using namespace std;
int main(){
char utf8[] =u8 "\u4F60\u597D\u597D\u554A";
char16_t utf16[] =u "hello";
char32_t utf32[] =U "hello equals\u4F60\u597D\u554A";
cout<<utf8<<endl;
cout<<utf16<<endl;
cout<<utf32<<endl;
char32_t u2[] =u "hello"; //Error
char u3[] = U "hello"; //Error
char16_t u4=u8 "hello";
}
在本例中嗤放,我們聲明了3中不同類型的Unicode字符串utf8、utf16和utf32壁酬。由于無(wú)論對(duì)哪種Unicode編碼次酌,英文的Unicode碼位都相同恨课,因此只有非英文使用了"\u"的碼位方式來(lái)標(biāo)志。
也就是說(shuō)岳服,一旦使用了Unicode字符串前綴剂公,這個(gè)字符串的類型就確定了,僅能放在相應(yīng)類型的數(shù)組中吊宋。
u2诬留、u3、u4就是因?yàn)轭愋筒黄ヅ涠荒芡ㄟ^(guò)編譯贫母。
如果我們注釋掉不能通過(guò)的定義,編譯并運(yùn)行盒刚,可以得到以下輸出:
你好啊
0x7fffaf087390
0x7fffaf087340
對(duì)應(yīng)于char utf8[] =u8"\u4F60\u597D\u554A"這句腺劣,該UTF-8字符串對(duì)應(yīng)的中文是“你好啊”。而對(duì)于utf16和utf32變量因块,我們本來(lái)期望它們分別輸出"hello" 及 "hello equals你好啊"橘原。不過(guò)實(shí)驗(yàn)機(jī)上我們都只得到了一串?dāng)?shù)字輸出。原因如下:
用戶要在自己的系統(tǒng)上看到正確的Unicode文字涡上,還需要輸出環(huán)境趾断、編譯器,甚至是代碼編輯器等的支持吩愧。我們可以按照編寫代碼芋酌、編譯、運(yùn)行的順序來(lái)看看它們對(duì)整個(gè)Unicode字符串輸出的影響雁佳。
(剩下的這部分的UTF部分的介紹比較高級(jí)脐帝,暫時(shí)還理解不了那么多)
中間跳過(guò)一些章節(jié)
原生字符串字面量
原生字符串字面量(raw string literal)并不是一個(gè)新鮮的概念,在許多編程語(yǔ)言中糖权,我們都可以看到對(duì)原生字符串字面量的支持堵腹。 原生字符串使用戶書寫的字符串 “所見(jiàn)即所得”,不再需要如'\t'星澳、'\n'等控制字符來(lái)調(diào)整字符串中的格式疚顷,這對(duì)編程語(yǔ)言的學(xué)習(xí)和使用都是具有積極意義的。
順應(yīng)這個(gè)潮流禁偎,在C++11中腿堤,終于引入了原生字符串字面量的支持。C++11中原生字符串的聲明相當(dāng)簡(jiǎn)單届垫,程序員只需要在字符串前加入前綴释液,即字母R,并在引號(hào)中用使用括號(hào)左右標(biāo)識(shí)装处,就可以聲明該字符串為原生字符串了误债。
#include <iostream>
using namespace std;
int main(){
cout<<R"(hello,\n
world)"<<endl;
return 0;
}
輸出如下浸船,可以看到'\n'并沒(méi)有被解釋為換行。
hello,\n
world
而對(duì)于Unicode的字符串寝蹈,也可以通過(guò)相同的方式聲明李命。聲明UTF-8、UTF-16箫老、UTF-32的原生字符串字面量封字,將其前綴分別設(shè)為u8R、uR耍鬓、UR就可以了阔籽。不過(guò)有一點(diǎn)需要注意,使用了原生字符串的話牲蜀,轉(zhuǎn)義字符就不能使用了笆制,這會(huì)給想使用\u或者\(yùn)U的方式寫Unicode字符的程序員帶來(lái)一定影響。下面看代碼:
#include <iostream>
using namespace std;
int main(){
cout<<u8R"(\u4F60,\n
\u597D)"<<endl;
cout<<u8R"(你好)"<<endl;
cout<<sizeof(u8R"(hello)") <<"\t"<<u8R"(hello)"<<endl;
cout<<sizeof(uR"(hello)") <<"\t"<<uR"(hello)"<<endl;
cout<<sizeof(UR"(hello)") <<"\t"<<UR"(hello)"<<endl;
return 0;
}
運(yùn)行結(jié)果如下:
![結(jié)果.png-19.6kB][1]
可以看到涣达,當(dāng)程序員試圖使用\u將數(shù)字轉(zhuǎn)義為Unicode的時(shí)候在辆,原生字符串會(huì)保持程序員所寫的字面值,所以這樣的企圖并不能如愿以償度苔。而借助文本編輯器直接輸入中文字符匆篓,反而可以在實(shí)驗(yàn)機(jī)的環(huán)境下在文件中有效地保存UTF-8的字符(因?yàn)榫庉嬈靼凑誙TF-8編碼保存了文件)。程序員應(yīng)該注意到編輯器使用的編碼對(duì)Unicode的影響寇窑。而在之后面的sizeof運(yùn)算符中鸦概,我們看到了不同編碼下原生字符串字面量的大小,跟其聲明的類型是完全一致的疗认。(我沒(méi)有看出是完全一致的)
此外完残,原生字符串字面量也像C的字符串字面量一樣遵從連接規(guī)則。代碼如下:
#include <iostream>
using namespace std;
int main(){
char u8string[] =u8R"(你好)""=hello";
cout<<u8string<<endl; //輸出"你好=hello"
cout<<sizeof(u8string)<<endl; //15
return 0;
}
代碼中的原生字符串字面量和普通的字符串字面量會(huì)被編譯器自動(dòng)連接起來(lái)横漏。整個(gè)字符串有2個(gè)3字節(jié)的中文字符谨设,以及8個(gè)ASCII字節(jié),加上自動(dòng)生成的\0缎浇,字符串的總長(zhǎng)度為15字節(jié)扎拣。與非原生字符串字面量一樣,連接不同前綴的(編碼)的字符串有可能導(dǎo)致不可知的結(jié)果素跺,所以程序員總是應(yīng)該避免這樣使用字符串二蓝。
(問(wèn):在程序設(shè)計(jì)時(shí),如何保證數(shù)據(jù)對(duì)齊)