深入理解C++11:C++11新特性解析與應(yīng)用之 融入實(shí)際應(yīng)用

對(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ì)齊)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末指厌,一起剝皮案震驚了整個(gè)濱河市刊愚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踩验,老刑警劉巖鸥诽,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件商玫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡牡借,警方通過(guò)查閱死者的電腦和手機(jī)拳昌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钠龙,“玉大人炬藤,你說(shuō)我怎么就攤上這事〔昀铮” “怎么了沈矿?”我有些...
    開封第一講書人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)咬腋。 經(jīng)常有香客問(wèn)我细睡,道長(zhǎng),這世上最難降的妖魔是什么帝火? 我笑而不...
    開封第一講書人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮湃缎,結(jié)果婚禮上犀填,老公的妹妹穿的比我還像新娘。我一直安慰自己嗓违,他們只是感情好九巡,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蹂季,像睡著了一般冕广。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上偿洁,一...
    開封第一講書人閱讀 52,807評(píng)論 1 314
  • 那天撒汉,我揣著相機(jī)與錄音,去河邊找鬼涕滋。 笑死睬辐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宾肺。 我是一名探鬼主播溯饵,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锨用!你這毒婦竟也來(lái)了丰刊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤增拥,失蹤者是張志新(化名)和其女友劉穎啄巧,沒(méi)想到半個(gè)月后寻歧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棵帽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年熄求,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片逗概。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弟晚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逾苫,到底是詐尸還是另有隱情便脊,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布鹅髓,位于F島的核電站陷寝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏星掰。R本人自食惡果不足惜多望,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氢烘。 院中可真熱鬧怀偷,春花似錦、人聲如沸播玖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜀踏。三九已至维蒙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間果覆,已是汗流浹背颅痊。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留局待,地道東北人八千。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像燎猛,于是被迫代替她去往敵國(guó)和親恋捆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容