【C編程】The Power of 10: Rules for Developing Safety-Critical Code

引言:這些天閱讀C代碼,作C編程,受盡某些折磨。當我看到這個C編程10項原則的演講時,頓生被解放感。

本文轉載編程10項原則,目標是程序寫得好讀、好分析、好測試、好擴展:The Power of 10,原作者Gerard J. Holzmann先生[1],介紹者:Low Level博主[2]

我聽說對開發者來說,最糟糕的事情是自己寫的代碼在運行過程中崩潰了。可如果代碼的運行地點是在天空呢?一個空指針或者free一個已經釋放的存儲塊可能讓一顆衛星朝Ether疾馳、完全無法控制。為此,NASA要求代碼容易靜態分析(好懂)。Gerard J. Holzmann提出The Power of 10,他承認是有點嚴格,但想想程序運行場景要求絕對安全,這點妥協是值得的。

1 Simple control flow

即不使用goto、setjmp、longjmp語句,不使用遞歸(recursion)。遞歸程序行成循環的控制流,很容易崩潰,尤其是嵌入式系統。

(我們知道,遞歸funciton都可以轉化為普通function。)

換句話說,control flow只有loop和條件語句。

2 Limit all loops

NASA gives all of its Loops a fixed upper bound.

例如搜索list查找關鍵詞的程序,

int list_search(list_t *e, int search){
	int i=0;
	while (e) {
		if(e->val==search)
			return e;
		e=e->fd;
	}
	return null;
}

改成

int list_search(list_t *e, int search){
	int i=0;
	while (e&& i++<MAX_ITER) {
		if(e->val==search)
			return e;
		e=e->fd;
	}
	return null;
}

3 NO dynamic memory allocation

即不使用malloc, calloc, realloc, free這些動態分配內存、回收內存的方法。

這樣就沒有內存泄漏、內存管理堆棧溢出、耗乾,free已經free的內存導致程序崩潰。這樣C語言最大的困難、困惑、缺陷就不存在了。

請看一個程序:
Screen Shot 2025-10-24 at 12.59.29 AM

再看:

typedef struct{
	int in_use;
	char data[DATA_SIZE];
}msg_t;

void msg_task(){
	msg_t msg_buf[NUM_MSG]={0};
	msg_process(msg_buf);
	...
	return;
}

(當領導說,只能用、必須用後一種程序,禁止前一種程序。幸福吧?)
Memory use is deterministic.

4 Limit function size

Function 最多60行,或者不超過一頁。

A function should do one thing.

否則就不是good structure,well thought。

Function behavior is obvious and can be easily wrapped unit test. (unit test我好像第一次聽到這個測試術語。)

5 Limit assert

assert用於檢查變量是否滿足條件,例如,assert("dividend!=0");如果不滿足,程序報錯、停止,這麼做有什麼必要?隱含了程序的exit,control flow分岔,違反“1 Simple control flow”原則。

(Java默認不支持assert。我編程至今未用過這個功能。請不要用assert。)

6 Variables at the smallest scope

(變量有scope,全局變量、局部變量,進function出function,相同名稱的變量值變化了沒有?是不是很囉嗦的?原則是scope盡量小,變量不許重名,變量名稱和功能要一致。

變量定義要確定,變量datatype要清晰。function參數不要用省略號,這是va_list,參數個數不定。比如,void msg(char c,...);我第一次讀到這樣的代碼的時候,真不敢相信自己的眼睛。)

7 Check return value of funtions

如果返回值不重要,如printf,則explicitly註明(void)printf("blabla");,換句話說,function返回值全部要處理。

(但我感覺檢查function return value很難,我就沒有這個習慣,查一個function的return value用man 命令,man=manual,例如man scanf,但scanf return value部分我讀了一遍,感覺仍不太清楚。這個man文檔是能自己修改的嗎?就寫0 blabla,NULL blabla,像前面的參數那麼條分縷析才行。)

8 Limit the preprocessor

preprocessor 載入頭文件,刪除comment。通常文件會變很大。

我的理解是2件事:
a) Conditional compilation
Having flags that change the code at compile time.

例如:

#ifdef __x86_64__
	#ifdef FEATURE_1
		// conditional target 1
	#end
#elif __ARM__
	#ifdef FEATURE_2
		// conditional target 2
	#end
#ifdef

如果有10個flag,就有\(2^{10}\)個test cases,這是很難測試的。

少用preprocessor,我想指的是除了操作系統提供的通用的耳熟能詳的頭文件(比如stdio.h),不要自己編寫頭文件,因為不好讀代碼,function的定義也找不到,可能structure定義也找不到。

b)Include guard
如果實在要自己編寫頭文件.h file,寫的時候需要定義ID,比如calc.h,那麼:

#ifndef CALC_H
#define CALC_H
/*所有代碼寫在這裡*/
#endif

The #ifndef, #define, and #endif lines are called an include guard.

They prevent the file from being included more than once by mistake, which can cause errors during compilation.

This is a common and recommended practice in all C header files.

如果include 同一個頭文件兩遍,運行會崩潰的。

9 Restrict pointer use

禁用function pointer。其他pointer只能一層。(否則就是捉弄code review的人。)

10 Be pedantic

pedantic的意思是:过分注重学术形式而忽视实质的。

編譯的時候不要放過所有warning,高亮所有可疑之處。比如cc -Wall -Werror -Wpedantic HelloWorld.c

(我試了,說發生一個error,最後一行沒有new line,我看了半天什麼叫做沒有new line?最後一行}後我加了回車,編譯通過了。)

參考:


  1. Gerard J. Holzmann, The Power of 10: Rules for Developing Safety-Critical Code, NASA/JPL Laboratory for Reliable Software, June 2006, http://web.eecs.umich.edu/~imarkov/10rules.pdf ↩︎

  2. Low Level, ``how NASA writes space-proof code'', 2023-Jun-4, https://www.youtube.com/watch?v=GWYhtksrmhE ↩︎

posted @ 2025-10-24 02:11  Dersu  阅读(7)  评论(0)    收藏  举报