-
Notifications
You must be signed in to change notification settings - Fork 6
CODING STYLE zh_tw
Wei-Cheng Yeh (IID) edited this page Dec 10, 2023
·
49 revisions
本頁說明 DreamBBS 的 coding style 與慣例 (不含 indentation style)。
Indentation style 的說明,請見 INDENT。
- 語法要符合 10 年前最新的 ISO C 標準 (C11),但不應使用不被最新 ISO C++ 標準或草案 (C++23) 所支援的語法
- 至 2019-09-01 為止的程式碼,在語法上符合 C99 而已經不符合 C90,已不必再繼續維持 C90 語法
- 需要支援 C++ 時,僅考慮過去 10 年內的 ISO C++ 標準 (最舊到 C++14),不必考慮更久遠的標準
- 新的程式碼不能將最新 ISO C++ 標準中的關鍵字當作變數/函式/型別名
- 至 2019-09-01 為止沒有轉移使用 C++ 的計畫,不須完全相容標準 C++ 語法
- 不過,至 2020-02-24 為止已基本相容 C++20 語法,可通過
g++-8
或clang++-6
編譯並正常執行
- 可以使用 GNU C extensions;
但若能以等效 ISO C 語法代替且不影響可讀性,就僅使用 ISO C 語法
- 目前 (2022-03-19) 僅使用 GCC 和 Clang 編譯器,而它們都支援 GNU C extensions
- 原則上一律使用英文
- 不應使用其它語言的拉丁化文字
- 為求用法的一致,遇到用詞上有英式與美式之別時,原則上一律使用美式用法
- 如用詞包含附加符號,原則上不省略附加符號
但以下情況例外:
- 用於顯示的字串
- 目前此專案尚待支援介面上的國際化
- 從其它程式專案引進的註解
- 舊有註解
- 直接引用的文字註解
- 尚未有正式英文名稱或尚不能以英文精準描述的概念
- 官方名稱原文並非英文的專有名詞 (但有英文名稱時須列出補充)
上述例外情況中,若有必要,可再補充人工翻譯至英文的文字。
如使用的非英文語言使用拉丁字母系統作為文字,則須標明所使用的語言。
基本上適用程式碼與註解的規則,再加上以下規則:
- 僅能使用 7-bit ASCII 字元
- 可使用其它語言,惟使用語言非英文時,原則上應標明文件所使用的語言
- 例外:舊有說明文件可不標明所使用的語言
- Commit 標題應為以下格式之一
<修改種類>(<修改範圍>): <簡要說明>
-
<修改種類>(<修改範圍>): <原文字> -> <新文字>: <簡要說明>
:有重要的文字替換-
原文字
或新文字
,若含有空格或->
的話,應被`…`
包圍
-
-
<修改種類>: <簡要說明>
:全範圍修改 - 簡要說明末可加上
[<相關 issues 的編號>]
或[<關鍵字> <相關 issues 的編號>]
-
<關鍵字>
可為fix
或close
-
- 修改種類可為下列之一:
-
docs
:僅說明文件或註解改變,實際程式碼不變 -
refactor
:程式碼改變,但程式邏輯不變 -
chore
:程式邏輯改變,效能不變或降低,但功能不變 -
perf
:程式邏輯改變,效能改善 -
fix
:程式邏輯或功能的修正- 包含效能從 0 至非 0 的改善
-
feat
:新功能或功能改變 -
test
:測試程式的新增或功能改變- 相當於測試程式的
feat
- 相當於測試程式的
-
build
:以 Makefile 的改變或修正為主 -
ci
:以 GitHub Action 的改變或修正為主
-
- 修改範圍可為下列之一或其組合:
<目錄名稱>/
-
<檔案名稱或路徑>
- 非 header 檔案,若無其它主檔名相同的檔案的話,可省略其副檔名
- 可包含萬用字元
-
<概念名稱>
,應使用大寫開頭。代表性的有:-
UI
:使用者介面 Xover
Main Menu
-
M3 More
:Maple 3.xx 原生文章瀏覽函式庫(非pmore
) -
M3 Visio Screen
:Maple 3.xx 原生畫面繪製函式庫(非pfterm
) -
DBCS
:CJK 雙位元組字元集 -
LP64
:見於 x86_64 Linux 平臺的 LP64 架構 -
UB
:C/C++ 語言的未定義行為
-
<大範圍概念名稱>/<小範圍概念名稱>
<範圍> <程式物件名>
-
<範圍>, <適用條件>
-
<適用條件>
代表性的有:-
C
,使用 C 語言模式編譯 -
C++20
,使用 C++20 語言模式編譯 -
GNU C
,使用帶有 GNU 語言擴充的 C 語言模式編譯 -
pfterm
,啟用了pfterm
-
!NO_SO
,未#define
NO_SO
、#undef NO_SO
、或#define
NO_SO
為0
-
-
-
<範圍>; <注意事項>
-
<注意事項>
全為大寫,代表性的有:NEEDS SHM RELOAD
NEEDS CONF CHANGE
-
- 簡要說明中,應敘述具體變更或新的行為
- 敘述舊的行為時,應使用
fix …
、instead of …
等字詞 - 修改種類為
fix
時,禁止將舊的行為直接當作簡要說明- 應使用
fix(…): fix …
的方式呈現
- 應使用
- 需敘述多個不相關的變更或行為時,應先嘗試分拆 commit;需敘述多個彼此相關的變更或行為時,應先考慮以其它方式敘述,不可行的話再以分號
;
分隔- 以網頁介面編輯 wiki 時,可不必分拆 commit,亦不必以其它方式敘述,直接以分號
;
分隔即可
- 以網頁介面編輯 wiki 時,可不必分拆 commit,亦不必以其它方式敘述,直接以分號
- 敘述舊的行為時,應使用
- Commit 內文依序可包含以下內容:
- 原因:陳述一般事實
-
* <修改方式或新程式行為>
- 行尾可加上
[<單字元代號>]
作為編號 - 具體原因可在內部項目列出
- 影響範圍可用
* <受影響程式物件>
的方式在項目內部列出呈現
- 行尾可加上
-
* <被修改程式物件>
- 行尾可加上
[<代號字串>]
表示受到對應的修改 - 具體修改可在內部項目列出
- 行尾可加上
-
Reference:
-
* <參考資料的項目>
(無 indentation)* > 引用文字
-
-
DEPRECATED: <程式物件名>
- (空一行)
-
<原因及更新方式說明>
(無 indentation) - 若要 deprecate 多個不直接相關的物件的話,應先嘗試分拆 commit,不可行的話應以多個
DEPRECATED:
分別列出
-
BREAKING CHANGE: <更改簡述>
- (空一行)
-
原因及更新方式說明
(無 indentation) - 若有多個不直接相關的 breaking changes 的話,應先嘗試分拆 commit,不可行的話應以多個
BREAKING CHANGE:
分別列出
- Issue 編號格式應為
#<編號>
或<Repo 擁有者>/<Repo 名>#<編號>
其一 - Commit hash 格式應為
<完整 hash>
或<Repo 擁有者>/<Repo 名>@<完整 hash>
其一 - 其它 repo 的檔案路徑格式應為
<Repo 擁有者>/<Repo 名>@<完整 hash 或 branch/tag 名>/<檔案路徑>
- 程式物件名應為下列格式之一:
-
<
<路徑>
>
(被角括號<…>
包圍):物件為系統 header 檔 -
`<路徑>`
:物件為檔案路徑,且出現於內文中 -
`<struct/union/enum/class> <型別名>`
:物件為struct
/union
/enum
/class
型別,且非以typedef
或using
定義之別名 -
<struct/union/enum/class> `<型別名>`
:物件為其它struct
/union
/enum
/class
型別-
`…`
可依照「其它種物件」的原則決定是否省略
-
-
<型別>::<成員>
:物件為型別成員(可與以下格式組合) -
<物件名>[]
:物件為陣列 -
<物件名>()
:物件為函式或 function-like macro -
`<物件名>`
:其它種物件-
`…`
在物件名符合以下任一情況時可省略:- 並非出現於內文中
- 含底線
_
或貨幣符號$
- 包含非位於開頭的大寫字母
- 不在句首而以大寫字母開頭
-
- 物件或一群物件為 macro 時,其名稱前方應加上
macro
或macros
- 物件或一群物件為參數時,其名稱前方應加上
param
或params
-
- 直接引用 shell 命令或不只包含物件名的程式碼時,應使用
`…`
的方式呈現- 以無引數的方式呼叫函式的程式碼應使用
`func()`
的方式呈現,而非func()
- 物件名可包含萬用字元
- 以無引數的方式呼叫函式的程式碼應使用
- 單純並列 2 個事物時,應以
A & B
的方式呈現 - 單純並列 ≥ 3 個事物時,應以
A, B, …, & Z
的方式呈現 - 列出多個事物作為選項時,應以
A/B/…/Z
的方式呈現 - 應統一以
*
作為列表項目符號 - 應使用 4-space 的 indent
- 項目文字需換行時,換行後增加 4-space 的 indentation
- 非項目文字需換行時,換行後不增加 indentation
- 陳述程式碼變更時,句首以小寫開頭的原形動詞開頭,不以句號結尾
- 陳述新的程式動作時,句首以大寫開頭的原形動詞開頭,不以句號結尾
- 陳述一般事實時,句首以大寫開頭,並以句號結尾
- 註解單獨成行時,應以
/**/
的形式說明其後方的程式碼,以//
的形式說明其前方的程式碼 - 註解位於行末時,應使用
//
的形式 - 單行中,若行內註解過多,應分拆為多行並改為使用行末註解
- 註解開頭可包含以下額外資訊:
// <TAG>(<註解者暱稱>.<日期>):
// <TAG>(<註解者暱稱>):
// <TAG>:
// <註解者暱稱>.<日期>:
// <註解者暱稱>:
- 禁止僅包含標註日期的註解開頭
-
<TAG>
可為下列之一:-
XXX
:須特別注意的程式碼;建議改用下列其一 -
HACK
:原理特別或艱澀,可能需改寫的程式碼 -
FIXME
:待修正的程式碼 -
TODO
:待辦事項
-
- 日期格式須為下列之一:
-
YYYY-MM-DD
:帶 ISO 式日期的註解(建議新註解使用) -
YYYYMMDD
:舊式帶日期的註解(不建議新註解使用) -
YYMMDD
:主要見於公元 2000 年前的程式碼(禁止新註解使用)
-
- 註解內容不應包含「註解」兩字或其同義詞
- 禁止忽略註解開頭後的內容符合以下任一情況的新註解:
- 無內容的註解
- 內容僅包含「註解」兩字或其同義詞的註解
- 內容與被註解的程式碼的字面意義相同的註解
- 禁止改動註解開頭包含註解者暱稱的註解,應另外增加註解以說明原註解的問題
- 但以下情況除外:
- 原註解含有錯字
- 原註解為亂碼,但可幾乎完全解讀,或是可找到非亂碼的版本
- 註解內含有過時程式碼
- 此情況宜改增加註解說明,或是直接刪除原註解並重寫
- 若改動或刪除此類註解,須記錄於 commit 訊息
- 但以下情況除外:
- 使用某事物的注意事項的註解,應只撰寫一份並置於其定義處,而非複製多份置於其使用處
- 若此事物僅與語言本身或標準函式庫相關,則不應註解
- 若需進行說明,則應記錄於 commit 訊息
- 若此事物僅與語言本身或標準函式庫相關,則不應註解
- 陳述程式動作時,句首以小寫開頭的原形動詞開頭,不以句號結尾
- 簡述程式物件或其用途時,句首以大寫開頭,不以句號結尾
- 陳述一般事實時,句首以大寫開頭,並以句號結尾
- 不應以底線
_
後接大寫字母開頭,或包含連續兩個或以上的底線__
, 因其被 ISO C 與 ISO C++ 標準保留給編譯器與標準函式庫的內部實作 - Enumeration、object-like macro、等等編譯時期常數的名稱應為 MACRO_CASE
- Function-like macro 的名稱應為 MACRO_CASE
- 若不以動詞或助動詞開頭,所包含的單字間應使用底線
_
隔開 - 若會造成與系統函式庫的 macro 名稱衝突,且為常用 macro,方可改用 PascalCase, 但不應包含兩個或以上的大寫字母
- 若其用法及作用與某普通函式相似,且要使用類似的名稱以便於記憶,方可改用函式的命名方式
- 若不以動詞或助動詞開頭,所包含的單字間應使用底線
- Function-like macro 的參數名稱必須為 snake_case,且應有單個底線
_
的前綴 - 資料結構的名稱應為 PascalCase,且不應為單字元,亦不應包含動詞或助動詞
- 若為既有資料結構,方可維持 UPPERFLATCASE,惟不應包含底線
_
- 不應使用 snake_case 接上
_t
後綴,因其被系統函式庫所保留
- 若為既有資料結構,方可維持 UPPERFLATCASE,惟不應包含底線
- 函式的名稱可為 snake_case、camelCase、或 flatcase 前綴後接底線
_
再接 camelCase, 且不應以底線_
開頭- 若為既有資料結構,方可維持 PascalCase,惟應含有動詞或以
Xo
為前綴詞 - 若不以動詞或助動詞開頭,所包含的單字間應使用底線
_
隔開 - 若命名時所使用的前綴詞與標準函式庫中某些函式的前綴詞相同,前綴詞後須使用底線
_
- 若為既有資料結構,方可維持 PascalCase,惟應含有動詞或以
- 避免在 PascalCase 或 camelCase 中使用連續的大寫字母
- 變數與資料結構成員的名稱必須為 snake_case,且不應以底線
_
開頭 - 程式碼檔的去除副檔名後的檔名應為 snake_case
- 命名長度
- 簡易判斷原則:名稱長度應與其作用域大小成正比,並與其常用程度成反比
- 應使全域變數的名稱與區域變數的名稱易於區分
- 禁止在標頭檔中宣告或定義單字元的 enumeration、macro、變數、或函式
- 禁止程式碼檔的去除副檔名後的檔名為空或為單字元
- 型別命名法
-
bool
變數的名稱應含有形容詞 - 回傳
bool
的函式的名稱應含有do
以外的助動詞 - N 層指標變數的名稱應有 N 個
p
前綴, 但若解參照 M 層後的指標值本身被當作陣列或字串使用則應有 M 個p
前綴, 而原名已由ptr
開頭時則省略最後一個p
前綴 - 變數名稱不應包含資料大小、有號與否、或資料對齊單位的資訊
-
- 若某事物的定義改變,且導致其用法改變時,應更改其名稱,尤其是下列事物:
- 全域函式的回傳值 → 更改函式名稱
- 全域函式的參數 → 更改此參數名稱
- 全域資料結構的成員 → 更改此成員名稱
- 改寫並修訂自 itoc 所撰寫的〈[文件] 一些常用參數的名稱〉
名稱 | 常見型別 粗斜體表示限用此型別 |
意義 | 備註 |
---|---|---|---|
rc |
int |
return code 回傳碼 | 應改用 ret
|
ret |
int |
return value 回傳值 | 也可回傳其它型別,只是原始 MapleBBS 3 未如此使用 |
res |
(不定) | result value 結果值 | DreamBBS v2.0 引入 |
fp |
FILE * |
file pointer 檔案指標 | 不應與 fd 混淆 |
fd |
int |
file descriptor 檔案描述子 | 不應與 fp 混淆 |
ch |
int |
(temporary) character (暫時) 字元 | |
num |
int |
(temporary) number (暫時) 數字 | 迴圈數字變數應使用 i 、j 、k 、等等名稱 |
pos |
int |
position 位置 - 元素索引位置 (Xover, etc.) - 包含 ANSI escapes 後的游標原始縱排座標 (visio) - 游標顯示縱排座標 (edit) |
在 visio 與在 edit 中的定義相反,不應混淆 |
col |
int |
column (position) 縱排 (位置) - 游標顯示縱排座標 (visio) - 包含 ANSI escapes 後的游標原始縱排座標 (edit) |
在 visio 與在 edit 中的定義相反,不應混淆 |
max |
int |
maximum 最大值 | 常用於 x < max (排除性上界) |
ufo |
unsigned int |
user favorite option (= user preference 使用者偏好設定) |
|
buf |
char [] |
(temporary) buffer (暫時) 緩衝區 | |
msg |
char [] (for chatting, displayed message, etc.) |
message 訊息 | |
tmp |
- char [] - (any) |
- temporary (buffer) 暫時 (緩衝區) - temporary (variable) 暫時 (變數) |
應改用其它意義更明確的名稱 |
cmd |
- char [] (for chatting, etc.) - int (for Xover, etc.) |
- (text) command (文字) 命令 - command (code) 命令 (代碼) |
|
ans |
- char [3] (for vget() , etc.) - int (for vmsg() , etc.) |
answer (= response 回應) | |
uid |
char [IDLEN + 1] /const char *
|
user ID 使用者 ID | |
bid |
char [IDLEN + 1] /const char *
|
board ID 看板 ID | 罕用 |
fpath |
char [] /const char *
|
file path 檔案路徑 | |
folder |
char [] /const char *
|
folder (path) 資料夾 (路徑) | |
str |
const char * |
string 字串 | 唯讀;僅用於讀取 |
ptr |
(const) char * |
pointer 指標 | |
dir |
const char * |
directory 目錄 | |
slp |
screenline * |
screenline pointer screenline 指標 |
|
slt |
screenline /screenline [] |
screenline temporary 暫時 screenline
|
罕用 |
hdr |
HDR /(const) HDR * |
(generic) (file) header (通用) (檔案) 標頭 | |
mhdr |
HDR /(const) HDR * |
mail (file) header 信件 (檔) 標頭 | |
fhdr |
HDR /(const) HDR * |
file header 檔案標頭 | |
ghdr |
HDR /(const) HDR * |
gem (file) header 精華區 (檔) 標頭 | |
brd |
BRD /(const) BRD * |
board (header) 看板 (標頭) | |
mf |
MF /(const) MF * |
my favorite 我的最愛 (MapleBBS-itoc 版) | DreamBBS 未使用 - pmore 亦使用 mf 作為存放執行資訊的變數名 |
myfavorite |
HDR /(const) HDR * |
my favorite 我的最愛 (DreamBBS 版) | DreamBBS 特有 - 罕用,常以 hdr 代之 |
nbrd |
NBRD /(const) NBRD * |
new(ly applied) board 新 (申請) 看板 | |
acct |
ACCT /(const) ACCT * |
(user) account (data) (temporary) (暫時) (使用者) 賬號 (資料) | |
u |
(const) ACCT * |
user (account data) (pointer) 使用者 (賬號資料) (指標) | 名稱過短,應改用 acct
|
cuser |
ACCT |
current user (account data) 目前使用者 (帳號資料) | 全域變數 |
utmp |
UTMP /(const) UTMP * |
user (online) temporary (data) 使用者 (線上) 暫時 (資料) | |
up |
(const) UTMP * |
user (online temporary data) pointer 使用者 (線上暫時資料) 指標 | |
cutmp |
UTMP * |
current user (online) temporary (data) 目前使用者 (線上) 暫時 (資料) | 全域變數 |
pal |
PAL /(const) pal * |
pal 好友 | |
aloha |
ALOHA /(const) ALOHA * |
aloha 打招呼 (= element of user login notification list 使用者登入通知名單元素) |
|
bmw |
BMW /(const) BMW * |
BBS message write (= user message 使用者訊息) |
又稱「熱訊」、「水球」、等等 |
benz |
BMW /(const) BMW * |
similar to BMW 類似於 BMW (= user login message 使用者登入訊息) |
Maple-itoc 使用 BENZ
|
xo |
XO /XO * |
Xover (data) Xover 資料 | |
xt |
XO * |
Xover (data) temporary (pointer) 暫時 Xover (資料) (指標) | |
xz |
XZ [] |
Xover zone (data) Xover 區域 (資料) | 全域變數 |
- 應透過限制變數的 scope 而非重用變數來節省記憶體的使用量
- 限制變數 scope 有利於編譯器分析變數的使用狀況,利於讓編譯器重新利用不使用的變數的記憶體空間;
而重用變數不利於編譯器分析變數的使用狀況
- 限制變數 scope 有利於編譯器分析變數的使用狀況,利於讓編譯器重新利用不使用的變數的記憶體空間;
- 不應一次將所有變數宣告於函式定義的開頭
- 應善用 block scope 變數以及 C99 的迴圈 scope 變數
- 可依可讀性的需要,而在須使用之處時再定義變數
Good:
for (int i = 0, n = get_n(sth); i < n; ++i) {
code;
}
Bad:
int i, n;
n = get_n(sth);
for (i = 0; i < n; ++i) {
code;
}
- 宣告變數時,應將其顯式初始化為所需的值
- 如為
struct
型別的變數,且所需的值為0
,則必須將其顯式初始化- ISO C++ 會將隱式初始化的
struct
型別變數進行0
初始化,若其後再使用memset()
將其歸零,則有礙閱讀,且有效能不彰之虞
- ISO C++ 會將隱式初始化的
- 如為
- 在維持易讀性的前提下,儘可能不要定義暫時變數,尤其是不要定義未使用的變數;既有的未使用變數則應移除
- 非得使用暫時變數時,則儘可能使用
const
Good:
char buf[32];
const char *str = "<anonymous>";
const char *const name = get_name();
if (name) {
strlcpy(buf, name, sizeof(buf));
str = buf;
}
process(str);
Bad:
char buf[32];
char *str = "<anonymous>";
char *name = get_name();
size_t len;
if (name)
len = strlcpy(str = buf, name, sizeof(buf));
process(str);
- 欲僅宣告全域變數並於稍後定義時,應使用
extern
- 減少與避免全域變數的使用
- 不要使用全域變數回傳函式執行結果;盡量使用
return
或 output arguments
- 不要使用全域變數回傳函式執行結果;盡量使用
Good:
bool func(void)
{
if (do_task() == TASK_SUCCESS)
return true;
return false;
}
void process(void)
{
if (func())
do_sth();
else
do_sth_else();
}
Bad:
static bool ok = false;
void func(void)
{
if (do_task() == TASK_SUCCESS)
ok = true;
else
ok = false;
}
void process(void)
{
func();
if (ok)
do_sth();
else
do_sth_else();
}
-
- 如未能完全避免全域變數的使用,則應將用於同一功能的全域變數以 struct 組織起來
- 程式碼不應造成 compiler 發出容易解決的 warning
- 對於用語言標準難以解決的 compiler warning,如果使用 GNU C extension 可容易解決,就使用 GNU C extension;
如果還是難以解決,就暫不解決,等待新的語法標準或新的 GNU C extensions
- 對於用語言標準難以解決的 compiler warning,如果使用 GNU C extension 可容易解決,就使用 GNU C extension;
- 不應假設函式的回傳值必為某值
- 不應為了節省記憶體的使用,而將函式的指標參數所指向的 struct 暫時用作其他型別資料的 buffer
- 此能避免改動相關程式後出現 buffer 大小不足的狀況
Good:
int func(Struct *obj)
{
FILE *fp;
{
char path[LENGTH];
get_path(path);
if (!(fp = fopen(path, "r")))
return 1;
}
code_about_obj;
fclose(fp);
return 0;
}
Bad:
int func(Struct *obj)
{
FILE *fp;
get_path((char *)obj);
if (!(fp = fopen((char *)obj, "r")))
return 1;
code_about_obj;
fclose(fp);
return 0;
}
- 不要使用避免或依賴編譯器最佳化的 workarounds
- 不要以破壞可讀性的方式手動最佳化運算式
- 現代許多編譯器已經能夠自動最佳化運算式(
gcc
及clang
在-O0
下也會最佳化)
- 現代許多編譯器已經能夠自動最佳化運算式(
Good:
int y = get_value();
int x = 31 * y;
Bad:
int y = get_value();
int x = (y << 5) - y;
- 避免撰寫不必要的程式分支
- 避免 control hazard
Good:
x = 0;
Bad:
if (x != 0)
x = 0;
Good:
free(ptr);
Bad:
if (!ptr)
free(ptr);
- 根據 ISO C 與 ISO C++ 標準,
free(NULL)
不具有任何作用,無須手動進行空指標檢查。
- 避免 boilerplate code,以減少 code size
- 需要增加新功能時,盡量使用或擴充既有的函式,不要複製原有函式
Good:
void func(const char *str_task)
{
do_sth(str_task);
}
Bad:
void func(void)
{
do_sth("sth");
}
void func2(void)
{
do_sth("sth_else");
}
- 常用的位元與邏輯運算的形式:
運算 | 邏輯運算 | 位元運算 | 位元賦值 |
---|---|---|---|
BUF 緩衡 |
!!x (bool)x
|
x |
(no-op) |
NOT 反相 | !x |
~x |
x = ~x x ^= ~(0 ? x : 0)
|
AND 及 | x && y |
x & y |
x &= y (bit mask) |
NAND 反及 | !x || !y |
~(x & y) |
|
OR 或 | x || y |
x | y |
x |= y (bit set) |
NOR 反或 | !x && !y |
~(x | y) |
|
XOR 互斥或 |
!!x != !!y (bool)x != (bool)y
|
x ^ y |
x ^= y (bit toggle) |
XNOR 反互斥或 |
!!x == !!y (bool)x == (bool)y
|
~(x ^ y) |
|
IMPLY 蘊含 |
y || !x !x || y (bool)y >= (bool)x (bool)x <= (bool)y
|
~((x | y) ^ y) ~(x & ~(0 ? x : y))
|
|
NIMPLY 反蘊含 |
x && !y !y && x (bool)x > (bool)y (bool)y < (bool)x
|
(x | y) ^ y x & ~(0 ? x : y)
|
x &= ~(0 ? x : y) (bit clear) |
- 避免對二元位元運算的運算元直接使用
~
,以使該運算元為無號整數且寬度至少為int
但窄於另一運算元時(e.g.,~(unsigned int)x & (long long)y
)的結果正確。- 若需使用
~
,應先將該運算元轉型成至少與另一運算元同寬的型別,再對其結果取反相。
- 若需使用
Good:
flag &= ~(0 ? flag : FLAG_X); // 確保 bit mask 的寬度不比 `flag` 窄
Bad:
if (flag & FLAG_X)
flag ^= FLAG_X;
- 避免不必要的
!
與~
的使用- 例外:
!!
等效於轉型爲bool
,故可使用
- 例外:
Good:
if (((bool)x == (bool)y) && ((bool)y == (bool)z))
Good:
if ((!!x == !!y) && (!!y == !!z))
Bad:
if ((!x != !!y) && (!y == !z))
- 值互相反相的邏輯或位元表達式在臨近之處出現時,其中一個應使用另一個的結果的反相的形式
Good:
if ((a || b) && c)
sth();
if (!(a || b) && d)
sth_else();
Bad:
if ((a || b) && c)
sth();
if (!a && !b && d)
sth_else();
- 常用的整數除法與取餘運算的參考形式:
除法定義 | 取商 | 餘數範圍 | 取餘 |
---|---|---|---|
floored (toward -∞) |
floor(x / (double)y) x / y - (x % y && (x < 0) != (y < 0)) (y > 0) → x / y - (x % y < 0) (x ≥ 0, y > 0) → (= truncated) (y = 2ⁿ) → x >> n (x − y ∈ [−MIN − 1, MAX]) → (x - (((x < 0) != (y < 0)) ? ((y < 0) ? -1 : 1) * (abs(y) - 1) : 0)) / y
|
(y > 0) → [0, |y|) (y < 0) → (−|y|, 0] |
x % y + ((x % y && (x < 0) != (y < 0)) ? y : 0) (y > 0) → x % y + ((x % y < 0) ? y : 0) (x ≥ 0, y > 0) → (= truncated) (y = 2ⁿ) → x & (y - 1)
|
truncated floored toward 0 |
trunc(x / (double)y) x / y (y = 2ⁿ) → (x >> n) + (x < 0 && (x & (y - 1U)))
|
(x ≥ 0) → [0, |y|) (x ≤ 0) → (−|y|, 0] |
x % y (y = 2ⁿ) → (x & (y - 1)) - ((x < 0 && (x & (y - 1U))) ? y : 0)
|
Euclidean |
copysign(1, y) * floor(x / fabs((double)y)) x / y - ((x % y < 0) ? (y < 0) ? -1 : 1 : 0) (y > 0), (y = 2ⁿ) → (= floored) (x ≥ 0, y > 0) → (= truncated) (x − |y| ∈ [MIN − 1, MAX]) → (x - ((x < 0) ? abs(y) - 1 : 0)) / y
|
[0, |y|) |
x % y + ((x % y < 0) ? abs(y) : 0) (y > 0), (y = 2ⁿ) → (= floored) (x ≥ 0, y > 0) → (= truncated) |
ceiling (away from 0) |
copysign(ceil(fabs(x / (double)y)), x * copysign(1, y)) x / y + ((x % y) ? ((x < 0) != (y < 0)) ? -1 : 1 : 0) (y > 0) → x / y + ((x % y) ? (x < 0) ? -1 : 1 : 0) (x ≥ 0, y > 0) → (= ceiling toward +∞) (y = 2ⁿ) → (x >> n) + (x > 0 && (x & (y - 1U))) (x ± |y| ∈ [MIN − 1, MAX + 1]) → (x + ((x < 0) ? -1 : 1) * (abs(y) - 1)) / y
|
(x ≥ 0) → (−|y|, 0] (x ≤ 0) → [0, |y|) |
x % y - ((x % y) ? (x < 0) ? -abs(y) : abs(y) : 0) (x ≥ 0, y > 0) → (= ceiling toward +∞) (y = 2ⁿ) → (x & (y - 1)) - ((x > 0 && (x & (y - 1U))) ? y : 0)
|
ceiling (toward +∞) |
ceil(x / (double)y) x / y + (x % y && (x < 0) == (y < 0)) (y > 0) → x / y + (x % y > 0) (y = 2ⁿ) → (x >> n) + !!(x & (y - 1U)) (x + y ∈ [MIN − 1, MAX + 1]) → (x + (((x < 0) == (y < 0)) ? ((y < 0) ? -1 : 1) * (abs(y) - 1) : 0)) / y
|
(y > 0) → (−|y|, 0] (y < 0) → [0, |y|) |
x % y - ((x % y && (x < 0) == (y < 0)) ? y : 0) (y > 0) → x % y - ((x % y > 0) ? y : 0) (y = 2ⁿ) → (x & (y - 1)) - ((x & (y - 1U)) ? y : 0)
|
rounded (.5 toward -∞) |
round(x / (double)y) - (fmod(x / (double)y, 1) == 0.5) x / y + ((abs(x % y) > (((unsigned)abs(y) - ((x < 0) != (y < 0))) >> 1)) ? ((x < 0) != (y < 0)) ? -1 : 1 : 0) (x ≥ 0, y > 0) → (= rounded (.5 toward 0)) (y = 2ⁿ) → (x >> n) + ((x & ((unsigned)y >> 1)) && x % ((unsigned)y >> 1)) (x + |y| / 2.0 ≤ MAX) → (x + ((x < 0) ? -1 : 1) * ((abs(y) - ((x < 0) == (y < 0))) >> 1)) / y
|
(y > 0) → (−|y| / 2.0, |y| / 2.0] (y < 0) → [−|y| / 2.0, |y| / 2.0) |
x % y - ((abs(x % y) > (((unsigned)abs(y) - ((x < 0) != (y < 0))) >> 1)) ? (x < 0) ? -abs(y) : abs(y) : 0) (x ≥ 0, y > 0) → (= rounded (.5 toward 0)) (y = 2ⁿ) → (x & (y - 1)) - (((x & ((unsigned)y >> 1)) && x % ((unsigned)y >> 1)) ? y : 0)
|
rounded (.5 toward 0) |
copysign(round(fabs(x / (double)y)) - (fmod(fabs(x / (double)y), 1) == 0.5), x * copysign(1, y)) x / y + ((abs(x % y) > ((unsigned)abs(y) >> 1)) ? ((x < 0) != (y < 0)) ? -1 : 1 : 0) (x ≥ 0, y > 0) → x / y + (x % y > (y >> 1)) (y = 2ⁿ) → (x >> n) + ((x & ((unsigned)y >> 1)) && (x < 0 || x % ((unsigned)y >> 1))) (x + |y| / 2.0 ≤ MAX + 1) → (x + ((x < 0) ? -1 : 1) * ((abs(y) - 1) >> 1)) / y
|
(x ≥ 0) → (−|y| / 2.0, |y| / 2.0] (x ≤ 0) → [−|y| / 2.0, |y| / 2.0) |
x % y - ((abs(x % y) > ((unsigned)abs(y) >> 1)) ? (x < 0) ? -abs(y) : abs(y) : 0) (x ≥ 0, y > 0) → x % y - ((x % y > (y >> 1)) ? y : 0) (y = 2ⁿ) → (x & (y - 1)) - (((x & ((unsigned)y >> 1)) && (x < 0 || x % ((unsigned)y >> 1))) ? y : 0)
|
rounded (.5 to even) |
round(x / (double)y) - (fmod(x / (double)y, 2) == 0.5) + (fmod(x / (double)y, 2) == -0.5) x / y + ((abs(x % y) > (((unsigned)abs(y) - (x / y) % 2U) >> 1)) ? ((x < 0) != (y < 0)) ? -1 : 1 : 0) (x ≥ 0, y > 0) → x / y + (x % y > ((y - (x / y) % 2U) >> 1)) (y = 2ⁿ) → (x >> n) + ((x & ((unsigned)y >> 1)) && ((x >> n) % 2U || x % ((unsigned)y >> 1))) (x + |y| / 2.0 ≤ MAX) → (x + ((x < 0) ? -1 : 1) * ((abs(y) - !((x / y) % 2U)) >> 1)) / y
|
(q ≡ 0 (mod 2)) → [−|y| / 2.0, |y| / 2.0] else → (−|y| / 2.0, |y| / 2.0) |
x % y - ((abs(x % y) > (((unsigned)abs(y) - (x / y) % 2U) >> 1)) ? (x < 0) ? -abs(y) : abs(y) : 0) (x ≥ 0, y > 0) → x % y - ((x % y > ((y - (x / y) % 2U) >> 1)) ? y : 0) (y = 2ⁿ) → (x & (y - 1)) - (((x & ((unsigned)y >> 1)) && ((x >> n) % 2U || x % ((unsigned)y >> 1))) ? y : 0)
|
rounded (.5 away from 0) |
round(x / (double)y) x / y + ((abs(x % y) > (((unsigned)abs(y) - 1) >> 1)) ? ((x < 0) != (y < 0)) ? -1 : 1 : 0) (x ≥ 0, y > 0) → x / y + (x % y > ((y - 1) >> 1)) (y = 2ⁿ) → (x >> n) + ((x & ((unsigned)y >> 1)) && (x > 0 || x % ((unsigned)y >> 1))) (x + |y| / 2.0 ≤ MAX) → (x + ((x < 0) ? -1 : 1) * (abs(y) >> 1)) / y
|
(x ≥ 0) → [−|y| / 2.0, |y| / 2.0) (x ≤ 0) → (−|y| / 2.0, |y| / 2.0] |
x % y - ((abs(x % y) > (((unsigned)abs(y) - 1) >> 1)) ? (x < 0) ? -abs(y) : abs(y) : 0) (x ≥ 0, y > 0) → x % y - ((x % y > ((y - 1) >> 1)) ? y : 0) (y = 2ⁿ) → (x & (y - 1)) - (((x & ((unsigned)y >> 1)) && (x > 0 || x % ((unsigned)y >> 1))) ? y : 0)
|
rounded (.5 toward +∞) |
round(x / (double)y) + (fmod(x / (double)y, 1) == -0.5) x / y + ((abs(x % y) > (((unsigned)abs(y) - ((x < 0) == (y < 0))) >> 1)) ? ((x < 0) != (y < 0)) ? -1 : 1 : 0) (x ≥ 0, y > 0) → (= rounded (.5 away from 0)) (y = 2ⁿ) → (x >> n) + !!(x & ((unsigned)y >> 1)) (x + |y| / 2.0 ≤ MAX) → (x + ((x < 0) ? -1 : 1) * ((abs(y) - ((x < 0) != (y < 0))) >> 1)) / y
|
(y > 0) → [−|y| / 2.0, |y| / 2.0) (y < 0) → (−|y| / 2.0, |y| / 2.0] |
x % y - ((abs(x % y) > (((unsigned)abs(y) - ((x < 0) == (y < 0))) >> 1)) ? (x < 0) ? -abs(y) : abs(y) : 0) (x ≥ 0, y > 0) → (= rounded (.5 away from 0)) (y = 2ⁿ) → (x & (y - 1)) - ((x & ((unsigned)y >> 1)) ? y : 0)
|
(其它等式) | (x − r ∈ [−MIN, MAX]) → (x - r) / y
|
q * y + r == x |
x - q * y |
-
- MIN 與 MAX 為運算元之值域;假設
unsigned
為運算元的型別所對應的無號型別。 - 實際使用時應依已知除數範圍簡化。
- 若除數 y 為 2 的 n 次方,且 n 為非負整數,則 y 的 integer floored division 與 modulo 應分別用
x >> n
與x % (unsigned)y
(或x & ((1U << n) - 1)
)實作-
lhs >> rhs
的lhs
為負時,依據 C99 及 C++11 標準會產生 implementation-defined 的結果;依據 C++20 標準則會產生 arithmetic right shift 的結果(同 2 的幂次的 integer floored division)
-
- MIN 與 MAX 為運算元之值域;假設
Good:
int y = get_value();
int q = y >> 5;
unsigned int r = y % 32U; // or `y & ((1U << 5) - 1)`
Bad:
int y = get_value();
int q = y / 32 - (y % 32 && y < 0);
int r = x % 32 + ((x % 32 < 0) ? 32 : 0);
- 禁止連續使用前綴運算子
++
、--
、+
、-
、~
、或!
、以及後綴運算子++
或--
- 例外:
!!
等效於將運算元轉型爲bool
,故可使用,惟此時不應再使用前綴運算子!
- 連用兩次的前綴運算子
-
或~
等效於單個前綴運算子+
- 在 C++ 中,對某 glvalue
x
連用n
次的前綴運算子++
或--
等效於x += n
或x -= n
;在 C 中僅可使用x += n
或x -= n
- 在 C 與 C++ 中,內建的後綴運算子
++
與--
的結果爲非 (g)lvalue,無法連續使用 - 在 C++ 中,因存在運算子多載,前綴運算子
++
與--
的結果可能爲 glvalue,惟此時仍適用此規則,不應連續使用
- 例外:
Good:
i += 2;
!i++;
Bad:
++ ++i;
!~~+ +- -i++;
- 禁止對內建的以下運算子的運算結果使用前綴運算子
+
:前綴運算子+
、-
、~
、與!
、後綴運算子++
與--
、以及二元運算子*
、/
、%
、+
、-
、>>
、<<
、&
、^
、與|
- 由於這些運算子的運算結果會被提昇爲至少與
int
同寬,因此前綴運算子+
對其無作用 - 前綴運算子
++
與--
以及 (複合) 賦值運算子的運算結果,在 C 中亦會被提昇爲至少與int
同寬,但在 C++ 中則不會,因此仍可直接對其結果使用前綴運算子+
- 由於這些運算子的運算結果會被提昇爲至少與
- 應利用
continue
、break
、return
、或goto
減少 block 深度 - 在函式定義中,無限迴圈內不應包含無限迴圈
- 迴圈應為以下形式之一
- 無限迴圈
for (;;)
- 條件迴圈
-
while (cond())
:不暫存結果- 可為普通
whlie
迴圈或do
-while
迴圈
- 可為普通
-
for (Type v; (v = get_val());)
:暫存結果,並進行非零判斷 -
for (Type v; v = get_val(), cond(v);)
:暫存結果,並進行其它判斷
-
- 範圍迴圈
- 遞增型
-
for (int i = get_start(); i < n; ++i)
:不暫存終點 -
for (int i = get_start(), n = get_end(); i < n; ++i)
:暫存終點
-
- 遞減型
-
for (int i = get_end(); i-- > 0;)
:不暫存終點 -
for (int i = get_end(), b = get_begin(); i-- > b;)
:暫存終點
-
- 遞增型
- 指標型 for-each 迴圈
- 遞增型
for (Type *p = get_start(), *const n = get_end(); p != n; ++p)
- 遞減型
for (Type *p = get_end(), *const b = get_start(); p-- != b;)
- 遞增型
- 若需在迴圈之後使用迴圈變數的值,方可將迴圈的初始化語句搬出至迴圈之前
- 如使用
goto
可避免暫時變數的使用或是程式碼的重複,且難以透過使用迴圈或呼叫函式達成,應使用goto
-
goto
的目標應在goto
語句之後,除非要從無限迴圈跳出並重新進入 - 從
goto
語句前往目標的途中須僅允許跳出及跳過 blocks,以及最終跳入一層無區域變數的 block 的開頭。- 若難以改寫為上述形式,可嘗試改用迴圈或函式呼叫,或是將
goto
置入迴圈
- 若難以改寫為上述形式,可嘗試改用迴圈或函式呼叫,或是將
- 不要定義實作過於複雜的 macro 來處理容易解決的 C 語法問題
- 例如:不要用 macro 生成
malloc
回傳指標的轉型(parse 實作過於複雜),而應直接手寫轉型
- 例如:不要用 macro 生成
- 如果定義了較為複雜的 macro,應該使用註解解釋背後邏輯
- 參考
include/cppdef.h
- 參考
- 不應為了過舊的編譯環境或編譯器而將程式邏輯複雜化
- 目前 (2022-03-19) 主要考量的編譯環境為 Linux;
考量的 Linux 版本最舊為 4.18,glibc 版本最舊為 2.28 - 目前 (2022-03-19) 所考量的編譯器,Clang 版本最舊為 13,GCC 版本最舊為 11
- 目前 (2022-03-19) 主要考量的編譯環境為 Linux;
- 定義 function-like macro 時,參數出現時,應被圓括號
()
及逗號,
緊包圍, 如:(_x)
、(_x,
、, _x,
、與, _x)
- 如展開後可能產生未被圓括號包圍的逗號,則此參數須被圓括號
()
緊包圍, 如:(_x)
- 如展開後可能產生未被圓括號包圍的逗號,則此參數須被圓括號
- 如上包圍參數後,應改寫定義為以下形式之一
- 類常數定義 –– 只含有 macro 參數、算數型別常數、與無副作用的表達式的類表達式定義;
若預期任一參數會接受非算數型別或非常數的引數,則應使用下述的非類常數的類表達式定義
-
expr
,若看似不直接包含運算子或僅直接包含後綴運算子 -
(expr)
,若否 - 注意:
-
expr
可為其他類常數定義 macro。 - 包含運算子
,
的表達式,在 ISO C 中無法被用作常數, 且函式呼叫運算子(後綴運算子()
)應被視作有副作用(用以呼叫其它類常數定義 macro 時除外)。 故符合以上任一情況的 macro 定義為非類常數定義。 - 數字的前綴
+
/-
符號為前綴運算子,故含有這些符號的數字無法使用上述的expr
形式, 而必須使用(expr)
之形式。
-
-
- 非類常數的類表達式定義 –– 預期使用其計算結果;
若定義中含有非參數的非全域變數,即使其不需參數,也應被定義為 function-like macro
-
((void)0)
,若表達式為空 -
expr
,若未被圓括號()
包圍,且看似不直接包含運算子或僅直接包含後綴運算子 -
(exprs)
,若否,但包含任何void
表達式 -
((void)expr_first, exprs)
,若expr_first
看似不直接包含運算子或僅直接包含前綴與後綴運算子 -
((void)0, exprs)
,若否 - 注意:
- 若在任一非結尾的表達式中,具有最低優先結合順序的運算子不帶副作用,
應將此表達式轉型為
void
並視其為void
表達式。 (否則可能會引發編譯時期警告。) - 每一表達式可為其它類表達式定義 macro。
- 請將單獨出現的 expression block(GCC extension)或 C++ 匿名函式視為不直接包含運算子。
- 若某 macro
F
的定義為expr++
/expr--
, 其中的++
/--
必會被剖析為遞增/遞減後綴運算子, 即使遇到F 1
的非預期用法也是如此;此非預期用法會由於不符語法而被避免。 因此在上述的expr
形式中,允許直接包含後綴運算子++
與--
。 - 包含至少一個
void
表達式是為了使func_a MACROB
的用法不符語意而被避免, 且可彰顯其並非類常數定義 macro。 - 雖然
(Type) (exprs)
之形式更為理想, 但這需要事先得知exprs
的型別,實行上會造成不便,因此不採用。
- 若在任一非結尾的表達式中,具有最低優先結合順序的運算子不帶副作用,
應將此表達式轉型為
-
- 可改寫為一連串表達式的類陳述式定義 –– 預期忽略其計算結果;
應改寫為表達式,且即使其不需參數,也應被定義為 function-like macro
-
(void) ((void)0)
,若陳述式為空 -
(void) (exprs)
,若重寫後的一連串表達式以void
表達式結尾 -
(void) (exprs, (void)expr_last)
,若expr_last
看似不直接包含運算子或僅直接包含前綴與後綴運算子 -
(void) (exprs, (void)0)
,若否 - 注意:
- 若在任一表達式中,具有最低優先結合順序的運算子不帶副作用,
應將此表達式轉型為
void
並視其為void
表達式。 - 開頭的
(void)
是為了使MACROA MACROB
的用法不符語法而被避免, 且可彰顯其為某陳述式之替代。 - 結尾的
(void)
可避免此 macro 的呼叫式被用作後綴運算子的運算元。
- 若在任一表達式中,具有最低優先結合順序的運算子不帶副作用,
應將此表達式轉型為
-
- 無法改寫為表達式的類陳述式定義;
即使其不需參數,也應被定義為 function-like macro
-
statement
,若為單一陳述式,且其前後緊接任何運算子或運算元的話,會造成用法不符語法- 不應包含陳述式結尾的
;
- 不應包含陳述式結尾的
-
do { statements } while (0)
,若否
-
- 其它定義(非類陳述式定義) –– 無須改寫
- 目前 (2022-03-19) 考量的編譯環境的系統為 32-bit 及 64-bit x86 架構
- 在會被讀出/寫入 binary file 的資料結構中,不應使用
long
,time_t
, 以及其它會因編譯環境架構而有不同大小的資料型別- 參見
include/struct.h
- 目前 (2022-03-19) 已無在相關資料結構中使用這些資料型別
- 參見
- 在會被讀出/寫入 binary file 或是 shared memory 的資料結構中,不應使用指標型別
- 目前 (2022-03-19) 已無在相關資料結構中使用指標型別
- 應當使用下列形式的註解以標註會被讀出/寫入硬碟或 shared memory 的資料結構
<STORAGE_TYPE>(<formatting_type>); <dependency_type>
-
STORAGE_TYPE
可為下列之一:-
DISKDATA
:會直接或間接地讀出/寫入硬碟的資料;標註上比SHMDATA
優先 -
SHMDATA
:會直接或間接地讀出/寫入 shared memory 的資料
-
-
formatting_type
可為下列之一:-
raw
:Binary data 形式 -
format
:已格式化之文字形式
-
-
dependency_type
可為下列之一:-
dependency(<Type>)
:由於此資料結構被包含於Type
,而被間接地讀出/寫入硬碟或 shared memory 的資料結構 -
runtime
:僅於程式執行時期需要使用,而結束執行後可捨棄的資料
-
- 至 2020-02-24 為止,所有符合
DISKDATA(raw)
的資料結構都已被標註
- 應假設執行環境為即時作業系統中的多執行緒環境
- 操作檔案、共用記憶體、等等被多個執行緒與處理程序所共用的物件(下稱「共用物件」)時,應避免 race condition
- 建立或刪除共用物件時,應先直接嘗試進行操作,再透過例外處理檢查是否符合操作條件
- 存取共用物件時,應使用 atomic 操作或使用 lock 機制
- 若需一次進行多個不彼此獨立的操作,或是無對應的 atomic 操作可用時,應使用 lock 機制
- 若寫入期間需進行耗時操作,可先持著 read lock 複製一份資料,對資料副本更新,再持著 write lock 更新原資料; 或改用 read-copy-update 機制
- 若為檔案,應使用
lib/file.c
所提供之f_exlock()
與f_unlock()
函式
- 否則,應使用 atomic 操作
- POSIX.1/2008 標準的
read()
與write()
為 atomic,故僅需進行單個此類操作的話,不需要 lock 機制 - ISO C/ISO C++ 的內建遞增、遞減、與複合賦值運算子並非 atomic,不應直接使用,而須搭配 atomic 型別:
- 包含
++
、--
、&=
、|=
、^=
、>>=
、<<=
、+=
、-=
、*=
、/=
、%=
、等等
- 包含
- 操作 atomic 型別的物件時,應使用專用函式,以避免誤用非 atomic 操作
- POSIX.1/2008 標準的
- 若需一次進行多個不彼此獨立的操作,或是無對應的 atomic 操作可用時,應使用 lock 機制
- 開啟某共用物件後,不彼此獨立的操作應使用同一個 handler,不應重新取得 handler
- 若為檔案,handler 為 file descriptor 或 file pointer,直接使用檔案路徑則視為重新取得 handler
- 不同支程式使用的 header 應該分開,以方便控制特定程式的編譯環境
Good:
a.h
:
#include "lib.h"
void do_sth(void);
b.h
:
#include "lib.h"
lib.h
:
void do_lib(void);
a.c
:
#include "a.h"
void do_sth(void) { }
int main(int argc, char *argv[]) { }
b.c
:
#include "b.h"
static void do_sth(void) { }
int main(int argc, char *argv[]) { }
lib.c
:
void do_lib(void) { }
Bad:
main.h
:
void do_lib(void);
#ifdef A_C
void do_sth(void);
#endif
a.c
:
#define A_C
#include "main.h"
void do_sth(void) { }
int main(int argc, char *argv[]) { }
b.c
:
#define B_C
#include "main.h"
static void do_sth(void) { }
int main(int argc, char *argv[]) { }
lib.c
: 同前
- 避免在原始碼中自行宣告函式;統一使用
#include
- 自行宣告容易有型別錯誤,而且用 C++ 編譯時沒有統一
extern "C"
的使用時容易發生 linker errors
- 自行宣告容易有型別錯誤,而且用 C++ 編譯時沒有統一
- 決定一段宣告所屬的 header 時,先依循「用途」,再依循「語法類型」
- 泛用的宣告才可僅依循「語法類型」決定其所屬的 header
- 應先了解函式各個參數以及回傳值的意義,再使用該函式,以避免誤用而造成邏輯錯誤
- 使用功能相似的 library/system 函式的考量重點
- 除了以效能或安全為重點的情況下,如果在 BBS 中已經實作了所需要的功能的函式,就使用它
- 參見
lib/*.c
- 參見
- 原則上,以在編譯環境中最可能存在的函式為優先
- 優先度高到低:C standard library 函式 > GCC built-in 函式 > glibc 專有函式 = POSIX 系統函式 > *NIX 系統函式 > 外部 library 函式
- 在一般情況下,如果使用某兩個函式寫出的程式碼差不多一樣複雜,使用優先度高的函式;
否則,使用讓程式碼較簡潔的函式;
但如果編譯環境可能缺少該函式,就依序使用其它優先度高的函式作為後備
- 除了以效能或安全為重點的情況下,如果在 BBS 中已經實作了所需要的功能的函式,就使用它
Good:
int diff = strncasecmp(str1, str2, LENGTH);
-
strncasecmp()
在 glibc 2.5 前就已存在,可假設編譯環境有此函式
Bad:
int diff = 0;
const char *ptr1 = str1;
const char *ptr2 = str2;
int len = LENGTH;
while (len--) {
char ch1 = *ptr1;
char ch2 = *ptr2;
if (ch1 >= 'A' && ch1 <= 'Z')
ch1 += 'a' - 'A';
if (ch2 >= 'A' && ch2 <= 'Z')
ch2 += 'a' - 'A';
diff = ch1 - ch2;
if (diff || !*ptr1 || !*ptr2)
break;
++ptr1;
++ptr2;
}
- 冗長
Worse:
int diff;
char buf1[LENGTH+1], buf2[LENGTH+1];
strncpy(buf1, str1, LENGTH);
buf1[LENGTH] = '\0';
strncpy(buf2, str2, LENGTH);
buf2[LENGTH] = '\0';
for (char *ptr = buf1; *ptr; ptr++)
*ptr = tolower(*ptr);
for (char *ptr = buf2; *ptr; ptr++)
*ptr = tolower(*ptr);
diff = strncmp(buf1, buf2, LENGTH);
- 又冗長又浪費記憶體,而且不使用 variable length array (C99 或 GNUC++ 之功能) 的話,字串長度會有限制
-
- 在以效能或安全為重點的情況下,優先使用效能或安全較好的函式;
但如果編譯環境可能缺少該函式,就依序使用其它效能或安全較好的函式作為後備,
最後應使用在各個編譯環境中都能夠確定存在的函式作為最終後備 - 如果選擇了多個函式,而所寫出的程式碼會被重複利用,應將該段程式碼獨立定義成函式
- 在以效能或安全為重點的情況下,優先使用效能或安全較好的函式;
- 不應將外部 libraries 放進 BBS 程式碼中,而應該以 git submodule + symbolic link 的方式引用
- 原則上,不維護不是由自己維護的程式碼
- 盡量保持整個專案結構的扁平;限制 Makefile 的層次在 3 層以下
- 目前 (2022-03-19) 整個 DreamBBS 專案只有
scripts/wsproxy/
一個內層目錄,但沒有自己的 Makefile
- 目前 (2022-03-19) 整個 DreamBBS 專案只有
- Home
- Install — 安裝說明
- Version
- Project Documentations — 專案說明文件
- Coding Style & Conventions — 程式碼撰寫風格與慣例
- Indentation
- Xover List System — Xover 列表系統
- Menu Systems — 選單系統
- Screen Coordinate System — 畫面座標系統
- BoardReadingHistory — BRH 看板閱讀紀錄系統
- Visio I/O Library — Visio 輸出入函式庫
- Permission System — 權限系統
- TANet BBS Family Genealogy Chart — TANet BBS 家族譜系圖
- 與 MapleBBS 3 的按鍵差異
- [WIP] 與 MapleBBS 3 的差異
- References — 參考資料
- Changelog & TODO
- Issue & TODO list — 問題與代辦事項清單
- MapleBBS-itoc Porting Project — MapleBBS-itoc 移植計畫
- BBS-Lua Changelog
- BBS-Ruby Changelog (external link — 外部鏈結)
- 新式密碼加密 (DLBBS v2.0+)
- [WIP] DreamBBS v3 發佈說明 Release Note
- Release Notes of Version 2.0.0 Artoria
- Version 2.0.0 Artoria 發行說明
- Release Notes of Version 1.0.0 Rimuru
- Version 1.0.0 Rimuru 發行說明
- NoCeM-innbbsd 原始說明文件
- WindTop 3.02 原始說明文件