| 蟑 螂 是 生 命 力 很 強 的 動 物 , 大 概 有 人 的 地 方 就 有 它 ,
雖 然 人 類 發 明 了 許 多 殺 蟑 的 方 法 , 但 蟑 螂 還 是 沒 有 因 此
絕 跡 ; 電 腦 的 bug 很 像 蟑 螂 , 它 總 是 悄 悄 地 躲 進 我 們 所 開
發 的 程 式 中 , 雖 然 大 部 份 的 軟 體 開 發 工 具 都 有 殺 蟲 劑 (debugger),
但 bug 也 沒 有 因 此 絕 跡 。
我 有 一 位 很 強 壯 的 同 事 卻 很 怕 蟑 螂 , 只 要 看 到 蟑 螂 , 他 一 定 要 翻 箱 倒 櫃 把 它 找 出 來 , 否 則 便 無 法 入 睡 , 這 讓 我 聯 想 到 是 否 有 人 為 了 一 個 bug 而 翻 遍 所 有 的 程 式 ? 對 於 小 程 式 來 說 , 這 也 許 不 是 太 難 的 事 情 , 但 對 於 大 型 程 式 , 這 卻 是 不 實 際 的 , 就 像 您 要 在 幾 十 層 的 大 樓 中 搜 索 一 隻 蟑 螂 一 樣 。 我 想 應 該 先 從 蟑 螂 最 常 出 沒 的 地 方 開 始 , 甚 至 在 這 些 地 方 擺 放 殺 蟑 劑 , 同 樣 的 , 在 程 式 中 也 有 bug 比 較 容 易 躲 藏 的 地 方 , 我 們 首 先 要 確 定 bug 是 否 會 躲 藏 在 這 些 地 方 , 並 且 埋 設 一 些 檢 查 的 關 卡 , 只 要 bug 出 現 就 能 顯 現 警 告 訊 息 。 蟑 螂 的 多 寡 與 家 具 的 選 擇 有 極 大 的 關 係 , 同 樣 的 , bug 的 多 寡 與 開 發 程 式 的 語 言 也 有 極 大 的 關 係 , 而 在 眾 多 流 通 廣 泛 的 程 式 語 言 中 , 又 以 C 語 言 為 bug 的 最 愛 , 本 期 探 討 一 般 程 式 語 言 容 易 潛 藏 bug 的 地 方 , 或 多 或 少 會 以 C 語 言 為 例 , 不 過 較 完 整 的 整 理 及 討 論 則 留 待 以 後 。 |
如 果 客 戶 的 利 息 算 錯 , 如 果 飛 彈 的 彈 道 算 錯 , 如 果 … , 利 用 電 腦 來 代 替 人 類 作 運 算 , 原 本 在 取 其 既 迅 速 又 準 確 的 特 性 , 為 什 麼 還 會 有 錯 呢 ?
對 人 類 來 說 , 「 整 」 的 範 圍 是 從 -∞ 到 +∞ , 對 電 腦 程 式 語 言 來 說 , 「 數 」 卻 有 固 定 的 範 圍 , 因 此 只 要 含 有 + - × ÷ 等 運 算 符 號 ( operator) 的 運 算 式 , 其 運 算 結 果 都 可 能 超 過 電 腦 程 式 語 言 所 預 設 的 範 圍 。 舉 個 例 子 來 說 : 以 16 位 元 為 整 數 範 圍 ( -32768 到 32767) 的 運 算 中 , 當 a >= 128時 , 運 算 式 「 a * 256」 的 結 果 ( >=32768) 即 超 出 整 數 的 範 圍 , 這 就 是 所 謂 的 「 溢 位 」 ( overflow) 。
溢 位 的 預 防 別 無 他 法 , 只 有 檢 視 每 一 個 運 算 式 , 並 且 確 定 運 算 子 ( operand ) 的 可 能 值 不 會 使 運 算 式 造 成 溢 位 , 舉 例 來 說 , 您 可 以 在 a * 256 的 運 算 式 前 面 加 上 一 段 邏 輯 判 斷 , 例 如 :
但 對 於 比 較 複 雜 的 運 算 式 , 這 種 作 法 並 不 實 際 , 舉 例 來 說 , 我 們 很 難 寫 出 運 算 式 「 a*a + b*10 + c」 是 否 溢 位 的 判 斷 式 , 解 決 這 個 問 題 較 好 的 方 法 是 利 用 程 式 語 言 「 例 外 處 理 」 的 能 力 , 例 如 Visual Basic 就 有 「 On Error ...」 ( 當 發 生 錯 誤 時 , 就 … ) 的 語 法 , 以 下 是 一 個 實 例 :
不 過 並 不 是 所 有 的 程 式 語 言 都 具 有 「 例 外 處 理 」 的 機 制 , 以 C 語 言 為 例 , 發 生 溢 位 時 , 並 不 會 產 生 溢 位 的 錯 誤 , 只 是 簡 單 地 把 溢 位 的 部 份 去 掉 , 表 面 看 起 來 程 式 還 能 夠 正 常 地 執 行 , 但 實 際 上 執 行 的 結 果 卻 是 錯 誤 的 , 這 是 使 用 C 語 言 應 該 特 別 注 意 的 地 方 。
10 + 0.1 等 於 10.1, 這 麼 簡 單 的 算 數 小 學 生 都 會 , 以 下 是 以 Visual Basic 所 做 的 實 驗 :
結 果 列 印 出 來 的 值 自 11.3 以 後 就 不 完 全 正 確 了 :
這 並 非 Visual Basic 的 錯 誤 , 問 題 的 原 因 是 電 腦 以 有 限 的 位 數 來 表 達 無 限 的 數 值 範 圍 所 產 生 的 誤 差 , 雖 然 這 些 誤 差 通 常 都 小 得 可 以 忽 略 它 的 存 在 , 但 仍 有 幾 件 事 情 必 須 加 以 注 意 :
•當 我 們 需 要 更 精 確 的 浮 點 計 算 值 時 , 先 確 定 程 式 語 言 能 否 滿 足 需 求 。
• 同 樣 的 浮 點 運 算 式 在 不 同 機 器 、 或 不 同 的 程 式 語 言 , 可 能 因 為 其 表 達 浮 點 數 的 方 法 不 同 , 而 造 成 計 算 出 來 的 結 果 也 不 同 。
• 在 邏 輯 判 斷 式 中 , 儘 量 不 要 判 斷 兩 個 浮 點 數 是 否 相 等 , 例 如 以 下 的 迴 圈 極 可 能 無 法 結 束 :
其 原 因 是 x 雖 然 會 達 到 20.0 的 及 近 似 值 ( 以 Visual Basic 為 例 , 其 值 會 達 到 20.00004) , 但 終 究 不 等 於 20.0, 以 致 無 法 跳 出 迴 圈 。
錯 誤 類 型 之 三 : 除 以 0
當 然 沒 有 人 會 笨 到 寫 出 (x / 0) 這 樣 的 運 算 式 , 但 類 似 (x / y) 的 運 算 式 相 信 大 家 都 寫 過 , 因 此 遇 有 相 除 的 運 算 式 時 , 不 妨 先 檢 查 除 數 y 是 否 為 0 的 判 斷 式 , 例 如 :
錯 誤 類 型 之 四 : 弄 錯 運 算 符 號 的 優 先 順 序
數 學 運 算 式 中 , 先 × ÷ 後 + - , 相 信 大 家 都 記 得 清 清 楚 楚 , 不 過 一 個 夠 格 的 程 式 語 言 , 其 所 包 含 之 運 算 功 能 應 該 不 只 這 些 , 可 能 還 包 括 冪 次 方 、 求 餘 數 、 AND、 OR、 XOR 等 , 而 各 種 程 式 語 言 在 運 算 符 號 的 優 先 順 序 定 義 上 並 不 盡 相 同 , 舉 例 來 說 , 以 下 的 運 算 式 在 BASIC 及 C 語 言 的 運 算 結 果 並 不 相 同 : ( 註 : Mod 及 % 分 別 是 BASIC 及 C 語 言 的 求 餘 數 )
這 兩 種 運 算 式 的 結 果 之 所 以 不 同 , 問 題 就 出 在 運 算 符 號 的 優 先 順 序 上 , 其 中 BASIC 的 / 其 優 先 順 序 大 於 Mod, 所 以 結 果 等 於 10 Mod (6 / 2), 而 C 語 言 的 / 其 優 先 順 序 與 % 相 同 , 所 以 運 算 結 果 等 於 (10 % 6) / 2。
以 上 提 出 BASIC 及 C 語 言 的 差 異 , 並 不 是 要 您 去 熟 悉 所 有 程 式 語 言 在 運 算 元 上 的 優 先 順 序 , 只 是 提 醒 您 在 數 學 運 算 式 中 應 該 注 意 這 件 事 , 而 最 保 險 的 方 法 是 利 用 (), 只 要 是 沒 有 把 握 而 又 必 須 優 先 運 算 的 運 算 符 號 , 一 概 加 上 () 就 對 了 。
錯 誤 類 型 之 五 : 不 同 型 別 的 運 算
一 隻 雞 加 一 隻 雞 等 於 兩 隻 雞 , 但 一 隻 雞 加 一 條 狗 等 於 什 麼 呢 ? 這 就 好 像 "ABC" 加 100 是 沒 有 意 義 的 , 大 部 份 的 情 形 , 型 態 「 不 同 」 的 變 數 是 不 能 在 一 起 運 算 的 , 不 過 為 了 增 加 程 式 撰 寫 上 的 彈 性 , 有 一 大 半 的 程 式 語 言 允 許 型 態 「 相 容 」 的 變 數 互 相 作 運 算 , 例 如 整 數 與 浮 點 數 的 加 減 , 有 時 候 這 的 確 很 方 便 , 但 這 個 方 便 之 門 也 開 啟 了 bug 之 門 。
錯 誤 實 例 之 一 :
A 先 生 過 去 一 直 以 BASIC 開 發 程 式 , 最 近 因 工 作 需 要 改 用 C 語 言 , 他 將 一 「 計 算 平 均 數 」 的 程 式 段 直 接 由 BASIC 改 成 C 。
// 轉 換 成 C 以 後 的 程 式
錯 誤 說 明 :
請 注 意 這 個 程 式 中 的 sum 是 整 數 , 而 average 是 浮 點 數 , 一 般 來 說 , 浮 點 數 的 數 值 範 圍 比 整 數 來 得 大 , 所 以 將 整 數 指 定 給 浮 點 數 並 不 會 有 什 麼 問 題 , 這 個 例 子 所 出 的 問 題 其 實 是 在 除 號 (/)上 面 , 對 於 BASIC 來 說 , 以 / 所 運 算 出 來 的 結 果 均 表 示 成 「 浮 點 數 」 , 所 以 「 sum / 10」 所 得 到 的 結 果 是 55.0 / 10 = 5.5, 至 於 C 語 言 則 會 先 檢 查 / 號 兩 邊 的 資 料 型 態 , 然 後 再 選 擇 數 值 範 圍 較 大 的 資 料 型 別 作 為 運 算 的 根 據 , 以 「 sum / 10」 來 說 , 兩 邊 的 資 料 型 態 均 為 整 數 , 所 以 執 行 時 採 整 數 的 運 算 方 式 , 於 是 結 果 變 成 55 / 10 = 5。
註 : BASIC 的 另 一 個 運 算 符 號 \ 也 是 用 來 當 作 除 號 , 但 其 運 算 結 果 則 一 概 表 示 成 整 數 , 以 55 \ 10 為 例 , 其 結 果 等 於 5, 而 55.0 \ 10 的 結 果 也 是 5。
如 果 想 讓 前 面 的 C 程 式 得 到 正 確 的 結 果 有 三 個 方 法 : (1) 把 被 除 數 sum 的 資 料 型 態 改 成 浮 點 數 , (2) 在 被 除 數 sum 的 前 面 加 上 (float), 意 思 是 在 運 算 前 強 制 將 sum 轉 成 浮 點 數 , (3) 把 除 數 10 改 成 浮 點 型 態 的 10.0。
錯 誤 實 例 之 二 :
中 文 字 是 由 兩 個 字 元 所 組 成 的 , 其 中 第 一 個 字 元 一 定 大 於 等 於 128( = 16 進 位 的 80) , 以 下 的 C 語 言 程 式 段 就 用 這 個 原 則 來 判 斷 字 串 第 一 個 字 是 否 為 中 文 字 。
結 果 printf() 並 沒 有 被 執 行 , 這 表 示 the_word[0] >= 128 為 FALSE。
錯 誤 說 明 :
「 我 」 是 中 文 字 , 所 以 它 的 第 一 個 字 元 一 定 大 於 128, 而 在 「 the_word[] = "我 是 中 文 字 ";」 的 設 定 之 後 , the_word[0] 變 成 了 「 我 」 的 第 一 個 字 元 , 所 以 the_word[0] >= 128 應 該 為 TRUE。
問 題 點 就 出 在 the_word 的 資 料 型 態 上 , 由 於 它 被 宣 告 成 char, 而 C 語 言 中 char 是 含 有 正 負 值 的 1 byte 資 料 , 其 數 值 範 圍 是 -128 到 127, 因 此 「 我 」 的 第 一 個 字 元 在 指 定 給 the_word[0] 之 後 , 便 由 >= 128 的 數 值 變 成 <= 127 的 數 值 , 所 以 if (the_word[0] >= 128 ) 只 能 得 到 FALSE 值 了 。
解 決 這 個 問 題 的 方 法 是 把 the_word 的 宣 告 改 成 : 「 unsigned char」 型 態 , 使 其 數 值 範 圍 變 成 0 到 255, 這 樣 子 在 「 the_word[] = "我 是 中 文 字 ";」 的 設 定 之 後 , 就 不 至 於 有 數 值 被 強 制 轉 變 的 現 象 。
不 同 型 別 運 算 的 錯 誤 避 免 之 道 :
以 上 所 舉 的 只 是 不 同 型 別 運 算 後 產 生 錯 誤 的 兩 個 代 表 性 的 例 子 , 欲 避 免 所 有 的 錯 誤 , 以 下 是 筆 者 的 建 議 :
• 檢 查 各 種 運 算 符 號 ( + 、 - 、 × 、 ÷ 、 = 、 > 、 < 等 ) 兩 邊 的 資 料 型 別 是 否 相 同 , 若 不 同 , 則 要 先 確 定 程 式 語 言 是 如 何 處 理 這 些 運 算 符 號 的 , , 如 果 不 能 確 定 , 先 寫 個 小 程 式 試 看 看 。
• 不 要 假 設 同 一 種 運 算 符 號 在 不 同 程 式 語 言 之 間 會 有 相 同 的 處 理 結 果 , 例 如 除 號 (/)在 BASIC 及 C 語 言 中 就 有 不 同 的 處 理 方 式 。
if...else、 goto、 for 迴 圈 、 while 迴 圈 、 exit、 return、 break 等 指 令 讓 我 們 可 以 控 制 程 式 的 流 程 , 有 了 它 們 程 式 才 會 轉 彎 , 也 因 此 可 以 執 行 出 許 多 不 同 的 結 果 , 而 bug 也 特 別 喜 歡 躲 在 這 些 轉 彎 的 地 方 , 干 擾 程 式 的 執 行 結 果 。
goto 的 後 遺 症
大 家 都 知 道 濫 用 goto 將 使 程 式 的 結 構 變 得 不 好 , 影 響 程 式 的 可 維 護 性 , 不 過 那 並 不 表 示 任 何 情 況 都 不 要 使 用 goto, 舉 個 例 子 來 說 , 在 以 下 的 多 層 迴 圈 中 , 如 果 我 們 想 跳 出 所 有 的 迴 圈 , 使 用 goto 是 最 簡 單 明 白 的 方 法 。
其 實 goto 與 return、 exit、 break、 continue 等 指 令 在 本 質 上 並 沒 有 差 別 , 它 們 都 會 跳 過 一 段 程 式 , 雖 然 我 們 以 goto 作 為 討 論 的 標 題 , 但 實 際 上 所 要 談 的 是 「 跳 過 一 段 程 式 可 能 衍 生 的 後 遺 症 」 。
假 設 原 本 有 一 個 副 程 式 , 架 構 上 大 致 是 :
這 樣 的 副 程 式 很 不 錯 , 總 會 在 離 開 前 把 打 開 的 檔 案 關 掉 , 後 來 我 們 修 改 程 式 讓 它 在 某 種 狀 況 之 下 直 接 跳 離 副 程 式 , 所 以 加 了 一 行 判 斷 式 :
if ( condition ) return;
這 個 判 斷 式 當 然 沒 錯 , 但 我 們 忘 了 在 return 之 前 「 關 檔 」 了 。
前 面 這 個 例 子 是 個 簡 化 版 , fclose( fp ) 就 在 眼 前 , 所 以 很 容 易 看 到 這 樣 的 錯 誤 , 但 實 際 上 一 個 副 程 式 可 能 多 達 數 頁 , 在 我 們 加 上 跳 出 副 程 式 的 判 斷 式 時 , 通 常 不 會 看 到 fclose( fp ), 加 上 您 可 能 急 著 把 程 式 跑 出 來 , 根 本 就 忘 了 關 檔 那 一 回 事 了 。
而 更 糟 的 是 , 忘 了 關 檔 的 錯 誤 並 不 會 馬 上 顯 現 出 來 , 它 只 會 讓 程 式 的 可 開 檔 案 數 越 來 越 少 , 但 程 式 還 是 可 以 正 常 執 行 , 這 樣 的 錯 誤 必 須 在 不 退 掉 程 式 之 下 連 續 的 測 試 才 會 發 現 , 而 絕 大 多 的 人 總 以 為 測 過 兩 三 次 就 OK 了 , 於 是 這 個 bug 就 一 直 躲 藏 到 系 統 整 合 , 甚 至 軟 體 交 付 到 客 戶 的 手 上 才 被 發 現 。
除 了 「 開 、 關 檔 」 有 配 對 執 行 的 關 係 之 外 , 「 記 憶 體 的 配 置 、 釋 放 」 、 「 系 統 資 源 的 配 置 、 釋 放 」 等 也 有 相 似 的 性 質 , 在 「 跳 過 一 段 程 式 」 之 後 , 宜 防 只 執 行 了 「 頭 」 ( 例 如 開 檔 ) , 而 跳 過 「 尾 」 ( 例 如 關 檔 ) 的 現 象 。
無 窮 迴 圈
當 我 們 想 重 複 做 某 一 件 事 , 少 不 了 迴 圈 的 幫 忙 , 但 如 果 控 制 不 良 , 可 能 造 成 程 式 一 直 在 迴 圈 中 打 轉 , 進 入 所 謂 的 「 無 窮 迴 圈 」 。 一 旦 進 入 無 窮 迴 圈 , 程 式 就 像 死 當 一 樣 , 所 以 這 個 問 題 通 常 在 程 式 測 試 的 階 段 就 會 被 發 現 。
就 程 式 的 撰 寫 習 慣 上 , 有 些 小 動 作 可 以 輕 易 地 避 免 無 窮 迴 圈 的 發 生 , 舉 個 例 子 來 說 :
這 個 迴 圈 一 共 執 行 9 次 , 然 後 在 i = 10 的 時 候 跳 出 , 看 起 來 並 沒 有 什 麼 問 題 , 但 萬 一 我 們 在 「 迴 圈 內 的 程 式 段 」 也 會 改 變 i 的 值 , 那 麼 i 就 可 能 會 跳 過 10, 而 使 得 迴 圈 無 法 結 束 , 所 以 不 妨 把 「 i <> 10」 改 成 比 較 寬 鬆 的 條 件 「 i < 10」 , 以 降 低 進 入 無 窮 迴 圈 的 機 會 。
前 面 的 例 子 是 以 「 計 數 器 」 的 值 來 判 別 跳 出 迴 圈 的 條 件 , 只 要 稍 加 小 心 , 出 錯 的 機 會 其 實 不 大 , 另 一 種 情 形 是 以 「 輸 入 的 資 料 」 來 判 別 跳 出 迴 圈 的 條 件 , 舉 例 來 說 , 以 下 的 C 程 式 在 計 算 字 串 的 長 度 :
這 段 程 式 判 別 *str != '\0' 時 跳 出 for 迴 圈 , 這 是 假 設 輸 入 的 資 料 (str )一 定 包 含 '\0', 但 萬 一 str 不 含 '\0'( 也 許 是 呼 叫 端 的 錯 誤 ) , 就 可 能 造 成 無 法 跳 出 迴 圈 的 現 象 , 所 以 這 個 副 程 式 改 成 以 下 的 樣 子 比 較 好 : ( 在 迴 圈 中 多 加 了 「 n <= max」 的 判 斷 條 件 , 保 證 讓 迴 圈 可 以 結 束 )
無 窮 迴 圈 在 C 語 言 中 還 可 能 產 生 一 些 幻 象 , 下 面 的 程 式 以 顛 倒 順 序 取 出 字 串 的 字 元 ( 從 位 置 20 到 0) , 但 撰 寫 時 不 小 心 把 i-- 打 成 了 i++, 結 果 程 式 變 成 了 :
表 面 上 這 段 程 式 會 造 成 無 窮 迴 圈 , 但 其 實 不 然 , 因 為 i 加 到 極 限 時 會 繞 回 到 負 值 ( 如 果 是 BASIC 程 式 則 會 產 生 「 溢 位 」 的 錯 誤 ) , 還 是 跳 出 了 迴 圈 , 這 樣 的 錯 誤 在 執 行 時 並 不 會 馬 上 被 偵 測 到 , 頂 多 只 是 覺 得 程 式 有 點 慢 , 而 被 忽 略 了 。
邏 輯 判 斷 式
犯 錯 乃 人 之 常 情 , 在 棋 類 的 比 賽 中 , 獲 勝 者 往 往 是 想 的 步 數 多 而 又 犯 錯 少 者 , 在 程 式 設 計 中 , 沒 有 競 爭 對 手 , 邏 輯 判 斷 式 不 必 寫 得 複 雜 到 別 人 看 不 懂 , 反 倒 應 該 以 簡 單 明 瞭 為 原 則 , 舉 例 來 說 , 數 學 式 1 ≦ x ≦ 5 在 程 式 語 言 中 至 少 有 以 下 兩 種 表 達 方 式 :
其 中 以 第 (2) 種 表 達 方 式 與 大 家 習 慣 的 數 學 式 比 較 像 , 自 然 比 較 好 。
在 邏 輯 學 中 , 曾 教 過 我 們 一 些 簡 化 邏 輯 的 公 式 , 但 是 在 程 式 設 計 中 , 被 簡 化 的 式 子 未 必 比 較 容 易 理 解 , 舉 例 來 說 , 判 斷 某 一 個 變 數 不 在 1 與 5 之 間 , 其 推 演 過 程 是 :
所 以 以 Not (1 <= x And x <= 5 ) 來 表 達 變 數 不 在 1 與 5 之 間 是 很 自 然 , 而 且 容 易 理 解 的 。 不 過 有 人 認 為 這 樣 子 多 了 一 個 Not 的 判 斷 , 執 行 效 率 較 差 ( 他 小 看 了 編 譯 器 的 「 最 佳 化 」 能 力 ) , 所 以 想 把 這 個 判 斷 式 加 以 簡 化 , 根 據 De Morgan 定 理 :
Not (1 <= x And x <= 5 ) 應 該 轉 換 成 :
但 說 實 在 , 乍 看 之 下 , 這 個 被 簡 化 的 式 子 並 不 容 易 馬 上 意 會 到 它 想 表 達 的 是 x 介 於 1 及 5 之 間 。 而 筆 者 還 見 過 粗 心 的 程 式 設 計 師 把 它 轉 成 「 1 > x And x > 5 」 ( 忘 了 對 And 作 Not 的 動 作 ) 。
企 圖 簡 化 邏 輯 判 斷 式 應 該 小 心 , 而 對 於 過 於 冗 長 的 判 斷 式 則 應 該 讓 它 更 容 易 閱 讀 一 點 , 以 下 是 一 個 C 語 言 的 判 斷 式 :
乍 看 之 下 , 這 個 判 斷 式 想 表 達 (e1 Or e2) And (e3 Or e4) And e5, 但 依 程 式 語 言 的 慣 例 來 看 , And 會 優 於 Or 先 被 處 理 , 因 此 上 面 的 判 斷 式 應 該 等 於 e1 Or (e2 And e3) Or (e4 And e5), 所 以 不 妨 加 上 幾 個 () 使 判 斷 式 更 容 易 閱 讀 一 點 , 所 以 筆 者 建 議 把 以 上 的 C 語 言 判 斷 式 改 成 :
可 避 免 將 來 閱 讀 程 式 時 產 生 誤 解 。
如 果 您 寫 C 程 式 , 以 下 是 一 個 在 C 程 式 中 極 為 普 遍 的 錯 誤 :
結 果 不 管 x 的 值 等 於 什 麼 , 這 個 判 斷 式 永 遠 都 成 立 , 仔 細 檢 查 , 原 來 把 用 來 「 比 較 相 等 」 的 == 打 成 了 「 指 定 數 值 」 的 =, 所 以 經 過 「 x = 1」 之 後 , x 就 變 成 了 1, 而 被 判 斷 成 TRUE。
這 種 錯 誤 起 於 一 時 打 錯 , 而 它 在 C 語 言 中 又 屬 合 法 的 式 子 , 可 以 通 過 編 譯 器 的 檢 查 , 這 樣 的 錯 誤 往 往 不 是 肉 眼 可 以 馬 上 看 得 出 來 的 , 容 易 造 成 偵 錯 的 死 角 , 對 於 上 述 的 錯 誤 , 筆 者 建 議 將 「 常 數 」 放 在 「 比 較 等 於 的 ==」 的 左 邊 , 例 如 :
因 為 這 與 if ( x == 1 ) 的 意 思 完 全 相 同 , 而 萬 一 我 們 不 小 心 將 == 打 成 = 時 , if ( 1 = x ) 並 不 會 通 過 編 譯 器 的 檢 查 , 可 提 前 在 編 譯 階 段 檢 查 出 這 樣 的 錯 誤 。
打 錯 了 變 數 名 稱
只 要 是 人 , 一 定 會 打 錯 字 , 在 程 式 撰 寫 時 , 您 可 能 少 打 一 個 右 括 弧 , 或 是 多 打 一 個 分 號 , 也 可 能 把 函 數 或 變 數 的 名 稱 打 錯 了 , 這 些 錯 誤 絕 大 部 份 無 法 通 過 編 譯 器 的 法 眼 , 但 仍 有 少 數 錯 誤 可 以 過 關 , 這 些 漏 網 之 魚 基 本 上 還 是 符 合 程 式 的 語 法 , 只 是 與 程 式 設 計 師 的 意 思 相 違 背 , 也 許 正 因 為 如 此 , 這 類 型 的 錯 誤 也 容 易 被 忽 略 , 形 成 偵 錯 上 的 死 角 。
錯 誤 實 例 之 一 : 以 下 是 一 個 BASIC 的 程 式 段 , 在 計 算 利 率 加 本 金 。
Interest = Total * 0.08 ' 計 算 「 利 率 」
Total = Total + Intrest ' 本 金 + 利 率, Interest, 漏了 e
錯 誤 說 明 :
由 於 BASIC 允 許 在 「 不 預 先 宣 告 變 數 」 下 直 接 使 用 變 數 , 使 得 例 子 中 的 Intrest 被 當 成 另 一 個 新 的 變 數 , 而 不 會 在 編 譯 階 段 被 偵 測 出 來 。 如 果 換 成 C 、 PASCAL 等 「 變 數 必 須 事 先 宣 告 」 的 語 言 , 編 譯 器 會 馬 上 告 訴 您 「 Intrest 未 宣 告 」 , 使 這 種 錯 誤 不 至 於 溜 到 執 行 階 段 , 浪 費 偵 錯 的 時 間 。
錯 誤 避 免 :
「 不 預 先 宣 告 變 數 」 就 可 以 直 接 使 用 變 數 雖 然 方 便 , 卻 可 能 發 生 打 錯 字 而 不 自 覺 的 現 象 , 在 兩 權 相 害 取 其 輕 之 下 , 最 好 關 掉 這 個 方 便 之 門 。 在 BASIC 系 列 的 開 發 工 具 中 , Visual Basic 就 可 以 在 程 式 中 加 上 「 Option Explicit」 , 要 求 編 譯 器 將 未 預 先 宣 告 的 變 數 當 作 錯 誤 ; Clipper 中 則 可 以 在 編 譯 時 加 上 /W 的 選 擇 , 請 編 譯 器 針 對 未 預 先 宣 告 的 變 數 提 出 警 告 。
錯 誤 實 例 之 二 : 以 下 是 一 個 C 程 式 , 在 印 出 九 九 乘 法 表 。
錯 誤 說 明 :
這 個 錯 誤 出 在 第 二 個 迴 圈 的 j++ 打 成 了 i++, 我 們 大 概 可 以 猜 出 為 什 麼 會 犯 這 樣 的 錯 誤 : 程 式 撰 寫 到 第 二 個 for 迴 圈 時 , 一 看 與 第 一 個 迴 圈 相 似 , 於 是 就 把 第 一 個 for 迴 圈 複 製 下 來 , 但 修 改 中 漏 改 了 一 個 j, 而 j 又 與 i 長 得 差 不 多 , 不 注 意 看 就 沒 看 出 來 , 所 以 產 生 這 樣 的 錯 誤 。
錯 誤 避 免 :
以 i, j, k... 作 為 迴 圈 指 標 是 一 般 人 的 習 慣 , 但 應 該 避 免 在 多 層 迴 圈 中 同 時 使 用 近 似 的 的 字 母 , 例 如 「 i 與 j」 、 「 m 與 n」 。
|
一 個 打 錯 變 數 名 稱 的 小 故 事 有 一 回 幫 一 位 女 性 組 員 debug, 找 了 一 陣 子 才 發 現 原 來 是 變 數 名 稱 打 錯 了 , 其 實 那 個 錯 她 本 來 可 以 自 己 找 出 來 的 , 但 四 百 度 近 視 的 她 寫 程 式 時 偏 偏 不 戴 眼 鏡 ( 大 概 是 愛 漂 亮 ) , 而 且 又 喜 歡 把 螢 幕 調 得 暗 暗 的 ( 大 概 是 怕 輻 射 ) 。 |
舉 個 例 子 來 說 :
我 們 宣 告 了 一 個 大 小 為 10 的 陣 列 , 卻 想 把 數 值 指 定 到 陣 列 的 第 20 個 位 置 裡 , 當 然 不 會 有 人 寫 出 這 麼 蠢 的 程 式 , 但 相 信 您 寫 過 類 似 以 下 的 程 式 :
這 樣 的 程 式 並 沒 有 什 麼 問 題 , 但 它 仍 有 一 個 缺 點 : 當 我 們 想 改 變 陣 列 的 大 小 時 , 必 須 同 時 更 改 兩 個 地 方 ( 陣 列 的 宣 告 及 For 迴 圈 ) , 假 如 For 迴 圈 出 現 多 處 , 更 改 的 地 方 可 能 還 不 只 兩 個 , 也 許 您 就 漏 改 了 其 中 的 某 一 個 。 針 對 以 上 的 問 題 , 以 下 是 一 種 常 見 的 改 良 方 式 :
這 樣 子 一 來 , 想 改 變 陣 列 的 大 小 , 只 要 改 變 常 數 ARR_MAX 就 可 以 了 , 不 必 再 擔 心 漏 改 了 什 麼 地 方 。
除 了 以 迴 圈 來 存 取 陣 列 之 外 , 單 獨 存 取 的 情 形 也 十 分 普 遍 , 例 如 :
arr(idx) = some_value
這 樣 寫 就 由 idx 當 時 的 值 來 決 定 是 否 會 發 生 「 指 標 超 過 陣 列 的 範 圍 」 , 也 許 在 自 我 測 試 階 段 idx 都 剛 好 在 範 圍 之 內 , 但 這 並 不 能 確 保 idx 的 值 永 遠 不 會 超 過 陣 列 的 範 圍 , 不 妨 加 個 檢 查 來 得 更 安 心 :
有 一 件 值 得 注 意 的 事 情 , 當 指 標 超 過 陣 列 的 範 圍 時 , BASIC 的 程 式 會 立 刻 產 生 「 Subcript out of range」 的 錯 誤 訊 息 , 然 後 中 斷 程 式 的 執 行 , 但 是 對 C 程 式 來 說 , 卻 不 會 檢 查 陣 列 指 標 是 否 超 出 範 圍 , 而 更 糟 的 是 類 似 arr(idx) = some_value 的 指 定 動 作 還 是 會 照 常 執 行 , 於 是 造 成 某 一 塊 未 知 的 記 憶 體 被 破 壞 , 如 果 因 此 讓 程 式 當 掉 還 比 較 容 易 偵 錯 , 最 怕 程 式 還 能 執 行 而 結 果 卻 錯 得 離 譜 。
全 域 變 數
全 域 變 數 的 缺 點 , 我 們 在 前 面 兩 期 已 經 談 過 許 多 , 不 發 生 錯 誤 則 已 , 只 要 發 生 錯 誤 , 它 極 可 能 到 處 流 竄 , 特 別 難 抓 , 因 此 使 用 時 要 特 別 小 心 。
當 程 式 越 來 越 大 時 , 想 檢 查 所 有 bug 可 能 出 現 的 地 方 就 越 來 越 難 , 就 像 房 子 已 經 佈 置 好 了 再 來 翻 箱 倒 櫃 找 蟑 螂 是 比 較 累 的 事 情 , 因 此 我 們 應 該 在 程 式 剛 開 始 編 碼 時 , 就 注 意 哪 些 地 方 容 易 躲 藏 bug, 甚 至 埋 設 一 些 檢 查 的 指 令 , 以 期 減 少 將 來 debug 的 時 間 。