真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C語言未定義行為分析

這篇文章主要介紹“C語言未定義行為分析”,在日常操作中,相信很多人在C語言未定義行為分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”C語言未定義行為分析”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

創(chuàng)新互聯(lián)公司網(wǎng)站建設(shè)由有經(jīng)驗(yàn)的網(wǎng)站設(shè)計(jì)師、開發(fā)人員和項(xiàng)目經(jīng)理組成的專業(yè)建站團(tuán)隊(duì),負(fù)責(zé)網(wǎng)站視覺設(shè)計(jì)、用戶體驗(yàn)優(yōu)化、交互設(shè)計(jì)和前端開發(fā)等方面的工作,以確保網(wǎng)站外觀精美、網(wǎng)站制作、網(wǎng)站設(shè)計(jì)易于使用并且具有良好的響應(yīng)性。

他在白板上寫了幾行代碼,并問這個(gè)程序會(huì)輸出什么?

#include    int main(){     int i = 0;     int a[] = {10,20,30};       int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];     printf("%d\n", r);     return 0; }

看上去相當(dāng)簡單明了。我解釋了操作符的優(yōu)先順序——后綴操作比乘法先計(jì)算、乘法比加法先計(jì)算,并且乘法和加法的結(jié)合性都是從左到右,于是我抓出運(yùn)算符號并開始寫出算式。

int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++]; //    =    a[0]    + 2 * a[1]  + 3 * a[2]; //    =     10     +     40    +    90; //    = 140

我自鳴得意地寫下答案后,我的同事回應(yīng)了一個(gè)簡單的“不”。我想了幾分鐘后,還是被難住了。我不太記得后綴操作符的結(jié)合順序了。此外,我知道那個(gè)順 序甚至  不會(huì)改變這里的值計(jì)算的順序,因?yàn)榻Y(jié)合規(guī)則只會(huì)應(yīng)用于同級的操作符之間。但我想到了應(yīng)該根據(jù)后綴操作符都從右到左求值的規(guī)則,嘗試算一遍這條算式。看上去  相當(dāng)簡單明了。

int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++]; //    =    a[2]    + 2 * a[1]  + 3 * a[0]; //    =     30     +     40    +    30; //    = 100

我的同事再一次回答說,答案仍是錯(cuò)的。這時(shí)候我只好認(rèn)輸了,問他答案是什么。這段短小的樣例代碼原來是從他寫過的更大的代碼段里刪減出來的。為了驗(yàn)   證他的問題,他編譯并且運(yùn)行了那個(gè)更大的代碼樣例,但是驚奇地發(fā)現(xiàn)那段代碼沒有按照他預(yù)想的運(yùn)行。他刪減了不需要的步驟后得到了上面的樣例代碼,用gcc   4.7.3編譯了這段樣例代碼,結(jié)果輸出了令人吃驚的結(jié)果:“60”。

這時(shí)我被迷住了。我記得,C語言里,函數(shù)參數(shù)的計(jì)算求值順序是未定義的,所以我們以為后綴操作符只是遵照某個(gè)隨機(jī)的、而非從左至右的順序,計(jì)算的。   我們?nèi)匀淮_信后綴比加法和乘法擁有更高的操作優(yōu)先級,所以很快證明我們自己,不存在我們可以計(jì)算i++的順序,使得這三個(gè)數(shù)組元素一起加起來、乘起來得到  60。

現(xiàn)在我已對此入迷了。我的***個(gè)想法是,查看這段代碼的反匯編代碼,然后嘗試查出它實(shí)際上發(fā)生了什么。我用調(diào)試符號(debugging symbols)編譯了這段樣例代碼,用了objdump后很快得到了帶注釋的x86_64反匯編代碼。

Disassembly of section .text:   0000000000000000 
: #include    int main(){    0:   55                      push   %rbp    1:   48 89 e5                mov    %rsp,%rbp    4:   48 83 ec 20             sub    $0x20,%rsp     int i = 0;    8:   c7 45 e8 00 00 00 00    movl   $0x0,-0x18(%rbp)     int a[] = {10,20,30};    f:   c7 45 f0 0a 00 00 00    movl   $0xa,-0x10(%rbp)   16:   c7 45 f4 14 00 00 00    movl   $0x14,-0xc(%rbp)   1d:   c7 45 f8 1e 00 00 00    movl   $0x1e,-0x8(%rbp)     int r = 1 * a[i++] + 2 * a[i++] + 3 * a[i++];   24:   8b 45 e8                mov    -0x18(%rbp),%eax   27:   48 98                   cltq    29:   8b 54 85 f0             mov    -0x10(%rbp,%rax,4),%edx   2d:   8b 45 e8                mov    -0x18(%rbp),%eax   30:   48 98                   cltq    32:   8b 44 85 f0             mov    -0x10(%rbp,%rax,4),%eax   36:   01 c0                   add    %eax,%eax   38:   8d 0c 02                lea    (%rdx,%rax,1),%ecx   3b:   8b 45 e8                mov    -0x18(%rbp),%eax   3e:   48 98                   cltq    40:   8b 54 85 f0             mov    -0x10(%rbp,%rax,4),%edx   44:   89 d0                   mov    %edx,%eax   46:   01 c0                   add    %eax,%eax   48:   01 d0                   add    %edx,%eax   4a:   01 c8                   add    %ecx,%eax   4c:   89 45 ec                mov    %eax,-0x14(%rbp)   4f:   83 45 e8 01             addl   $0x1,-0x18(%rbp)   53:   83 45 e8 01             addl   $0x1,-0x18(%rbp)   57:   83 45 e8 01             addl   $0x1,-0x18(%rbp)     printf("%d\n", r);   5b:   8b 45 ec                mov    -0x14(%rbp),%eax   5e:   89 c6                   mov    %eax,%esi   60:   bf 00 00 00 00          mov    $0x0,%edi   65:   b8 00 00 00 00          mov    $0x0,%eax   6a:   e8 00 00 00 00          callq  6f      return 0;   6f:   b8 00 00 00 00          mov    $0x0,%eax }   74:   c9                      leaveq   75:   c3                      retq

***和***的幾個(gè)指令只建立了堆棧結(jié)構(gòu),初始化變量的值,調(diào)用printf函數(shù),還從main函數(shù)返回。所以我們實(shí)際上只需要關(guān)心從0×24到0×57之間的指令。那是令人關(guān)注的行為發(fā)生的地方。讓我們每次查看幾個(gè)指令。

24:   8b 45 e8                mov    -0x18(%rbp),%eax 27:   48 98                   cltq  29:   8b 54 85 f0             mov    -0x10(%rbp,%rax,4),%edx

***的三個(gè)指令與我們預(yù)期的一致。首先,它把i(0)的值加載到eax寄存器,帶符號擴(kuò)展到64位,然后加載a[0]到edx寄存器。這里的乘以1的運(yùn)算(1*)顯然被編譯器優(yōu)化后去除了,但是一切看起來都正常。接下來的幾個(gè)指令開始時(shí)也大致相同。

2d:   8b 45 e8                mov    -0x18(%rbp),%eax 30:   48 98                   cltq  32:   8b 44 85 f0             mov    -0x10(%rbp,%rax,4),%eax 36:   01 c0                   add    %eax,%eax 38:   8d 0c 02                lea    (%rdx,%rax,1),%ecx

***個(gè)mov指令把i的值(仍然是0)加載進(jìn)eax寄存器,帶符號擴(kuò)展到64位,然后加載a[0]進(jìn)eax寄存器。有意思的事情發(fā)生了——我們再次 期待  i++在這三條指令之前已經(jīng)運(yùn)行過了,但也許***兩條指令會(huì)用某種匯編的魔法來得到預(yù)期的結(jié)果(2*a[1])。這兩條指令把eax寄存器的值自加了一  次,實(shí)際上執(zhí)行了2*a[0]的操作,然后把結(jié)果加到前面的計(jì)算結(jié)果上,并存進(jìn)ecx寄存器。此時(shí)指令已經(jīng)求得了a[0] + 2 *   a[0]的值。事情開始看起來有一些奇怪了,然而再一次,也許某個(gè)編譯器魔法在發(fā)生。

3b:   8b 45 e8                mov    -0x18(%rbp),%eax 3e:   48 98                   cltq  40:   8b 54 85 f0             mov    -0x10(%rbp,%rax,4),%edx 44:   89 d0                   mov    %edx,%eax

接下來這些指令開始看上去相當(dāng)熟悉。他們加載i的值(仍然是0),帶符號擴(kuò)展至64位,加載a[0]到edx寄存器,然后拷貝edx里的值到eax。嗯,好吧,讓我們在多看一些:

46:   01 c0                   add    %eax,%eax 48:   01 d0                   add    %edx,%eax 4a:   01 c8                   add    %ecx,%eax 4c:   89 45 ec                mov    %eax,-0x14(%rbp)

在這里把a(bǔ)[0]自加了3次,再加上之前的計(jì)算結(jié)果,然后存入到變量“r”?,F(xiàn)在不可思議的事情——我們的變量r現(xiàn)在包含了a[0] + 2 *   a[0] + 3 * a[0]。足夠肯定的是,那就是程序的輸出:“60”。但是那些后綴操作符上發(fā)生了什么?他們都在***:

4f:   83 45 e8 01             addl   $0x1,-0x18(%rbp) 53:   83 45 e8 01             addl   $0x1,-0x18(%rbp) 57:   83 45 e8 01             addl   $0x1,-0x18(%rbp)

看上去我們編譯版本的代碼完全錯(cuò)了!為什么后綴操作符被扔到***下、所有任務(wù)已經(jīng)完成之后?隨著我對現(xiàn)實(shí)的信仰減少,我決定直接去找本源。不,不是編譯器的源代碼——那只是實(shí)現(xiàn)——我抓起了C11語言規(guī)范。

這個(gè)問題處在后綴操作符的細(xì)節(jié)。在我們的案例中,我們在單個(gè)表達(dá)式里對數(shù)組下標(biāo)執(zhí)行了三次后綴自增。當(dāng)計(jì)算后綴操作符時(shí),它返回變量的初始值。把新   的值再分配回變量是一個(gè)副作用。結(jié)果是,那個(gè)副作用只被定義為只被付諸于各順序點(diǎn)之間。參照標(biāo)準(zhǔn)的5.1.2.3章節(jié),那里定義了順序點(diǎn)的細(xì)節(jié)。但在我們  的例子中,我們的表達(dá)式展示了未定義行為。它完全取決于編譯器對于 什么時(shí)候 給變量分配新值的副作用會(huì)執(zhí)行 相對于表達(dá)式的其他部分。

到此,關(guān)于“C語言未定義行為分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!


分享題目:C語言未定義行為分析
新聞來源:http://weahome.cn/article/gdsedd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部