看板 C_and_CPP 關於我們 聯絡資訊
※ 引述《BitTorrent (螳勃唬)》之銘言: : 請問一下 : strcpy 中 : while( *s++ = *t++); : 可以copy char arrays : 想請問一下可是++ 優先權 不是大於* : 所以照理講第一個不會被複製到阿?
LiloHuang: 優先權是指誰先做誰後做,但是該做的事情還是都會執行 06/28 12:58
這裡其實我覺得優先權不是指誰先誰後,而是指一個運算式該怎麼被我們解讀 (後面修文補完文章內容) -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 36.225.46.73 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1435482334.A.2D2.html
suhorng: 不小心按錯發文了QQ 還沒寫完, 可以麻煩板主幫刪嗎.. 06/28 17:06
ssdoz2sk: 在文章標題上按大寫E可以修改內文 標題可以用大寫T 06/28 18:08
LiloHuang: 我意指像是 +-*/ 運算子,我們會知道先乘除後加減 06/28 19:14
LiloHuang: 對於運算子的優先權解讀,就是會有誰先做誰後做的順序 06/28 19:15
LiloHuang: 我猜測原po認為優先權較大的情況,可能某運算子被忽略 06/28 19:16
LiloHuang: 在 flat expression 裡 Precedence 用以定義執行順序 06/28 19:27
LiloHuang: 就像 https://goo.gl/2O5Wq9 開頭提及的部分,不過我想 06/28 19:44
LiloHuang: 應該有更加嚴謹的定義吧 06/28 19:45
LPH66: 你想要的「更加嚴謹的定義」很多人都回了, 就是如何解讀 06/28 20:11
LPH66: 只是在沒有副作用的時候它就是運算順序而已 06/28 20:12
LPH66: 這裡因為 ++ 是有副作用的運算子所以才會有這種差別 06/28 20:12
suhorng: 今天有事沒法回文了...明天再來修完 既然有推文了文章還 06/28 20:14
suhorng: 是留著好了XD 06/28 20:15
LiloHuang: postfix ++ operator 有 side effect 這我知道 XD 06/28 20:34
LiloHuang: 我上一篇的推文,是假設原 po 已知 side effect 的情況 06/28 20:35
LiloHuang: 提及運算子優先權,決定了編譯後生成的機器碼順序 06/28 20:36
LiloHuang: 畢竟運算子優先權的定義,就是決定了誰該先做誰該後做 06/28 20:38
LiloHuang: 事後想想,side effect 的部分才是最初原 po 要的答案 06/28 20:39
LiloHuang: 但是我的疑惑是,暫時撇開 postfix ++ 的 side effect 06/28 20:46
LiloHuang: 為什麼運算子優先權,suhorng 認為不是先後執行順序 06/28 20:47
suhorng: 有三個運算子在 "優先權 => 運算先後" 的命題下會變例外 06/28 21:20
suhorng: &&, ||, ? : 06/28 21:20
suhorng: 另外 sizeof 應該也可以算是一個 operator @@ 06/28 21:22
LiloHuang: 謝謝,這樣我懂你的意思了,的確用 "如何解讀" 較恰當 06/28 21:29
LiloHuang: sizeof 的確也是一個 operator 沒錯 XD 06/28 21:29
講更詳細一點, 我喜歡的 approach (尤其適用於程式語言) 對於一個算式 我們心理想的其實是一棵樹(Abstract Syntax Tree, AST), 為了寫作方便, 寫成平的算式, 並用括弧和 operator precedence, grammar 等等 來建立一個平的字串跟這棵樹之間的關係. + λ / \ / \ 1 * x @ / \ / \ 2 3 x x 1 + 2 * 3 λx. x x 而對於這棵樹有什麼 "意義", 是由我們定義決定的. 最簡單的方式是定義一個解釋函數 value_t eval(tree_t expr) { if (expr == "+") { return intval( eval(expr.left).intval() + eval(expr.right).intval() ); } else if (expression == "*") { ... } } 而當然 eval 函數會適當的決定求值順序以及好好的解釋副作用等等, 例如 ... if (expr == "?:") { val ret = eval(expr.first); if (res.boolval() == true) return eval(expr.second); else return eval(expr.third); } else if (expr == "&&") { val ret = expr(expr.left); if (ret.boolval() == true) return eval(expr.right); else return false; } ... 等等. 我們去看一般語言的定義時, 通常語意都是遞歸的定義在這棵語法樹上, 很少有直接對平平的算式做討論. 當然, 考慮普通的四則運算加減乘除, 由於 +-*/ 這幾個運算子都是 strict, 要說這導致了 +-*/ 的優先權決定了運算順序, 當然不能說是錯的. 但這畢竟是難以一般化的特例. 舉例來說, 函數呼叫其實也 可以是個 operator (我不是說 operator() ), 但函數呼叫要不要把參數先算完 是可以有不同的選擇. 回到 C++. 目前的 C++ 描述的方式是設計 value computation 跟 side effect 兩種. value computation 描述一個值的計算, 而 side effect, 副作用, 一般來說 就是做一些其他的事情像變數修改, 做 I/O 等等. 對於運算子及函數呼叫, C++ 規定了一套該運算子本身的運算和它的參數們之間 value computation, side effect 的偏序關係. 有受到偏序關係規範的我們能說 他們之間誰會先做誰會後做, 未受偏序關係規範的則還有分有可能交替著做, 或者 只能是誰先誰後(不能重疊)等等. 最簡單的例子來說, + 就規定其左右運算元的 value computation 要在 + 的 value computation 之前作完, 不過對於 side effects 則沒有規範. 當兩個 未受偏序關係規制的 side effects 影響到同一個物件時, 就是未定義行為. 回到最一開始的例子, 一個算式如 f1(g()) + f2(h(z(), w())) * f3() 當中, 其實我們副作用發生的順序根本是很寬鬆的定義的. 我們只知道 z,w 的副作用 發生在 h 之前, h 的副作用發生在 f2 之前, g 的副作用發生在 f1 之前, 其餘副作用是有可能任意穿插發生的. 在這種例子中我們很難說優先權就決定了 運算順序. 在副作用之外, 甚至在運算式 e1 + e2 + e3 當中, 三個運算元的 計算順序根本沒有確定規則. 這個算式依據優先權及結合性被解讀為 + / \ + e3 / \ e1 e2 所以我們知道 e3 跟 (e1+e2) 兩者的 value computation 要在最上面的 + 之前算完, 以及 e1 和 e2 的 value computation 要在 e1+e2 的 + 之前算完. 但是 e1, e2, e3 誰先算呢? 不知道. 此外我推文提到的 && || ?: 就是想要反駁說, expr1 && expr2 當中 && 的優先權 即使比 expr1, expr2 算式中的低, 但是 expr2 甚至根本不一定會計算到. 假使 expr1 為 false, (&&) 就計算為 false 了, 那計算順序不就比 expr2 更前面了? 同樣的 expr1 ? expr2 : expr3 這樣的 conditional, expr2 跟 expr3 也一定至 少有一個不會被算, 但 ? : 的結果還是會比不會算的那個先算出來. ※ 編輯: suhorng (36.225.42.192), 07/01/2015 01:21:08
LPH66: 另一個語法樹結構跟運算順序是兩回事的例子 #1Deo3lyi 07/01 01:35
LiloHuang: 我同意是因為 && 跟 || 有 short circuit evaluation 07/01 22:04
LiloHuang: 我一開始的說法並沒有考量到這些運算子,不夠嚴謹了 XD 07/01 22:04
LiloHuang: 但這也隱含著特定的判斷順序,機器碼仍是有規則的執行 07/01 22:09
因為語言是定義在 AST 上面的, 我會覺得, 既然 precedence 那些影響 parsing, parsing 出來不同的 AST 又由定義有不同的結果, 當然要說 precedence 在 C++ 中 決定部份東西的先後順序是沒有問題... 不過真的只是部份順序喔. 像我舉的 e1 + e2 + e3 例子即使改成 e1 + e2 * e3, 仍然沒有說 e1, e2, e3 的 side-effects 甚至 value computation 誰先做誰後做. 我們是不知道的. 倒是有興趣的話, Haskell 是 non-strict 的, 通常都用 lazy evaluation 實現, 特點是一個東西真的被需要時才會被求值~ 例如 fib = 1 : 1 : zipWith (+) fib (tail fib) 當我們在 main 裡面 print (take 5 fib) 的時候, 會一個一個從頭開始去找 fib 的內容, 用到的才會去展開算出來, 這裡執行順序就更多的像是執行時才決定了. ※ 編輯: suhorng (36.225.42.192), 07/01/2015 23:01:08 ※ 編輯: suhorng (36.225.42.192), 07/01/2015 23:01:43
LPH66: Lilo 你可以去看我貼的那篇, 那篇跟 && || 都無關 07/01 23:49
LPH66: 純粹是這個的 side-effect 跟那個的 value computation 間 07/01 23:49
LPH66: 誰先誰後的問題 07/01 23:49
LPH66: 而就是這個誰先誰後的不明確導致了那種寫法在不同編譯器 07/01 23:50
LPH66: 之間會得到不同的結果 07/01 23:50
LiloHuang: 我認同你的說法,你那篇的例子舉的相當恰當,謝啦 :) 07/02 00:03
LiloHuang: 某些有 side effect 情況下,順序的確跟編譯器實作相關 07/02 00:06
LiloHuang: 也許我太過在意的是 codegen 後的機器碼都有特定順序 07/02 00:14
suhorng: 偷渡 Haskell 失敗QQ (X 07/02 00:17
LiloHuang: 對 Haskell 不懂,用過 boost::phoenix 也能 lazy 求值 07/02 00:48
LiloHuang: 我知道你要講的意思啦 :) 07/02 00:48