關(guān)系講解:Sequence Point時序點(diǎn)域那、副作用、完整表達(dá)式及運(yùn)算優(yōu)先級

出現(xiàn)這篇文章的初衷猜煮,是因?yàn)閷虒W(xué)內(nèi)容的不解次员,在C Primer中也沒有找到很好的解答,遂將自己找到的資料在這里做一個匯總王带,也聊表心意的分享給大家淑蔚,覺得前面贅述的請從分割線處開始閱讀。

從一個問題開始

對于下面這個程序的運(yùn)行結(jié)果是什么愕撰?
int x = 1,y;
y = x++ + x++;
相信對于部分C語言初學(xué)者會給出y=2或者y=3這些不同的結(jié)果刹衫,那么問題就產(chǎn)生了到底此時的y的值到底是什么呢醋寝?
或許你還會疑惑:

  • ++的優(yōu)先級不是比=的優(yōu)先級更高嗎?先算++為什么又要在后面自增呢带迟?那這樣優(yōu)先級的意義又在哪里呢音羞?
  • 對于這種式子結(jié)果到底是什么?
  • 如果你嘗試用不同編譯器去編譯仓犬,你又會發(fā)現(xiàn)結(jié)果有所不同嗅绰?這又是為什么呢?

那么首先我將拋開問題從基本的幾個概念講起

這部分的內(nèi)容參考了 Eternity的文章內(nèi)容搀继,如果對于我的講解有疑惑也可以看看這篇文章窘面。

主要要講解的有:時序點(diǎn)(Sequence Point)、完整表達(dá)式叽躯、副作用财边、以及其與運(yùn)算優(yōu)先級的關(guān)系


  • 第一點(diǎn)時序點(diǎn)(Sequence Point)副作用與完整表達(dá)式

    時序點(diǎn)或者說序列點(diǎn)(整篇文章選擇時序點(diǎn)這個翻譯來講解)在C Primer plus書中是這樣描述的(對于在本書中的全部講解,我將放在文末):

    A sequence point is a point in program execution at which all side effects are evaluated before going on to the next step. In C, the semicolon in a statement marks a sequence point. That means all changes made by assignment operators, increment operators, and decrement operators in a statement must take place before a program proceeds to the next statement. Some operators that we’ll discuss in later chapters have sequence points. Also, the end of any full expression is a sequence point.

    譯文:
    序列點(diǎn)(sequence point)是程序執(zhí)行的點(diǎn)点骑,在該點(diǎn)上酣难,所有的副作用都在進(jìn)入下一步之前發(fā)生。在 C語言中畔况,語句中的分號標(biāo)記了一個序列點(diǎn)鲸鹦。意思是,在一個語句中跷跪,賦值運(yùn)算符馋嗜、遞增運(yùn)算符和遞減運(yùn)算符對運(yùn)算對象做的改變必須在程序執(zhí)行下一條語句之前完成。后面我們要討論的一些運(yùn)算符也有序列點(diǎn)吵瞻。另外葛菇,任何一個完整表達(dá)式的結(jié)束也是一個序列點(diǎn)。

    關(guān)于副作用橡羞、完整表達(dá)式會在稍后講到
    書中對于時序點(diǎn)的闡述并不是十分明確眯停,在Eternity的文章當(dāng)中這樣講解道:
    首先為什么Sequence Point要叫做Sequence point
    叫Sequence Point僅僅是因?yàn)樗雌饋韼洑鈫幔窟€是會讓人有“學(xué)術(shù)感”呢卿泽?當(dāng)然不是莺债。

    Sequence的解釋是:

    n. [數(shù)][計(jì)] 序列;順序签夭;續(xù)發(fā)事件
    vt. 按順序排好

    那么非Sequence的意思是什么呢齐邦?
    是Parallel

    n. 平行線;對比
    vt. 使…與…平行
    adj. 平行的第租;類似的措拇,相同的

    所以Sequence最恰當(dāng)?shù)姆g應(yīng)該是被稱作循序點(diǎn)

    對于副作用(side-effect)這個名詞,也將引用Primer Plus里面的介紹作為引入

    Now for a little more C terminology: A side effect is the modification of a data object or file. For instance, the side effect of the statement
    states = 50;
    is to set the states variable to 50. Side effect? This looks more like the main intent! From the standpoint of C, however, the main intent is evaluating expressions. Show C the expression 4 + 6, and C evaluates it to 10. Show it the expression states = 50, and C evaluates it to 50. Evaluating that expression has the side effect of changing the states variable to 50. The Expressions and Statements
    increment and decrement operators, like the assignment operator, have side effects and are used primarily because of their side effects.
    Similarly, when you call the printf() function, the fact that it displays information is a side effect. (The value of printf(), recall, is the number of items displayed.)

    譯文:
    我們再討論一個C語言的術(shù)語副作用(side effect)慎宾。副作用是對數(shù)據(jù)對象或文件的修改丐吓。例如浅悉,語句:
    states = 50;
    它的副作用是將變量的值設(shè)置為50。副作用券犁?這似乎更像是主要目的术健!但是從C語言的角度看,主要目的是對表達(dá)式求值粘衬。給出表達(dá)式4 + 6苛坚,C會對其求值得10;給出表達(dá)式states = 50色难,C會對其求值得50。對該表達(dá)式求值的副作用是把變量states的值改為50等缀。跟賦值運(yùn)算符一樣枷莉,遞增和遞減運(yùn)算符也有副作用,使用它們的主要目的就是使用其副作用尺迂。
    類似地笤妙,調(diào)用 printf()函數(shù)時,它顯示的信息其實(shí)是副作用(printf()的返回值是待顯示字符的個數(shù))噪裕。

    “ i++”的副作用就是它的值會“偷偷的”+1蹲盘,跟“=”這種馬上+1的副作用不同
    換言之它是在background(背后)被+1的,所以我們可以做這樣一種想象在程序執(zhí)行到“i++”的時候編譯器開辟了一條通道去把i的值更新成了“i+1”膳音。
    這就是剛才所提到的非SequenceParallel召衔。
    既然是非線性即平行的執(zhí)行的東西總需要一個匯合點(diǎn)進(jìn)行匯合,這個交匯的地方就是所謂的Sequence Point祭陷,這就是平行模式回到循環(huán)執(zhí)行模式的分界點(diǎn)苍凛。
    在這里放一個不太貼切的示意圖

    Sequence Point and Side-effect.png

從這里可以得出一個結(jié)論:所有的副作用都必須在時序點(diǎn)之前完成。(注意這里之前的意思并不是說剛好在這個時序點(diǎn)之前或者說到達(dá)時序點(diǎn)所有的副作用才開始全部一起產(chǎn)生)

前面看了在C Primer Plus里面對于副作用的解釋兵志,在這里我們再次介紹一下Side-effect
基本上只要是會對變量做改變的都算作Side-effect即副作用醇蝴。譬如a=b這種會改變a的狀態(tài)的行為就是“=”等號這個運(yùn)算符的副作用,“i++”的副作用就是會把i的值做“+1”想罕。同樣的如果一個函數(shù)foo(i)會改變i悠栓,那么這個改變行為就是foo()這個函數(shù)式子的副作用,當(dāng)然side-effect包括的不僅僅是對變量數(shù)值的改變按价。
在C語言執(zhí)行標(biāo)準(zhǔn)(版本未知)中有如下定義:

Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression may produce side effects. At certain specified points in the execution sequence called sequence points, all side effects of previous evaluations shall be complete and no side effects of subsequent evaluations shall have taken place.

譯文:
訪問易變對象惭适,修改對象或文件,或者調(diào)用包含這些操作的函數(shù)都是副作用俘枫,它們都會改變執(zhí)行環(huán)境的狀態(tài)腥沽。計(jì)算表達(dá)式也會引起副作用。執(zhí)行序列中某些特定的點(diǎn)被稱為序列點(diǎn)鸠蚪。在序列點(diǎn)上今阳,該點(diǎn)之前所有運(yùn)算的副作用都應(yīng)該結(jié)束师溅,并且后繼運(yùn)算的副作用還沒發(fā)生。

對于Side-effect的具體講解會在后面的補(bǔ)充文章當(dāng)中講到盾舌,這里僅僅講解對變量改變這一類型墓臭,也是為了能夠簡單的闡述清楚文章的主要內(nèi)容——對于概念關(guān)系的理解。

那么講到平行的程序最常見的BUG又是什么呢妖谴?當(dāng)然是出現(xiàn)最開始討論的問題的情況——出現(xiàn)了競爭窿锉。
回到平行程序設(shè)計(jì)的角度來看,開辟了一條“通道”去把一個變數(shù)+1膝舅,隨后又遇到另一個函數(shù)在Sequence Point時序點(diǎn)之前嗡载,開辟了另一條通道去把這個變量+1,就像前面的示意圖一樣仍稀,那么最終的結(jié)果會是什么呢洼滚?你可能會認(rèn)為結(jié)果就是加兩次的結(jié)果但是,在C語言中這個答案真的不是這樣的技潘,或者說天曉得會有什么樣的答案遥巴。

注:對于這里的答案你或許會有疑惑,筆者也同樣對此抱有不解的態(tài)度享幽,所以在后期會對這里的結(jié)果作出解釋铲掐。

所以從這個例子我們尚且可以歸納出一條程序的撰寫準(zhǔn)則:不能在時序點(diǎn)前改變一個變量的數(shù)值兩次

接下來看一個與文章開頭比較類似的例子:
i++ + ++i;
首先我先告訴你在C語言標(biāo)準(zhǔn)當(dāng)中“+”不是一個時序點(diǎn) 并且“;”分號是一個時序點(diǎn)標(biāo)志(具體對于時序點(diǎn)的講解也在后面一點(diǎn)會講到)值桩。因?yàn)椤?”不是一個時序點(diǎn)所以這個程序會開辟出三個通道摆霉。(具體哪幾個通道?颠毙?待更新)
所以對于這個程序最后的結(jié)果會是多少也是不確定的斯入。
或許此處也會有一個疑問都是開辟一個自增的變臉為什么i++會在之后自增,而++i則不會呢(文章更新后補(bǔ)充)

接下來可能會有人問:那i=i++呢蛀蜜?
首先明確一點(diǎn)就是:“=”等號不是時序點(diǎn)刻两。其次以平行程序的角度來看前面i++ + ++i有三條通道,i=i++其實(shí)有兩條通道滴某,“=”等號在主通道里對i的值更新磅摹,而++則是在另一條通道里面對i的值做更新,所以當(dāng)然是不行的霎奢,有兩條通道對i的值做更新户誓。

那么哪些是時序點(diǎn)呢?在C99標(biāo)準(zhǔn)中的Annex C確實(shí)有明確的整理出來:

The following are the sequence points described in 5.1.2.3:
——The call to a function, after the arguments have been evaluated (6.5.2.2).
——The end of the first operand of the following operators: logical AND && (6.5.13); logical OR || (6.5.14); conditional ? (6.5.15); comma , (6.5.17).
——The end of a full declarator: declarators (6.7.5);
The end of a full expression: an initializer (6.7.8); the expression in an expression statement (6.8.3); the controlling expression of a selection statement (if or switch) (6.8.4); the controlling expression of a while or do statement (6.8.5); each of the expressions of a for statement (6.8.5.3); the expression in a return statement (6.8.6.4).
——Immediately before a library function returns (7.1.4).
——After the actions associated with each formatted input/output function conversion specifier (7.19.6, 7.24.2).
——Immediately before and immediately after each call to a comparison function, and also between any call to a comparison function and any movement of the objects passed as arguments to that call (7.20.5).

第一點(diǎn)說的是幕侠,在foo(i++)在控制程序真正跳進(jìn)foo( )內(nèi)部之前帝美,i++的Side-effect必須完成。
特別需要注意個是晤硕,并不意味著傳進(jìn)去foo( )的會是“i+1”的的結(jié)果悼潭。
要知道傳進(jìn)去的是i++這條運(yùn)算式的運(yùn)算結(jié)果而不是庇忌,受附加效果影響后的值,因此傳進(jìn)去的還是i的原值舰褪。

現(xiàn)在看一個更具體的例子:首先假設(shè)i的初始值為1皆疹,那么寫foo(i++,i++,i++)會發(fā)生什么事呢?
首先說明foo( )里面的“占拍,”逗號只是用于間隔函數(shù)參數(shù)略就,并不是上面第二條當(dāng)中所說的“comma”“,”逗號運(yùn)算符晃酒。所以第二條規(guī)則在此處是不適用的表牢。
根據(jù)上一段,很容易得出結(jié)果是foo(1,1,1)贝次,但是在編譯器中可能會得到foo(3,2,1)初茶。
這里肯定會有人質(zhì)疑說,不是前面講過“i++”運(yùn)算傳入foo( )中的是函數(shù)的原始值浊闪?這的確是沒錯的但是請注意一點(diǎn):
正如前面所說所有的副作用(Side-effect)必須在時序點(diǎn)之前完成這句話,并不代表著剛好在時序點(diǎn)(Sequence Point)之前才完成螺戳。

副作用(Side-efffect)完成時有一個時間范圍的搁宾,也就是說從副作用開始到時序點(diǎn)結(jié)束這段時間內(nèi),都有可能完成這個副作用倔幼。
所以從概念上來說盖腿,上一個程序其實(shí)開辟了三條通道去更新i的值,而交匯點(diǎn)是在所有的函數(shù)參數(shù)全部求值完后损同,到進(jìn)入foo( )中的這一瞬間翩腐。此外標(biāo)準(zhǔn)并沒有規(guī)定函數(shù)參數(shù)的求值順序,所有哪條通道先開啟是個未知數(shù)膏燃。所以foo(3,2,1)只是剛好編譯器倒著順序求值茂卦,而更新i的時序點(diǎn)剛好落在進(jìn)入foo( )之前了而已。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末组哩,一起剝皮案震驚了整個濱河市等龙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伶贰,老刑警劉巖蛛砰,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異黍衙,居然都是意外死亡泥畅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門琅翻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來位仁,“玉大人柑贞,你說我怎么就攤上這事≌习” “怎么了凌外?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涛浙。 經(jīng)常有香客問我康辑,道長,這世上最難降的妖魔是什么轿亮? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任疮薇,我火速辦了婚禮,結(jié)果婚禮上我注,老公的妹妹穿的比我還像新娘按咒。我一直安慰自己,他們只是感情好但骨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布励七。 她就那樣靜靜地躺著,像睡著了一般奔缠。 火紅的嫁衣襯著肌膚如雪掠抬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天校哎,我揣著相機(jī)與錄音两波,去河邊找鬼。 笑死闷哆,一個胖子當(dāng)著我的面吹牛腰奋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抱怔,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼劣坊,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了屈留?” 一聲冷哼從身側(cè)響起讼稚,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绕沈,沒想到半個月后锐想,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乍狐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年赠摇,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡藕帜,死狀恐怖烫罩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洽故,我是刑警寧澤贝攒,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站时甚,受9級特大地震影響隘弊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜荒适,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一梨熙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刀诬,春花似錦咽扇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至糠馆,卻和暖如春把敞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背榨惠。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盛霎,地道東北人赠橙。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像愤炸,于是被迫代替她去往敵國和親期揪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

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

  • One 我不知道該怎么形容自己的生活座泳。 每天早上6點(diǎn)準(zhǔn)時起床,洗刷完畢后缤苫,圍上厚厚的圍巾,穿上羽絨大衣墅拭,帶上口罩走...
    墨離c閱讀 258評論 0 0
  • 零點(diǎn)了活玲,距舉行婚禮的8月1號還有整整一個星期的時間,已經(jīng)十三年了,從十二歲開始呢舒憾。蘇洋躺在床上迷迷糊糊地想镀钓,又拿起...
    小師妹姽婳閱讀 657評論 0 3