MISRA C:2012 標準中文詳解
前言
在汽車工業、航空航天、醫療設備等高安全性和高可靠性要求的嵌入式系統領域,軟件的質量至關重要。代碼中的細微缺陷都可能導致嚴重的后果。為了解決嵌入式 C 語言開發中常見的安全性和可靠性問題,汽車產業軟件可靠性協會 (MISRA) 發布了 MISRA C 編碼標準。
1. MISRA C 概述
MISRA C (Motor Industry Software Reliability Association C) 是汽車工業 C 編碼標準的縮寫,由 MISRA 協會發布。其目標是為嵌入式系統中的 C 語言開發提供一套嚴格的編碼規范,旨在:
- 提升代碼可靠性 (Reliability): 減少因編碼錯誤導致的程序缺陷,提高系統運行的穩定性。
- 提升代碼可讀性 (Readability): 統一代碼風格,使代碼更易于理解和維護,降低維護成本。
- 提升代碼可移植性 (Portability): 減少對特定編譯器或硬件平臺的依賴,增強代碼在不同環境下的適應性。
- 提升代碼可維護性 (Maintainability): 規范的代碼結構和風格,降低代碼維護和升級的難度。
- 提升代碼安全性 (Safety): 避免潛在的安全漏洞,保障系統運行的安全。
MISRA C:2012 是該標準的第三版,于 2012 年發布,是對之前版本的重要升級和完善。它整合了之前的 AMD1、TC1 修訂內容,并持續進行更新以適應新的技術發展和安全挑戰。雖然 MISRA C 標準并不能 100% 保證程序零缺陷,但它能顯著降低因編程錯誤引入問題的風險,是提升嵌入式系統軟件質量的有效手段。
2. MISRA C:2012 的規則體系
MISRA C:2012 標準并非簡單的代碼風格指南,而是一套嚴謹的規則體系,它包含 指令 (Directives) 和 規則 (Rules) 兩大類。
2.1 指令 (Directives)
指令 (Directives) 主要關注代碼的組織、構建和文檔等方面,旨在建立良好的開發流程和代碼管理規范。MISRA C:2012 標準中共包含 16 條指令,下面將逐一介紹:
-
Dir 1.1 實現定義行為文檔化:
- 要求: 應該用文檔記錄并了解程序輸出依賴的任何實現定義行為。
- 解釋: C 語言標準中有一些行為是“實現定義”的 (implementation-defined),這意味著這些行為的具體實現由編譯器或平臺決定。不同的編譯器或平臺可能會對這些行為有不同的實現,導致代碼的行為在不同環境下不一致。為了確保代碼的可移植性和可預測性,應該將程序依賴的任何實現定義行為記錄在文檔中,并確保開發團隊充分理解這些行為。
- 示例:
- 整數類型的大小 (int, long 等) 在不同平臺上可能是不同的。
- 有符號整數的右移操作可能是算術右移 (保留符號位) 或邏輯右移 (不保留符號位)。
- 結構體成員的對齊方式可能因編譯器和平臺而異。
-
Dir 2.1 零編譯錯誤:
- 要求: 所有源文件必須沒有任何編譯錯誤。
- 解釋: 這是代碼質量最基本的要求。存在編譯錯誤的代碼無法生成可執行程序,更談不上功能和性能。在開發過程中,應該及時修復所有編譯錯誤,確保代碼始終處于可編譯狀態。
-
Dir 3.1 需求可追溯性:
- 要求: 所有代碼應該可以追溯至文件化的需求。
- 解釋: 可追溯性是指代碼和需求之間建立清晰的對應關系。良好的可追溯性可以幫助開發人員理解代碼的設計意圖,驗證代碼是否滿足需求,并在需求變更時快速定位受影響的代碼。
- 示例:
- 可以在代碼注釋中注明該代碼段對應的需求編號或需求描述。
- 可以使用需求管理工具來維護代碼和需求之間的映射關系。
-
Dir 4.1 運行時故障最小化:
- 要求: 運行時故障必須最小化。
- 解釋: 運行時故障是指程序在運行過程中發生的錯誤,例如空指針解引用、數組越界、除零錯誤等。這些故障會導致程序崩潰或產生不可預測的結果。在代碼設計階段就應考慮各種可能的錯誤情況,并采取措施預防運行時故障的發生,例如進行輸入驗證、邊界檢查、錯誤處理等。
-
Dir 4.2 所有匯編語言的使用應當用文檔記錄
- 要求: 建議所有匯編的使用應當用文檔記錄。
- 解釋: 雖然在某些情況下, 為了提高性能或訪問底層硬件, 可能需要使用匯編語言。 但是, 匯編語言的可讀性, 可維護性和可移植性都比較差。 因此,MISRA C建議詳細記錄匯編代碼的用途, 功能和接口, 以便其他開發人員理解和維護。
-
Dir 4.3 Assembly language shall be encapsulated and isolated
- 要求: 匯編語言必須封裝、隔離。
- 解釋: 為了減少匯編語言對代碼可維護性和可移植性的影響, 應該將匯編代碼封裝在獨立的函數或模塊中, 并與 C 代碼隔離。 這樣可以限制匯編代碼的作用范圍, 降低代碼的復雜性, 并方便后續的維護和移植。
-
Dir 4.4 Sections of code should not be 'commented out'
- 要求: 建議代碼部分不應當注釋掉。
- 解釋:
-
不合規代碼示例:
/* x = y + z; // 這段代碼暫時不需要 */ -
合規代碼示例:
#if 0 x = y + z; // 使用預處理指令 #endif
// ...或者/* ... */注釋代碼。而應該用#ifdef ...#endif等預編譯指令。 -
-
Dir 4.5 Identifiers in the same namespace with overlapping visibility should be typographically unambiguous
* 要求: 建議同一命名空間中, 具有重疊可見性的標識符, 必須在排版上毫不含糊。
* 解釋: 類似于C++名稱遮掩問題。例如, 全局變量不要與局部變量重名。
-
Dir 4.6 typedefs that indicate size and signedness should be used in place of the basic numerical types
- 要求: 建議指示大小, 符號的typedefs(類型定義), 應當用來替代基本的數字類型。
- 解釋: 例如, typedef定義的uint32_t, 用來替代32位無符號整型。
-
Dir 4.7 If a function returns error information, then that error information shall be tested
-
要求: 如果一個函數返回錯誤信息, 那么錯誤信息應答被測試。
-
解釋:許多庫函數和自定義函數會通過返回值來指示錯誤狀態。 調用這些函數后, 應該檢查返回值, 并進行相應的錯誤處理。 忽略錯誤信息可能導致程序在錯誤狀態下繼續運行, 產生不可預測的結果。
-
示例:
FILE *fp = fopen("myfile.txt", "r"); if (fp == NULL) { // 處理文件打開失敗的情況 perror("fopen failed"); return -1; } -
-
Dir 4.8 If a pointer to a structure or union is never dereferenced within a translation unit, then the implementation of the object should be hidden
要求:建議如果一個指向struct或union的指針, 在翻譯單元內從未被解引用, 則應該隱藏該對象的實現。
-
非合規代碼示例:
// A.h struct A { uint32_t id; uint8_t name[32]; }; //test.c #include <A.h> // 不必要 void foo(struct A *p){ //沒有解引用 } -
合規代碼示例:
// A.h struct A { uint32_t id; uint8_t name[32]; }; // test.c // 正確用法:前向聲明 struct A; void foo(struct A *p){ //沒有解引用 }
解釋: 如果結構體A實現如下,如果test.c中, 從未對指向A類型對象的指針進行解引用, 也就沒有訪問其成員。此時, 在test.c文件中應該使用前向聲明, 而不應該使用
"#include <A.h>"。 -
-
Dir 4.9 A function should be used in preference to a function-like macro where they are interchangeable
- 要求: 建議在函數、函數類的宏可以相互替換的地方, 應優先使用函數。
- 解釋: 因為函數會在編譯期做類型檢查, 更安全。
-
Dir 4.10 Precautions shall be taken in order to prevent the contents of a header file being included more than once
-
要求: 應當采取預防措施, 防止頭文件的內容被多次包含。
-
解釋: 通常, 使用
"#ifndef ... #endif"。也可以使用"#pragma once", 不過想要編譯器支持。 -
示例:
// my_header.h #ifndef MY_HEADER_H #define MY_HEADER_H // 頭文件內容 #endif -
-
Dir 4.11 The validity of values passed to library functions shall be checked
-
要求: 傳遞給庫函數的值的有效性, 應當被檢查。
-
解釋: 因為一些庫函數有嚴格的限制域, 需要檢查:
許多數學函數(<math.h>中的math函數), 如:
(1) 負數不允許傳遞給sqrt, log函數。
(2) fmod第二個參數不應為0。當非小寫字母的參數傳遞給函數toupper時(類似有tolower), 一些實現能產生非預期結果。
<ctype.h>中的字符測試函數, 在傳遞無效值時, 表現出未定義行為。如, isalnum, isalpha, islower等。
給abs函數傳遞最值負數時, 表現出未定義行為。最小負數值轉換成整數值, 由于位寬限制, 無法正確轉換。
-
示例:
#include <math.h> #include <stdio.h> int main() { double x = -1.0; if (x >= 0.0) { double result = sqrt(x); printf("sqrt(x) = %f\n", result); } else { printf("Error: Cannot compute square root of a negative number.\n"); } return 0; } -
-
Dir 4.12 Dynamic memory allocation shall not be used
- 要求: 不應該使用動態內存分配。
- 解釋: 例如, 不應該使用malloc/free進行動態內存分配。 動態內存分配(malloc, free等)可能導致內存泄漏, 碎片, 以及難以預測的程序行為。 在安全關鍵的嵌入式系統中, 應該避免使用動態內存分配。
-
Dir 4.13 Functions which are designed to provide operations on a resource should be called in an appropriate sequence
- 要求: 建議設計用來提供操作資源的函數, 應當以合適的序列進行調用。
- 解釋: 例如, 某個硬件模塊的操作, 應當遵循一定順序, 以符合硬件資源特性。 例如, 對于文件操作, 應該按照打開 -> 讀/寫 -> 關閉的順序進行。
2.2 規則 (Rules)
規則 (Rules) 則更側重于 C 語言編碼的具體細節,針對 C 語言的各種特性和潛在的陷阱,提出了詳細的編碼約束。
2.2.1 標準 C 環境 (Standard C environment)
-
Rule 1.1 (強制): 標準 C 語法和約束: 程序不得違反標準C語法和約束,不應超出實現的翻譯限制。
- 解釋: 程序必須只使用C語言特性及其庫, 除非使用語言擴展, 否則程序不應: 1) 包含任何違反本標準中描述的語言語法行為; 2) 包含任何違反本標準規定的限制行為。
- 示例:
- 語法行為:不支持寫const變量。
- 語言擴展:一些C90編譯器提供
__inline關鍵字聲明inline函數。許多編譯器支持使用一些關鍵字定義對象位置, 如__zpage,__near,__far。
-
Rule 1.2 (強制): 避免語言擴展
-
非合規代碼示例 (使用 GNU C 擴展):
// 使用 GNU C 擴展的語句表達式 int max(int a, int b) { return ({ int _a = a; int _b = b; _a > _b ? _a : _b; }); } // 使用 GNU C 擴展的零長度數組 struct my_struct { int data_len; char data[]; // 零長度數組 (非標準 C) }; // GNU C 擴展的 typeof int x = 10; typeof(x) y = 20; // y 的類型與 x 相同 (int) //GNU C 擴展, 指定初始化器 int arr[10] = { [2] = 10, [5] = 20 }; //GNU C擴展, case 范圍 switch (value) { case 1 ... 10: // 處理 1 到 10 之間的值 // ... break; // ... } -
合規代碼示例 (使用標準 C):
// 標準 C 代碼 int max(int a, int b) { return (a > b) ? a : b; } // 使用指針和動態內存分配代替零長度數組(MISRA C不允許動態分配, 這里僅做標準C演示) struct my_struct { int data_len; char *data; // 指針 }; // 標準 C 沒有 typeof,需要明確類型 int x = 10; int y = 20; //標準C 初始化 int arr[10] = {0, 0, 10, 0, 0, 20, 0, 0, 0, 0}; //標準C switch 語句 switch (value) { case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: // ... break; // ... } -
規則解釋及益處:
語言擴展雖然在某些情況下可以提供便利或提高性能,但它們通常是特定于編譯器的,會導致代碼的可移植性降低。如果將代碼移植到不支持這些擴展的編譯器或平臺,就需要進行大量的修改。遵循 Rule 1.2,堅持使用標準 C 特性,可以最大程度地保證代碼的可移植性。
-
-
Rule 1.3 (強制): 避免未定義和未指定行為:
- 要求: 不應出現未定義或關鍵的未指定行為。
- 解釋:
- 未定義行為 (Undefined behavior): 指 C 語言標準沒有規定其行為的操作。當程序執行到未定義行為時,可能會發生任何情況,包括程序崩潰、產生錯誤的結果、正常運行等。
- 未指定行為 (Unspecified behavior): 指 C 語言標準規定了可能的行為,但具體選擇哪種行為由編譯器實現決定。未指定行為本身不一定是錯誤,但它可能導致代碼的行為在不同編譯器或平臺下不一致。
- 關鍵的未指定行為是指那些可能導致嚴重后果或安全漏洞的未指定行為。
- 示例:
- 未定義行為:
- 數組越界訪問。
- 空指針解引用。
- 除以零。
- 有符號整數溢出。
- 修改字符串字面量。
- 在信號處理函數中訪問 volatile 對象 (除非該對象是 volatile sig_atomic_t 類型)。
- 未指定行為:
- 函數參數的求值順序。
- 表達式中子表達式的求值順序 (除非有明確的序列點)。
- printf 函數中格式化字符串中 %s 對應的參數不是指向字符串的指針。
- 未定義行為:
2.2.2 未使用代碼 (Unused code)
-
Rule 2.1 (強制): 不可達代碼
-
非合規代碼示例:
void foo() { int a = 1; if (a > 0) { printf("a is positive\n"); } else { // 這段代碼永遠不會被執行 printf("a is non-positive\n"); } return; //return 之后的語句永遠不會被執行 printf("Unreachable code\n"); } -
合規代碼示例:
void foo() { int a = 1; if (a > 0) { printf("a is positive\n"); } } -
規則解釋及益處:
不可達代碼是指在程序執行過程中永遠不會被執行到的代碼。它通常是由于邏輯錯誤、條件判斷錯誤或冗余代碼導致的。不可達代碼的存在會增加代碼的復雜性,降低可讀性,并可能掩蓋潛在的邏輯錯誤。
-
-
Rule 2.2 (強制): 死代碼
-
非合規代碼示例:
void bar() { int x = 10; int y = 20; x = x + y; // x 的值被計算,但后續沒有使用, 也不會對外部造成影響 printf("y = %d\n", y); } int compute(){ int result = 1; result * 2; //計算結果沒有被使用, 也不會對外部造成影響 return result; } -
合規代碼示例:
void bar() { int y = 20; printf("y = %d\n", y); } int compute(){ int result = 1; return result * 2; } -
規則解釋及益處:
死代碼是指被執行但其結果不會對程序的輸出或后續計算產生任何影響的代碼。它可能是未使用的變量賦值、未使用的表達式計算等。死代碼的存在會浪費計算資源,增加代碼的復雜性,并可能干擾代碼優化。
-
-
Rule 2.3 (建議): A project should not contain unused type declarations
-
要求: 建議項目不應包含未使用類型聲明。
-
解釋: 如果一個類型定義但未使用, 審核人不清楚該類型是否冗余, 或者遺留的未使用錯誤。
-
非合規代碼示例:
typedef struct { int x; int y; } Point; // Point 類型未被使用 void foo() { // ... } -
合規代碼示例:
// 刪除未使用的類型聲明, 或者在后續的代碼中使用它 void foo() { // ... }
-
-
Rule 2.4 (建議): A project should not contain unused tag declarations
-
要求: 建議項目不應包含未使用標記聲明。
-
非合規代碼示例:
struct MyStruct; // 聲明了標記 MyStruct,但從未定義或使用 void foo() { // ... } -
合規代碼示例:
// 刪除未使用的標記聲明,或者定義并使用它 void foo() { // ... }
-
-
Rule 2.5 (建議): A project should not contain unused macro declarations
-
要求: 建議項目不應包含未使用宏定義聲明。
-
非合規代碼示例:
#define MAX_VALUE 100 // 定義了宏 MAX_VALUE,但從未使用 void foo() { // ... } -
合規代碼示例:
// 刪除未使用的宏定義,或者在后續的代碼中使用它 void foo() { // ... }
-
-
Rule 2.6 (建議): A function should not contain unused label declarations
-
要求: 建議函數不應包含未使用標簽聲明。
-
非合規代碼示例:
void foo() { int x = 10; start: // 定義了標簽 start,但從未被 goto 語句引用 x++; } -
合規代碼示例:
// 刪除未使用的標簽,或者添加 goto 語句引用它 void foo() { int x = 10; x++; }
-
-
Rule 2.7 (建議): There should be no unused parameters in functions
-
要求: 建議函數中, 不應有未使用參數。
-
解釋: 如果確實定義了未使用的參數, 為了確保函數兼容性, 可以形參名省略。
-
非合規代碼示例:
void foo(int x, int y) { // 參數 y 未被使用 printf("x = %d\n", x); } -
合規代碼示例:
void foo(int x, int /*y*/) { // 省略未使用參數的名稱, 使用注釋說明 printf("x = %d\n", x); } // 或者, 如果 y 參數在未來可能會被使用, 可以添加 (void)y; 語句 void foo(int x, int y) { (void)y; // 明確表示 y 參數未被使用 printf("x = %d\n", x); }
-
2.2.3 注釋 (Comments)
-
Rule 3.1 (強制): The character sequences
/*and//shall not be used within a comment-
要求: 字符序列
/*和//不應在注釋中使用。 -
解釋: 在注釋中嵌套使用注釋符號可能導致注釋提前結束, 或產生意外的注釋效果。
-
非合規代碼示例:
/* * This is a comment. * // This is a nested comment, which is not allowed. */ /* This is a comment /* with a nested comment */ , which is not allowed. */ -
合規代碼示例:
/* * This is a comment. * This is NOT a nested comment. */
-
-
Rule 3.2 (強制): Line-splicing shall not be used in
//comments-
要求: 不得在注釋
//中使用續行符()。 -
解釋: 在
//注釋中使用續行符可能導致意外的代碼被注釋掉, 降低代碼的可讀性。 -
非合規代碼示例:
// This is a comment \ int x = 10; // 這行代碼會被意外注釋掉 -
合規代碼示例:
// This is a comment. int x = 10;
-
2.2.4 字符集和詞匯約定 (Character sets and lexical conventions)
-
Rule 4.1 (強制): Octal and hexadecimal escape sequences shall be terminated
-
要求: 8進制和16進制高級轉義序列應終止。
-
解釋: 如果8進制或16進制轉義序列后跟其他轉義序列, 可能出現混淆。如, 字符常量
\x1f由1個字符組成(表示ASCII 16進制值為1f的單元分隔符US), 而\x1g由2個字符\x1(表示ASCII 16進制值為1的標題開始SOH)和g組成。 -
非合規代碼示例:
const char *str = "\x1F"; // 16 進制轉義序列未終止 printf("%s\n", str); // 輸出結果可能不符合預期 -
合規代碼示例:
const char *str = "\x1F\0"; // 使用空字符終止 16 進制轉義序列 // 或者 const char *str = "\x1F" ""; // 使用空字符串字面量終止 // 或者 const char *str = "\x1F\x67"; // 使用另一個轉義序列終止 printf("%s\n", str); -
規則解釋及益處:
如果 16 進制或 8 進制轉義序列后面緊跟其他字符,可能會導致編譯器錯誤地解析轉義序列,產生意外的字符值。為了避免這種混淆,應該使用明確的方式終止轉義序列,例如使用空字符\0或空字符串字面量"",或者另一個轉義序列。
-
-
Rule 4.2 (建議): Trigraphs should not be used
-
要求: 建議三字母詞不應使用。
-
解釋: 三字母詞(Trigraphs)由兩個問號的序列, 跟著一個特殊的第三字符。例如,
??-表示~(波浪線),??)表示]。它們可能與兩個問號的其他用法混淆。 比如, 字符串"(Date should be in the form ??-??-??)"會被編譯器解釋為"(Date should be in the form ~~]"。 Trigraphs會在預處理階段被替換。 -
非合規代碼示例:
const char *date = "Date: ??-??-????"; // 使用了三字母詞 -
合規代碼示例:
const char *date = "Date: --/--/----"; //避免使用三字母詞
-
2.2.5 標識符 (Identifiers)
-
Rule 5.1 (強制): External identifiers shall be distinct
-
要求: 外部標識符應該不同。
-
解釋: 外部標識符是指具有外部鏈接的標識符, 例如全局變量和函數名。 不同的外部標識符應該具有不同的名稱, 以避免鏈接時的沖突。
-
非合規代碼示例:
// file1.c int my_global_variable; // file2.c int my_global_variable; // 與 file1.c 中的標識符沖突 -
合規代碼示例:
// file1.c int my_global_variable_file1; // file2.c int my_global_variable_file2; // 使用不同的名稱
-
-
Rule 5.2 (強制): Identifiers declared in the same scope and name space shall be distinct
-
要求: 聲明在同一作用域和命名空間的標識符, 應該是不同的。
-
解釋: 在同一個作用域和命名空間中, 不應該有重名的標識符。 這條規則適用于所有標識符, 包括變量名, 函數名, 類型名, 標簽名等。
-
非合規代碼示例:
void foo() { int x = 10; float x = 20.0f; // 與前一個 x 重名 } -
合規代碼示例:
void foo() { int x_int = 10; float x_float = 20.0f; // 使用不同的名稱 }
-
-
Rule 5.3 (強制): An identifier declared in an inner scope shall not hide an identifier declared in an outer scope
-
要求: 在內部作用域中聲明的標識符, 不應隱藏外部作用域中聲明的標識符。
-
非合規代碼示例:
int count = 10; // 全局變量 void my_function() { int count = 20; // 局部變量,與全局變量重名,隱藏了全局變量 printf("count = %d\n", count); // 輸出 20 (局部變量的值) } -
合規代碼示例:
int global_count = 10; // 全局變量使用不同的名稱 void my_function() { int local_count = 20; printf("local_count = %d\n", local_count); printf("global_count = %d\n", global_count); } -
規則解釋及益處:
在內部作用域 (例如函數內部) 使用與外部作用域 (例如全局變量) 相同的標識符名稱會導致名稱遮蔽 (name hiding)。在內部作用域中,訪問該名稱將引用內部作用域的變量,而不是外部作用域的變量。這可能會導致代碼的邏輯錯誤和混淆。為了避免這種情況,應確保不同作用域的標識符具有不同的名稱。
-
-
Rule 5.4 (強制): Macro identifiers shall be distinct
-
要求: 宏定義標識符應該不同。
-
解釋: 不同的宏定義應該使用不同的名稱, 以避免宏展開時的沖突和意外替換。
-
非合規代碼示例:
#define MAX_SIZE 100 #define MAX_SIZE 200 // 重復定義 -
合規代碼示例:
#define MAX_SIZE_1 100 #define MAX_SIZE_2 200 // 使用不同的名稱
-
-
Rule 5.5 (強制): Identifiers shall be distinct from macro names
-
要求: 標識符應與宏定義名稱不同。
-
解釋: 標識符(變量名, 函數名, 類型名等)不應該與宏定義的名稱相同, 以避免宏展開時發生意外替換。
-
非合規代碼示例:
#define SIZE 10 int SIZE = 20; // 變量名與宏名相同 -
合規代碼示例:
#define ARRAY_SIZE 10 int size = 20; // 使用不同的名稱
-
-
Rule 5.6 (強制): A typedef name shall be a unique identifier
-
要求: typedef類型名應該是獨有的標識符。
-
解釋: typedef 定義的類型名應該與其他標識符(變量名, 函數名, 宏名, 其他類型名等)不同, 以避免命名沖突。
-
非合規代碼示例:
int MyType; typedef int MyType; // 類型名與變量名沖突 -
合規代碼示例:
int my_variable; typedef int MyType; // 使用不同的名稱
-
-
Rule 5.7 (強制): A tag name shall be a unique identifier
-
要求: 標記名應該是獨有的標識符。
-
解釋: 標記名是指struct, union, enum的名稱。 標記名應該與其他標識符(變量名, 函數名, 宏名, 類型名等)不同, 以避免命名沖突。
-
非合規代碼示例:
int Point; struct Point { // 結構體標記名與變量名沖突 int x; int y; }; -
合規代碼示例:
int my_variable; struct Point { // 使用不同的名稱 int x; int y; };
-
-
Rule 5.8 (強制): Identifiers that define objects or functions with external linkage shall be unique
-
要求: 使用外部鏈定義對象或函數的標識符應該唯一。
-
解釋: 不同源文件, 不要存在標識符重名的情況。
-
非合規代碼示例:
// file1.c int my_global_variable; void foo() {} // file2.c int my_global_variable; // 與 file1.c 中的變量重名 void foo() {} // 與 file1.c 中的函數重名 -
合規代碼示例:
// file1.c int my_global_variable_file1; void foo_file1() {} // file2.c int my_global_variable_file2; // 使用不同的名稱 void foo_file2() {} // 使用不同的名稱
-
-
Rule 5.9 (建議): Identifiers that define objects or functions with internal linkage should be unique
-
要求: 建議使用內部鏈接定義的對象或函數應該唯一。
-
解釋: 同一源文件內,具有內部鏈接的標識符(例如使用 static 關鍵字聲明的變量和函數)的名稱應是唯一的,即使它們的作用域不同。雖然 C 語言允許在不同作用域中重復使用具有內部鏈接的標識符名稱,但這會降低代碼的可讀性和可維護性,并可能導致混淆。
-
非合規代碼示例:
// file1.c static int my_static_variable; static void foo() {} void bar() { static int my_static_variable; // 與全局的 my_static_variable 重名 static void foo() {} // 與全局的 foo 重名 } -
合規代碼示例:
// file1.c static int my_static_variable_global; static void foo_global() {} void bar() { static int my_static_variable_local; // 使用不同的名稱 static void foo_local() {} // 使用不同的名稱 }
-
2.2.6 類型 (types)
-
Rule 6.1 (強制): Bit-fields shall only be declared with an appropriate type
-
要求: 位域只能用適當的類型聲明。
-
解釋:
- 對于 C90,適當的位域類型:unsigned int 或 signed int。
- 對于 C99,適當的位域類型:
- unsigned int 或 signed int;
- 實現所允許的另一個顯式有符號或顯式無符號整型;
- _Bool。
- 注意:允許使用 typedef 來指定適當的類型。
-
非合規代碼示例 (C90):
struct Flags { unsigned short is_enabled : 1; // 不符合 C90 規范 enum Status status : 2; // 不符合 C90 規范 char data : 4; // 不符合 C90 規范 }; -
合規代碼示例 (C90):
struct Flags { unsigned int is_enabled : 1; unsigned int status : 2; unsigned int data : 4; }; // 或者使用 signed int (如果需要表示負數) struct Flagsw { signed int is_negative : 1; }; -
非合規代碼示例 (C99):
//C99允許, 但MISRA C:2012限制了類型 struct Flags { unsigned short is_enabled : 1; // 不符合 MISRA C:2012 規范 enum Status status : 2; // 不符合 MISRA C:2012 規范 char data : 4; // 不符合 MISRA C:2012 規范 }; -
合規代碼示例 (C99):
struct Flags { unsigned int is_enabled : 1; // 合規 unsigned int status : 2; // 合規 unsigned int data : 4; // 合規 }; // 或者使用 _Bool struct Flagsw { _Bool is_valid : 1; // 合規 }
-
-
Rule 6.2 (強制): Single-bit named bit fields shall not be of a signed type
-
要求: 單比特命名位字段不應為帶符號類型。
-
解釋: 單比特有符號位域的行為是實現定義的。在某些編譯器上,它可能只能表示 0 和 -1,而在另一些編譯器上,它可能只能表示 0 和 1。為了避免這種不確定性,單比特位域應使用無符號類型 (unsigned int 或 _Bool)。
注意:該規則不適用于匿名位域。 -
非合規代碼示例:
struct Data { signed int flag : 1; // 單比特有符號位域 }; -
合規代碼示例:
struct Data { unsigned int flag : 1; // 單比特無符號位域 }; // 或者使用 _Bool (C99) struct Data2 { _Bool flag : 1; }
-
2.2.7 文字和常量 (Literals and constants)
-
Rule 7.1 (強制): Octal constants shall not be used
-
要求: 不應使用8進制常數。
-
解釋: 因為很容易跟10進制常數混淆,比如52(10進制),052(8進制,對應10進制42)。
-
非合規代碼示例:
int x = 052; // 八進制常量 (等于十進制的 42) int y = 010; // 八進制常量 (等于十進制的 8) -
合規代碼示例:
int x = 42; // 使用十進制 int y = 8; // 使用十進制 -
規則解釋及益處:
八進制常量以數字 0 開頭, 容易和十進制數混淆。
-
-
Rule 7.2 (強制): A "u" or "U" suffix shall be applied to all integer constants that are represented in an unsigned type
-
要求: "u"或"U"后綴應該應用到整型常量,代表無符號類型。
-
非合規代碼示例:
unsigned int value = 10; // 沒有 'u' 或 'U' 后綴 long unsigned val2 = 1000; //沒有 'u' 或 'U' 后綴 -
合規代碼示例:
unsigned int value = 10u; // 使用 'u' 后綴明確表示無符號類型 // 或者 unsigned int value = 10U; // 使用 'U' 后綴 long unsigned val2 = 1000U; -
規則解釋及益處:
在 C 語言中,整數常量可以有多種類型 ( int, long, unsigned int, unsigned long 等)。如果不顯式指定類型,編譯器會根據常量的值和上下文進行類型推導。對于無符號整數常量,如果不添加 u 或 U 后綴,編譯器可能會將其推導為有符號類型,導致潛在的類型不匹配問題。添加 u 或 U 后綴可以明確地告訴編譯器該常量是無符號類型,避免類型推導錯誤。
-
-
Rule 7.3 (強制): The lowercase character 'l' shall not be used in a literal suffix
-
要求: 小寫字母'l'不應用于文字后綴。
-
解釋: 因為很容易跟數字'1'混淆。
-
非合規代碼示例:
long int value = 123l; // 使用了小寫字母 'l' 作為后綴 -
合規代碼示例:
long int value = 123L; // 使用大寫字母 'L' 作為后綴
-
-
Rule 7.4 (強制): A string literal shall not be assigned to an object unless the object's type is “pointer to const-qualified char”
-
要求: 字符串文字不應賦值給對象,除非對象的類型是“指向常量限定字符的指針”。
-
解釋: 因為字符串文字是常量,傳遞給non-const對象的話,用戶修改內容可能會導致異常。
-
非合規代碼示例:
char *str = "hello"; // 字符串字面量賦值給 char* str[0] = 'H'; // 嘗試修改字符串字面量,可能導致未定義行為 -
合規代碼示例:
const char *str = "hello"; // 字符串字面量賦值給 const char* // str[0] = 'H'; // 編譯錯誤,不能修改 const char* 指向的內容 //如果需要修改, 則應該使用字符數組 char str_arr[] = "hello"; str_arr[0] = 'H'; //ok -
規則解釋及益處:
字符串字面量(例如 "hello")在 C 語言中是存儲在只讀內存區域的字符數組。 將其賦值給 char* 類型的指針,意味著可以通過指針修改該字符串的內容。 但是,嘗試修改字符串字面量會導致未定義行為(undefined behavior), 可能會導致程序崩潰或產生不可預測的結果。
將字符串字面量賦值給 const char* 類型的指針可以避免這個問題。 const 關鍵字表示指針指向的內容是常量,不能通過指針修改。 如果嘗試通過 const char* 指針修改字符串字面量,編譯器會產生錯誤,從而在編譯階段就發現問題。
-
2.2.8 聲明和定義 (Declarations and definitions)
-
Rule 8.1 (強制): Types shall be explicitly specified
-
要求: 類型應明確指定。
-
解釋: 因為C90標準允許在特定情形下, 省略類型, 此時, int類型為隱式指定。可能使用隱式int的情況示例如下:
- 對象聲明;
- 參數聲明;
- 成員聲明;
- typedef 聲明;
- 函數返回類型。
-
非合規代碼示例:
extern x; // 隱式聲明為 int 類型 const y = 10; // 隱式聲明為 int 類型 f(void); //隱式聲明返回int struct W { const m; //隱式int } -
合規代碼示例:
extern int x; const int y = 10; int f(void); struct W { const int m; } -
規則解釋及益處:
在 C90 標準中,如果在聲明中省略了類型說明符,編譯器會默認將其視為 int 類型。這種隱式 int 規則容易導致代碼的類型不明確,降低可讀性,并可能引入類型相關的錯誤。為了避免這種情況,MISRA C:2012 要求所有聲明都必須顯式指定類型。
-
-
Rule 8.2 (強制): Function types shall be in prototype form with named parameters
-
要求: 函數類型應該為帶命名參數的原型。
-
解釋: 為了避免混淆, 或者不一致。
-
非合規代碼示例:
int func(); // 沒有參數列表 void process(int); // 沒有參數名稱 -
合規代碼示例:
int func(void); // 使用 void 表示沒有參數 void process(int value); // 使用命名參數
-
-
Rule 8.3 (強制): All declarations of an object or function shall use the same names and type qualifiers
-
要求: 所有對象或函數的聲明應使用相同的名字或類型限定符。
-
解釋: 相同基本類型的兼容版本可以互換, 如int, signed, signed int 都是等價的。而const, non-const則不可以互換。
-
非合規代碼示例:
extern int x; extern const int x; // 類型限定符不一致 extern void foo(int a); extern void foo(int b); // 參數名稱不一致 -
合規代碼示例:
extern int x; extern int x; // 類型限定符一致 extern void foo(int a); extern void foo(int a); // 參數名稱一致
-
-
Rule 8.4 (強制): A compatible declaration shall be visible when an object or function with external linkage is defined
-
要求: 當對象或函數由外部鏈接定義時, 兼容聲明應當是可見的。
-
解釋: 定義外部對象或函數時, 有一個定義, 必須對應一個聲明。
-
非合規代碼示例:
// 沒有提前聲明 int global_var = 10; // 定義 void foo() { // 沒有提前聲明 extern void bar(); // 使用 bar(); } // 沒有提前聲明 void bar() {} // 定義 -
合規代碼示例:
// file.h extern int global_var; // 聲明 extern void bar(void); //聲明 //file.c #include "file.h" int global_var = 10; // 定義, 聲明可見 void foo() { bar(); //bar的聲明在之前, 可見 } void bar() {} // 定義
-
-
Rule 8.5 (強制): An external object or function shall be declared once in one and only one file
-
要求: 外部對象或函數應在一個且僅在一個文件中聲明。
-
解釋: 也就是說, 每個外部對象或函數, 應當僅在一個.h文件中聲明一次。
-
非合規代碼示例:
// header1.h extern int global_var; // 聲明 // header2.h extern int global_var; // 重復聲明 -
合規代碼示例:
// my_global.h extern int global_var; // 只在一個頭文件中聲明 // file1.c #include "my_global.h" // file2.c #include "my_global.h"
-
-
Rule 8.6 (強制): An identifier with external linkage shall have exactly one external definition
-
要求: 外部鏈接的標識符應有一個外部定義。
-
解釋: 包含2點: 1) 標識符必須有定義;2) 定義只能有一個。
-
非合規代碼示例:
// file1.c extern int global_var; // 聲明,沒有定義 // file2.c 沒有定義 // 或者 // file1.c int global_var; // file2.c int global_var; // 重復定義 -
合規代碼示例:
// my_global.h extern int global_var; // 聲明 // my_global.c int global_var = 10; // 定義,且只有一處
-
-
Rule 8.7 (建議): Functions and objects should not be defined with external linkage if they are referenced in only one translation unit
-
要求: 如果函數和對象僅在一個翻譯單元中引用,則不應由外部鏈接定義。
-
解釋: 這是為了限制對象可見性, 減少不同翻譯單元之間的耦合。
-
非合規代碼示例:
// file1.c int helper_function(int x) { // 外部鏈接,但僅在 file1.c 中使用 return x * 2; } void public_function() { int result = helper_function(10); // ... } -
合規代碼示例:
// file1.c static int helper_function(int x) { // 使用 static 關鍵字限制為內部鏈接 return x * 2; } void public_function() { int result = helper_function(10); // ... }
-
-
Rule 8.8 (強制): The static storage class specifier shall be used in all declarations of objects and functions that have internal linkage
-
要求: 靜態存儲類說明符 應該用于所有內部鏈接的對象和函數的聲明。
-
解釋: 對于只在當前翻譯單元中使用的對象和函數, 應該使用static關鍵字聲明, 明確表示其內部鏈接。
-
非合規代碼示例:
// file1.c int helper_variable; // 沒有使用 static,默認為外部鏈接 void helper_function(int x) { // 沒有使用 static,默認為外部鏈接 // ... } -
合規代碼示例:
// file1.c static int helper_variable; // 使用 static static void helper_function(int x) { // 使用 static // ... }
-
-
Rule 8.9 (建議): An object should be defined at block scope if its identifier only appears in a single function
-
要求: 如果對象的標識符僅出現在單個函數中,則應在塊范圍內定義該對象。
-
解釋: 減少對象的可見性, 提高代碼的可維護性。 如果一個變量只在一個函數內部使用, 應該將其定義為局部變量, 而不是全局變量或文件作用域的靜態變量。
-
非合規代碼示例:
int temp; // 全局變量,但只在 process_data 函數中使用 void process_data(int data) { temp = data * 2; // ... } -
合規代碼示例:
void process_data(int data) { int temp = data * 2; // 定義為局部變量 // ... }
-
-
Rule 8.10 (強制): An inline function shall be declared with the static storage class
-
要求: 內聯函數應與靜態存儲類一同聲明。
-
解釋: 內聯函數聲明為外部鏈接, 但沒有在同一翻譯單元內定義, 會導致未定義行為。 調用外部鏈接的內聯函數, 可能調用外部函數的定義, 或者使用內聯定義, 這會影響執行速度。 注意: 可通過內聯函數置于將頭文件, 使得內聯函數在多個翻譯單元內可用。
-
非合規代碼示例:
// my_header.h inline int square(int x) { // 沒有使用 static return x * x; } -
合規代碼示例:
// my_header.h static inline int square(int x) { // 使用 static return x * x; }
-
-
Rule 8.11 (建議): When an array with external linkage is declared, its size should be explicitly specified
-
要求: 當外部鏈接的數組聲明時,它的尺寸應明確指定。
-
解釋: 該規則僅應用于非定義的聲明。聲明中明確數組尺寸, 便于檢查一致性, 以及邊界檢查。
-
非合規代碼示例:
extern int arr[]; // 沒有指定數組大小 -
合規代碼示例:
extern int arr[10]; // 明確指定數組大小
-
-
Rule 8.12 (強制): Within an enumerator list, the value of an implicitly specified enumeration constant shall be unique
-
要求: 在枚舉列表中, 隱式指定枚舉常量值是唯一的。
-
解釋: 未指定值的枚舉成員默認遞增, 注意不能與其他成員同值。
-
非合規代碼示例:
enum Color { RED, // 默認為 0 GREEN, // 默認為 1 BLUE = 1, // 顯式指定為 1,與 GREEN 重復 YELLOW // 默認為 2 }; -
合規代碼示例:
enum Color { RED, // 默認為 0 GREEN, // 默認為 1 BLUE, // 默認為 2 YELLOW // 默認為 3 }; // 或者顯式指定所有值 enum Color2 { RED = 1, GREEN = 2, BLUE = 4, YELLOW = 8 }
-
-
Rule 8.13 (建議): A pointer should point to a const-qualified type whenever possible
-
要求: 指針應盡可能指向常量限定類型。
-
解釋: 如果一個函數不需要修改指針指向的數據, 應該將指針參數聲明為指向const的指針。 這可以防止意外修改數據, 提高代碼的安全性, 并且可以提高代碼的可讀性(明確表明函數不會修改數據)。
-
非合規代碼示例:
void print_string(char *str) { // 沒有使用 const printf("%s\n", str); } -
合規代碼示例:
void print_string(const char *str) { // 使用 const printf("%s\n", str); }
-
-
Rule 8.14 (強制): The restrict type qualifier shall not be used
- 要求: restrict類型限定符不應使用。
- 解釋: C中restrict關鍵字用于告訴編譯器, 對象已經被指針所引用, 不能通過除該指針外所有其他直接或間接的防曬修改該對象的內容。當使用restrict類型限定符時, 可能會改善由編譯器生成的代碼效率。但使用restrict同時, 也要求程序員必須確保兩個指針所指內存區域沒有重疊。MISRA C:2012出于安全考慮,禁止使用。
2.2.9 初始化 (Initialization)
-
Rule 9.1 (強制): The value of an object with automatic storage duration shall not be read before it has been set
-
強制: 自動變量在設置之前不允許讀取。
-
非合規代碼示例:
void func() { int a; // 未初始化 int b = a; // 讀取未初始化的值 } int foo(){ int x; if (x) { //x未初始化 } } -
合規代碼示例:
void func() { int a = 0; // 初始化 int b = a; } int foo(){ int x = 0; if (x) { } } -
規則解釋及益處:
讀取未初始化的值會導致未定義行為, 結果不可預測。
-
-
Rule 9.2 (強制): The initializer for an aggregate or union shall be enclosed in braces
-
要求: 聚合體或聯合體的初值應該包含在大括號中。
-
解釋: 聚合體是指數組(array)或類(class)或結構體(struct)。 注意:
{ 0 }形式的初始化器,可以設置所有值為0,而無需嵌套括號。 -
非合規代碼示例:
int arr[3] = {1, 2, 3}; // 對于簡單數組,可以省略最外層大括號, 但不建議 struct point { int x; int y; } p = {1, 2}; // 對于結構體,也可以省略最外層大括號, 但不建議 //多維數組 int matrix[2][2] = {1, 2, 3, 4}; //不建議 -
合規代碼示例:
int arr[3] = {1, 2, 3}; // 保持一致性 struct point { int x; int y; } p = { {1, 2} }; // 使用嵌套大括號, 或者 {1, 2} // 對于所有元素初始化為 0 的情況, 可以用 {0} int arr2[5] = {0}; //多維數組 int matrix[2][2] = { {1, 2}, {3, 4} }; //建議 -
規則解釋及益處:
聚合類型(數組和結構體)初始化, 使用大括號可以提高代碼可讀性, 避免歧義, 尤其是在嵌套聚合類型的情況下。
-
-
Rule 9.3 (強制): Arrays shall not be partially initialized
-
要求: 數組不應部分初始化。
-
解釋: 必須為每個數組元素設定初值。
-
非合規代碼示例:
int arr[5] = {1, 2}; // 只初始化了前兩個元素,其余元素隱式初始化為 0, 不建議 -
合規代碼示例:
int arr[5] = {1, 2, 0, 0, 0}; // 顯式初始化所有元素 // 或者使用 {0} 初始化所有元素為 0 int arr2[5] = {0};
-
-
Rule 9.4 (強制): An element of an object shall not be initialised more than once
-
要求: 對象的元素不應初始化超過一次。
-
非合規代碼示例:
int arr[3] = { [0] = 1, [0] = 2, [1] = 3 }; // 重復初始化 arr[0] -
合規代碼示例:
int arr[3] = { [0] = 1, [1] = 3, [2] = 0 };
-
-
Rule 9.5 (強制): Where designated initialisers are used to initialize an array object the size of the array shall be specified explicitly
-
要求: 如果指定的初始化器用于初始化數組對象,那么應明確指定數組的大小。
-
非合規代碼示例:
int arr[] = { [0] = 1, [2] = 3 }; // 沒有顯式指定數組大小 -
合規代碼示例:
int arr[5] = { [0] = 1, [2] = 3 }; // 顯式指定數組大小
-
2.2.10 基本類型模型 (The essential type model)
-
Rule 10.1 (強制): Operands shall not be of an inappropriate essential type
-
要求: 操作數不應具有不恰當的基本類型。
-
解釋: 算術操作數的基本類型類別, 總結如下表:
Opeartor Operand Boolean character enum signed unsigned floating [] integer 3 4 1 ++ 3 4 5 -- 3 4 5 8 + - either 3 * / either 3 4 5 % either 3 4 5 1 < > <= >= either == != either ! && !! any 2 2 2 2 2 2 << >> left 3 4 5,6 6 1 << >> right 3 4 7 7 1 ~ & ! ^ any 3 4 5,6 6 1 ?: 1st 2 2 2 2 2 ?: 2nd and 3rd上面的數字對應下面原理編號:
- 對這些操作數使用floating類型,是違反約束的。
- 本質上Boolean類型的表達式,應該用于操作數解釋為Boolean值的地方。
- 本質上Boolean類型的操作數,不應用于操作數解釋為數值的地方。
- 本質上character類型的操作數,不應用于操作數解釋為數值的地方。字符數據的數值是由實現定義的。
- 本質上enum類型的操作數,不應用于算術操作,因為enum對象使用整型定義實現。涉及枚舉對象的操作,可能產生意外類型的結果。注意匿名枚舉中的枚舉常量,本質上具有帶符號類型。
- 移位和逐位操作僅對本質上無符號的操作數執行。本質上有符號類型的使用產生的數值,是由定義實現的。
- 移位運算的右側操作符,應該是本質上無符號類型,以確保負移位不會刀子劃未定義行為。
- 本質上有符號類型的操作數,不應用于一元減號運算符的操作數,因為結果的符號性由實現的int大小決定。
-
示例:
//不符合規則的 float arr[10]; int index = 1.1f; //不符合, 浮點數不能做下標 arr[index] = 0; bool flag = 1; //不符合, 布爾類型不能做算術運算 int x = flag + 1; char c = 'A'; int y = c + 1; //不符合, 字符類型不應作為數值//符合規則的 float arr[10]; int index = 1; arr[index] = 0; bool flag = true; int x = flag ? 1 : 0; //使用條件運算符 char c = 'A'; //字符類型
-
-
Rule 10.2 (建議): Expressions of essentially character type shall not be used inappropriately in addition and subtraction operations
-
要求: 本質上為字符類型的表達式,不應用在不正確的加法和減法運算中。
-
解釋: 當數據不代表數值時,帶字符類型的表達式不能用于算術運算。然而有些情況是允許字符數據的運算的。
例如:
1)2個字符類型的操作數的減法,可用于在數字'0'到'9'和相應的序數之間轉換。
2)字符類型和無符號類型的加法,可能用于序數到相應數字范圍'0'至'9的轉換。
3)從字符類型到無符號類型底減法,可能用于小寫轉大寫。 -
非合規代碼示例:
char c = 'a'; int x = c + 10; // 不符合:字符類型與整數相加 int y = c - '0'; //可以使用, 但是要保證c是數字字符 -
合規代碼示例:
char c = '5'; if(c >= '0' && c <= '9') { //確保是數字字符 int x = c - '0'; // 可以用于數字字符和數值之間的轉換 }
-
-
Rule 10.3 (強制): The value of an expression shall not be assigned to an object with a narrower essential type or of a different essential type category
-
要求: 表達式的值不應賦值給本質類型較窄 或 不同的基本類型 的對象。
-
解釋: 寬數字賦值給窄數字,會發生截斷。
-
非合規代碼示例:
uint32_t large_value = 0xFFFFFFFF; uint16_t small_value = large_value; // 數據截斷,small_value 的值不等于 large_value float f = 3.14f; int i = f; // 浮點數賦值給整數,丟失小數部分 -
合規代碼示例:
uint32_t large_value = 0xFFFFFFFF; uint16_t small_value; if (large_value <= 0xFFFF) { small_value = (uint16_t)large_value; //顯式轉換 } else { // 處理溢出情況 } float f = 3.14f; int i = (int)f; //顯式轉換 -
規則解釋及益處:
將較寬類型(如uint32_t)的值賦值給較窄類型(如uint16_t)的變量時,可能會發生數據截斷,導致信息丟失。為了避免這種情況,在賦值之前應該進行檢查,確保值不會超出較窄類型的范圍, 或者進行顯式類型轉換。
-
-
Rule 10.4 (強制): Both operands of an operator in which the usual arithmetic conversions are performed shall have the same essential type category
-
要求: 通常的算術轉換時的運算符的2個操作數,應具有相同的基本類型類別。
-
解釋:
C 語言的算術運算符在對不同類型的操作數進行運算時,會進行 “尋常算術轉換” (usual arithmetic conversions)。 轉換的目的是將兩個操作數轉換為同一類型,然后再進行運算。 MISRA C 要求進行算術運算的兩個操作數應屬于同一基本類型類別(例如,都是有符號整數、都是無符號整數或都是浮點數)。這條規則的目的是避免隱式類型轉換可能導致的意外結果和精度損失。 -
非合規代碼示例:
unsigned int u = 10u; int s = -5; long result = u + s; // 不符合:unsigned int 和 int 屬于不同類別 -
合規代碼示例:
unsigned int u = 10u; int s = -5; // 將兩個操作數顯式轉換為同一類別 (例如,都轉換為 unsigned int) unsigned long result = (unsigned long)u + (unsigned long)s; //不推薦, 可能會有負數問題 // 或者都轉換為int long result2 = (long)s + (long)u; //如果能保證u可以被int表示 // 更安全的做法是確保兩個操作數在運算前就具有相同的類型 unsigned int u = 10u; unsigned int s = 5u; // 如果 s 必須表示負數,則需要重新設計 unsigned int result = u + s; -
規則解釋及益處:
不同類型類別的操作數進行算術運算時,C 語言會進行隱式類型轉換。這種隱式轉換可能不是程序員所期望的,并可能導致意外的結果或精度損失。例如,將 unsigned int 和 int 相加時,int 會被轉換為 unsigned int,如果 int 為負數,則轉換后的值會是一個很大的正數。
-
-
Rule 10.5 (建議): The value of an expression should not be cast to an inappropriate essential type
-
要求: 建議表達式的值不應轉型為不恰當的本質類型。
-
解釋: 強制類型轉換應該謹慎使用,避免將表達式的結果強制轉換為不恰當的類型。 “不恰當” 的類型轉換包括:
- 將指針類型轉換為任何其他類型(除了 void* 或從 void* 轉換)。
- 將浮點類型轉換為任何其他類型(除了整數類型,并且要確保沒有溢出或精度損失)。
- 將復數類型轉換為任何其他類型。
- 將枚舉類型轉換為任何其他類型(除了整數類型,并且要確保沒有溢出)。
- 將 _Bool 類型轉換為任何其他類型(除了整數類型)。
- 將一個本質類型(非typedef)轉換為具有不同基本類型的typedef類型
-
非合規代碼示例:
float x = 3.14f; char *p = (char *)x; // 不恰當:浮點數轉換為指針 int arr[5]; float f = (float)arr; //不恰當: 數組轉換為浮點數 typedef signed int sint_t; unsigned int u = 10u; sint_t s = (sint_t)u; // 不恰當,unsigned int 轉換為 signed int -
合規代碼示例:
float x = 3.14f; int i = (int)x; // 浮點數轉換為整數(確保不會溢出) typedef unsigned int uint_t; unsigned int u = 10u; uint_t u2 = (uint_t)u; // 合規, 本質類型相同
-
-
Rule 10.6 (強制): The value of a composite expression shall not be assigned to an object with wider essential type
-
要求: 復合表達式的值不應賦值給具有更寬基本類型的對象。
-
解釋: 復合表達式是由多個操作數和運算符組成的表達式。這條規則的目的是避免在計算過程中進行不必要的類型提升,然后在賦值時又進行類型轉換。
-
非合規示例:
uint8_t a = 10u; uint8_t b = 20u; uint16_t c = a + b; // a + b 是復合表達式,結果為 uint8_t,然后隱式轉換為 uint16_t -
合規代碼示例:
uint8_t a = 10u; uint8_t b = 20u; // 顯式地將復合表達式的結果轉換為目標類型 uint16_t c = (uint16_t)(a + b); // 或者,如果能保證結果不會溢出,也可以將 a 和 b 轉換為更寬的類型 uint16_t a16 = a; uint16_t b16 = b; uint16_t c = a16 + b16; -
規則解釋及益處:
可以讓運算過程提升數值寬度,但不要在賦值時提升。
-
-
Rule 10.7 (強制): If a composite expression is used as one operand of an operator in which the usual arithmetic conversions are performed then the other operand shall not have wider essential type
-
要求: 如果一個復合表達式被用作執行常用算術轉換的運算符的一個操作數,另一個運算數不應具有更寬的本質類型。
-
解釋: 這條規則與 Rule 10.6 相輔相成,目的是控制復合表達式的類型提升。它要求在進行算術運算時,如果一個操作數是復合表達式,那么另一個操作數的類型不能比復合表達式的結果類型更寬。
-
非合規代碼示例:
uint8_t a = 10u; uint8_t b = 20u; uint32_t c = 30u; uint32_t result = (a + b) * c; // (a + b) 是復合表達式,結果為 uint8_t,c 為 uint32_t -
合規代碼示例:
uint8_t a = 10u; uint8_t b = 20u; uint32_t c = 30u; // 顯式地將復合表達式的結果轉換為更寬的類型 uint32_t result = (uint32_t)(a + b) * c; // 或者,將較窄類型的操作數提升為更寬的類型 uint32_t a32 = a; uint32_t b32 = b; uint32_t result = (a32 + b32) * c;
-
-
Rule 10.8 (強制): The value of a composite expression shall not be cast to a different essential type category or a wider essential type
-
要求: 復合表達式的值不應轉型為不同本質類型類別或一更寬的本質類型。
-
解釋: 類似于 Rule 10.6 和 10.7, 目的是避免不必要的類型轉換。
-
非合規代碼示例:
uint8_t a = 10u; uint8_t b = 20u; uint32_t result = (uint32_t)(a + b); //不建議 int8_t x = 1; int8_t y = 2; float z = (float)(x + y); //不建議 -
合規代碼示例:
uint8_t a = 10u; uint8_t b = 20u; uint8_t result = a + b; //如果能確保不會溢出 int8_t x = 1; int8_t y = 2; int8_t z = x + y; //如果能確保類型匹配
-
2.2.11 指針類型轉換 (Pointer type conversions)
-
Rule 11.1 (強制): Conversions shall not be performed between a pointer to a function and any other type
-
要求: 轉換不應在函數指針和其他類型指針之間進行。
-
解釋: 函數指針和其他類型指針(如 void、int 等)之間的轉換是未定義行為,可能導致程序崩潰或產生不可預測的結果。
-
非合規代碼示例:
void foo() {} void (*func_ptr)() = foo; int *int_ptr = (int *)func_ptr; // 不允許:函數指針轉換為 int* void *void_ptr = (void*)func_ptr; //不允許 typedef void (*FuncPtr)(void); FuncPtr my_func = (FuncPtr)0x12345678; // 不允許:整數轉換為函數指針 -
合規代碼示例:
void foo() {} void (*func_ptr)() = foo; //正確
-
-
Rule 11.2 (強制): Conversions shall not be performed between a pointer to incomplete and any other type
-
要求: 轉換不應在不完整類型和其他類型指針之間進行。
-
解釋: 不完整類型指:
- void 類型
- 未知大小數組
- 具有不完整類型元素的數組
- 未定義的結構體,聯合體,或枚舉(只有聲明)
- 指向已聲明但未定義的類的指針
- 聲明但未定義的類
-
異常情況:
- null 指針常量能轉換為指向不完整類型的指針。
- 指向不完整類型的指針,能轉換為 void。
-
非合規代碼示例:
struct S; // 前向聲明(不完整類型) struct S *s_ptr; int *int_ptr = (int *)s_ptr; // 不允許:不完整類型指針轉換為 int* //void *可以轉為任意類型指針 -
合規代碼示例:
struct S; // 前向聲明(不完整類型) struct S *s_ptr; void *void_ptr = (void*)s_ptr;//可以, 但是不推薦
-
-
Rule 11.3 (強制): A cast shall not be performed between a pointer to object type and a pointer to a different object type
-
要求: 轉型不應在指向不同對象類型的指針之間進行。
-
解釋: 指向不同對象類型的指針可能具有不同的大小和對齊要求。強制轉換這些指針可能導致未定義行為,例如訪問越界或未對齊的內存。
-
非合規代碼示例:
int x = 10; int *int_ptr = &x; char *char_ptr = (char *)int_ptr; // 不允許:int* 轉換為 char* float *float_ptr = (float*)&x; //不允許 -
合規代碼示例:
如果需要訪問不同類型的數據, 應該使用memcpy進行內存拷貝, 或者使用union:// 使用 memcpy int x = 10; char char_arr[sizeof(int)]; memcpy(char_arr, &x, sizeof(int)); // 使用 union union { int i; float f; } data; data.i = 10; float f = data.f; //避免使用union -
規則解釋及益處:
不同對象類型指針的轉換可能導致地址不對齊問題。例如, uint32_t 類型通常要求 4 字節對齊, 而uint8_t 類型是 1 字節對齊。如果將uint8_t 數組的地址強制轉換為uint32_t, 然后通過uint32_t 指針訪問內存, 可能會導致未定義行為(在某些架構上, 未對齊的訪問會導致硬件異常)。
-
-
Rule 11.4 (建議): A conversion should not be performed between a pointer to object and an integer type
-
要求: 建議轉型不應在指向對象的指針和整型之間進行。
-
解釋: 指針和整數之間的轉換是實現定義的,可能導致未定義行為或信息丟失。指針的大小和表示方式可能與整數類型不同。
-
非合規代碼示例:
int arr[5]; int *ptr = &arr[0]; uintptr_t addr = (uintptr_t)ptr; // 不建議:指針轉換為整數 int *ptr2 = (int *)addr; // 不建議:整數轉換回指針 -
合規代碼示例:
通常, 避免指針和整數之間的轉換。如果需要存儲指針的數值表示, 可以使用uintptr_t或intptr_t類型(C99), 但仍然需要謹慎使用, 確保轉換后的值在目標平臺上有效。
-
-
Rule 11.5 (建議): A conversion should not be performed from pointer to void into pointer to object
-
要求: 建議不應將指向void的指針轉型為指向對象的指針。
-
解釋: 反過來可以: 能將指向對象的指針轉型為void指針。 將 void* 轉換為對象指針需要顯式類型轉換,這可能會掩蓋類型錯誤。 應該盡量避免這種轉換,或者在轉換時進行額外的檢查。
-
非合規代碼示例:
void *void_ptr; int *int_ptr = (int *)void_ptr; // 不建議:void* 轉換為 int* -
合規代碼示例:
//避免 void* 到具體類型的轉換
-
-
Rule 11.6 (強制): A cast shall not be performed between pointer to void and an arithmetic type
-
要求: 轉型不應在void指針和算術類型之間進行。
-
解釋: void* 和算術類型之間的轉換沒有意義, 并且可能導致未定義行為。
-
非合規代碼示例:
void *void_ptr; int x = (int)void_ptr; // 不允許 float f = (float)void_ptr; //不允許
-
-
Rule 11.7 (強制): A cast shall not be performed between pointer to object and a non-integer arithmetic type
-
要求: 轉型不應在指向對象的指針和非整型算術類型之間進行。
-
解釋: 指向對象的指針和非整型算術類型(如float, double等)之間的轉換沒有意義, 并且可能導致未定義行為。
-
非合規代碼示例:
int arr[5]; int *ptr = &arr[0]; float f = (float)ptr; // 不允許
-
-
Rule 11.8 (強制): A cast shall not remove any const or volatile qualification from the type pointed to by a pointer
-
要求: 轉型不應移除來自指針指向的類型的任何const 或 volatile限定符。
-
解釋: const 和 volatile 限定符用于保證程序的正確性和安全性。 移除這些限定符可能導致意外修改只讀數據或錯誤地優化掉對 volatile 變量的訪問。
-
非合規代碼示例:
const int x = 10; const int *const_ptr = &x; int *non_const_ptr = (int *)const_ptr; // 不允許:移除 const 限定符 volatile int y; volatile int *volatile_ptr = &y; int *non_volatile_ptr = (int *)volatile_ptr; // 不允許:移除 volatile 限定符 -
合規代碼示例:
const int x = 10; const int *const_ptr = &x; const int *another_const_ptr = const_ptr; // 正確:保留 const 限定符 volatile int y; volatile int *volatile_ptr = &y; volatile int *another_volatile_ptr = volatile_ptr; // 正確:保留 volatile 限定符
-
-
Rule 11.9 (強制): The macro NULL shall be the only permitted form of integer null pointer constant
-
要求: NULL宏是唯一允許的整型空指針常量的形式。
-
解釋: 在 C 語言中,空指針常量可以表示為整數 0 或 (void*)0。為了代碼的清晰和一致性,MISRA C 規定只能使用 NULL 宏來表示空指針常量。
-
非合規代碼示例:
int *ptr = 0; // 不允許:使用整數 0 表示空指針 int *ptr2 = (void*)0; //不允許 -
合規代碼示例:
#include <stddef.h> // 包含 NULL 的定義 int *ptr = NULL; // 正確:使用 NULL 宏
-
2.2.12 表達式 (Expressions)
-
Rule 12.1 (建議): The precedence of operators within expressions should be made explicit
-
要求: 應明確表達式中運算符的優先級。
-
解釋: 雖然C語言有明確的運算符優先級規則, 但是過度依賴這些規則會讓代碼難以閱讀和理解。使用括號可以消除歧義, 提高可讀性。
-
非合規代碼示例:
int x = a + b * c; // 依賴運算符優先級,可讀性較差 int y = a << b + 1; //不清晰 -
合規代碼示例:
int x = a + (b * c); // 使用括號明確優先級,可讀性更好 int y = a << (b + 1); //更清晰 -
規則解釋及益處:
雖然C語言有明確的運算符優先級規則, 但是過度依賴這些規則會讓代碼難以閱讀和理解。使用括號可以消除歧義, 提高可讀性。
-
-
Rule 12.2 (強制): The right hand operand of a shift operator shall lie in the range zero to one less than the width in bits of the essential type of the left hand operand
-
要求: 移位運算符的右手操作數應在0~1之間, 比左操作數的基本類型的位寬小。
-
解釋: 右移操作數必須在合法范圍內, 否則會導致未定義行為。
-
非合規代碼示例:
uint8_t x = 10u; uint8_t y = x >> 8; // 不符合:右移位數大于等于左操作數位寬 uint8_t z = x << -1; //不允許, 負數 -
合規代碼示例:
uint8_t x = 10u; uint8_t y = x >> 7; // 右移位數小于左操作數位寬
-
-
Rule 12.3 (建議): The comma operator should not be used
-
要求: 建議逗號操作符不應使用。
-
解釋: 逗號運算符允許在單個表達式中執行多個操作,但它會降低代碼的可讀性,并可能導致意外的副作用。
-
非合規代碼示例:
int x = 10; int y = 20; int z = (x++, y++); // 使用逗號運算符,不建議 -
合規代碼示例:
int x = 10; int y = 20; x++; // 將逗號運算符分隔的操作分開寫 y++; int z = y;
-
-
Rule 12.4 (建議): Evaluation of constant expressions should not lead to unsigned integer wrap-around
-
要求: 建議常量表達式的值計算不應導致無符號整型環繞(溢出)。
-
解釋: 無符號整型表達式沒有嚴格的的溢出, 取而代之的是環繞(wrap-around)。
-
非合規代碼示例:
#define MAX_U8 255u uint8_t x = MAX_U8 + 1u; // 發生環繞, 結果為 0, 不建議 -
合規代碼示例:
//避免在常量表達式中導致無符號整數環繞 //如果需要, 使用更大范圍的類型, 或者添加運行時檢查
-
2.2.13 副作用 (Side effects)
-
Rule 13.1 (強制): Initialiser lists shall not contain persistent side effects
-
要求: 初始化列表不應包含持久副作用。
-
解釋: C90限制聚合類型的初始化器僅包含常量。但C99允許初值包含運行時計算的表達式, 也允許作為匿名初始化對象的復合文字。初始化器列表中表達式求值過程中, 副作用發生的順序鎖未指定的, 因此, 如果這些副作用持續存在, 那么初始化的行為不可預測。
-
非合規代碼示例:
int x = 0; int arr[3] = { x++, x++, x++ }; // 不允許:初始化列表中包含副作用 int y = 0; int foo(){ y++; return y; } int arr2[3] = { foo(), 2, 3}; //不允許, 副作用 -
合規代碼示例:
int x = 0; int arr[3] = { 0, 1, 2 }; // 避免在初始化列表中使用副作用 // 或者, 如果確實需要使用變量初始化, 則分開賦值 int arr2[3]; arr2[0] = x++; arr2[1] = x++; arr2[2] = x++;
-
-
Rule 13.2 (強制): The value of an expression and its persistent side effects shall be the same under all permitted evaluation orders
-
要求: 表達式的值和它的持久副作用, 應在所有允許的計算順序情況下相同。
-
解釋: C 語言標準并沒有嚴格規定表達式中子表達式的求值順序(除非有明確的序列點)。 這意味著編譯器可以自由選擇不同的求值順序。 如果表達式中包含副作用(例如修改變量的值), 并且這些副作用會影響表達式的最終結果, 那么不同的求值順序可能會導致不同的結果。
-
非合規代碼示例:
int x = 0; int y = x++ + x++; // 結果依賴于 x++ 的求值順序,可能導致未定義行為 int foo(int a, int b) { return a + b; } int z = 0; int result = foo(z++, z++); //不確定哪個參數先計算 -
合規代碼示例:
int x = 0; int y = x + 1; x += 2; // 將副作用操作分開 int foo(int a, int b) { return a + b; } int z = 0; int result = foo(z, z + 1); //避免副作用 z += 2;
-
-
Rule 13.3 (建議): A full expression containing an increment (++) or decrement (--) operator should have no other potential side effects other than that caused by the increment or decrement operator
-
要求: 建議。包含自增或自減運算符的完整表達式,除了由自增或自減運算符引起的副作用外,不應有其他潛在的副作用。
-
非合規代碼示例:
int arr[5] = {1, 2, 3, 4, 5}; int i = 0; int x = arr[i++] + arr[i]; // 結果依賴于 i++ 的求值順序,可能導致未定義行為 extern int foo(int); int y = 0; int z = foo(y++) + y; //不建議 -
合規代碼示例:
int arr[5] = {1, 2, 3, 4, 5}; int i = 0; int x = arr[i]; i++; x += arr[i]; // 將自增/自減操作與其他操作分離 extern int foo(int); int y = 0; int z = foo(y); //先調用函數, 再自增 y++; z = z + y; -
規則解釋及益處:
在一個表達式中多次使用自增/自減運算符, 或者將自增/自減運算符與其他運算符混合使用, 可能會導致代碼的行為難以預測, 甚至導致未定義行為。 這是因為 C 語言標準并沒有嚴格規定表達式中不同部分的求值順序。 為了避免這種問題, 應該將自增/自減操作與其他操作分離, 使代碼的求值順序更加清晰。
-
-
Rule 13.4 (建議): The result of an assignment operator should not be used
-
要求: 建議賦值運算符的結果不應使用。
-
解釋: 使用賦值表達式的結果會讓代碼難以閱讀, 并且容易引入錯誤(例如, 將=和==混淆)。
-
非合規代碼示例:
int x, y; if ((x = y) != 0) { // 不建議:使用賦值表達式的結果 // ... } -
合規代碼示例:
int x, y; x = y; // 將賦值操作和判斷操作分開 if (x != 0) { // ... }
-
-
Rule 13.5 (強制): The right hand operand of a logical && or || operator shall not contain persistent side effects
-
要求: 邏輯運算符
&&或||運算符不應包含持久副作用。 -
解釋: C 語言的邏輯運算符
&&和||具有短路求值特性。- 對于
&&,如果左操作數為 false,則不會計算右操作數。 - 對于
||,如果左操作數為 true,則不會計算右操作數。
如果&&或||的右操作數包含副作用,那么這些副作用可能不會發生,導致代碼的行為不確定。
- 對于
-
非合規代碼示例:
int x = 0; if (is_valid() && (x++ > 0)) { // 不允許:右操作數包含副作用 // ... } -
合規代碼示例:
int x = 0; if (is_valid()) { if (x++ > 0) { // 將副作用操作移到 if 語句內部 // ... } } //或者 bool valid = is_valid(); int temp = x; x++; if(valid && (temp > 0)) { }
-
-
Rule 13.6 (強制): The operand of the sizeof operator shall not contain any expression which has potential side effects
-
強制: sizeof運算符的操作數不應包含任何存在潛在副作用的表達式。
-
解釋: sizeof 運算符在編譯時求值,其操作數不會被執行。如果在 sizeof 的操作數中包含帶有副作用的表達式,這些副作用將不會發生,可能會導致代碼邏輯錯誤。
-
非合規代碼示例:
int x = 10; size_t size = sizeof(x++); // 不允許:sizeof 操作數包含副作用 -
合規代碼示例:
int x = 10; size_t size = sizeof(x); // 正確:sizeof 操作數不包含副作用
-
2.2.14 控制狀態表達式 (Control statement expressions)
-
Rule 14.1 (建議): A loop counter shall not have essentially floating type
-
要求: 循環計數器不應有實質上的浮點類型。
-
解釋: 浮點數存在精度問題, 使用浮點數作為循環計數器可能導致循環次數不準確。
-
非合規代碼示例:
for (float f = 0.0f; f < 10.0f; f += 0.1f) { // 不建議:使用浮點數作為循環計數器 // ... } -
合規代碼示例:
for (int i = 0; i < 100; i++) { // 使用整數作為循環計數器 float f = i / 10.0f; // ... }
-
-
Rule 14.2 (強制): A for loop shall be well-formed
-
要求: 一個循環應有良好的結構。
-
解釋: MISRA C 對 for 循環的結構有具體要求,主要包括:
- 循環應該只有一個循環計數器。
- 循環計數器應該在 for 循環的初始化部分進行初始化。
- 循環計數器應該在 for 循環的更新部分進行更新(遞增或遞減)。
- 循環計數器的類型應該是整型或枚舉類型。
- 循環條件應該只涉及循環計數器和循環不變量的比較。
- 循環體內不能修改循環計數器,也不能使用 goto 語句跳轉到循環外部或從循環外部跳轉到循環內部。
-
非合規代碼示例:
for (int i = 0; i < 10; ) { // 在循環體內修改循環控制變量 if (some_condition) { i += 2; } else { i++; } } // 多個循環控制變量 for(int i = 0, j = 0; i < 10; i++, j++){ } //修改循環不變量 int limit = 10; for(int i = 0; i < limit; i++) { limit--; } -
合規代碼示例:
for (int i = 0; i < 10; i++) { if (some_condition) { // 通過其他方式處理特殊情況,不直接修改循環控制變量 // ... } } -
規則解釋及益處:
在for循環體內修改循環控制變量, 會讓代碼難以理解和維護。for循環的控制部分 (初始化, 條件, 更新) 應該清晰地描述循環的邏輯。 如果需要在循環體內修改循環控制變量, 應該考慮使用while循環, 或者將修改邏輯移到循環控制部分。
-
-
Rule 14.3 (強制): Controlling expressions shall not be invariant
-
要求: 控制表達式不應是不變的。
-
解釋: 該規則適用于:
if/while/for/do..while/switch的條件;
?:運算符的第一個操作數。
控制表達式不應該始終為 true 或始終為 false。如果控制表達式始終為 true,則循環可能永遠不會結束(死循環),或者 if 語句的某個分支永遠不會被執行。如果控制表達式始終為 false,則循環體永遠不會被執行,或者 if 語句的某個分支永遠不會被執行。
例外情況:- 無限循環。
- do..while 循環的控制表達式允許為0。
-
非合規代碼示例:
if (true) { // 控制表達式始終為 true // ... } while (1) { // 控制表達式始終為 true (除非是故意設計的無限循環) // ... } for ( ; 0; ) { //不應該出現 } -
合規代碼示例:
int x = get_value(); if (x > 0) { // 控制表達式的值取決于變量 x // ... } while (check_condition()) { // 控制表達式的值取決于函數 check_condition() 的返回值 // ... }
-
-
Rule 14.4 (強制): The controlling expression of an if statement and the controlling expression of an iteration-statement shall have essentially Boolean type
-
要求: if語句的控制表達式和迭代語句的控制表達式, 應具有本質上布爾類型。
-
解釋: if 語句和迭代語句(for、while、do-while)的控制表達式應該具有本質上的布爾類型。這意味著表達式的結果應該是 true 或 false,或者可以隱式轉換為 bool 類型 (C99)。在 C90 中,雖然沒有 bool 類型,但控制表達式的結果應該被視為邏輯真或假(非零值為真,零值為假)。為了代碼的清晰和可移植性,MISRA C 建議控制表達式應顯式地與 0 進行比較,或者使用邏輯運算符 (!, &&, ||) 將其轉換為布爾值。
-
非合規代碼示例:
int x = 10; if (x) { // 不建議:使用整數作為控制表達式, 應該顯式與 0 比較 // ... } float f = 3.14f; while (f) { // 不建議:使用浮點數作為控制表達式 // ... } int *ptr = get_pointer(); if(ptr) { //不建議, 應該顯式與 NULL 比較 } -
合規代碼示例:
int x = 10; if (x != 0) { // 顯式地將整數與 0 進行比較 // ... } float f = 3.14f; while (f > 0.0f) { // 顯式地將浮點數與 0.0f 進行比較, 或者定義一個誤差范圍 // } int *ptr = get_pointer(); if(ptr != NULL) { // 顯式地與 NULL 進行比較 } // 或者, 定義 bool 類型 (C99) #include <stdbool.h> // 引入 bool 類型 bool is_valid = (x != 0); if (is_valid) { // ... }
-
2.2.15 控制流 (Control flow)
-
Rule 15.1 (建議): The goto statement should not be used
-
要求: 建議goto語句不應使用。
-
解釋: goto 語句會破壞代碼的結構化, 讓代碼的控制流難以追蹤, 降低代碼的可讀性和可維護性。 在大多數情況下, 可以使用if-else, while, for, break, continue等結構化控制語句來代替goto。
-
非合規代碼示例:
void process_data(int data) { if (data < 0) { goto error_handling; // 使用 goto 跳轉 } // ... 處理正常數據 ... return; error_handling: // ... 處理錯誤 ... return; } -
合規代碼示例:
void process_data(int data) { if (data < 0) { // ... 處理錯誤 ... } else { // ... 處理正常數據 ... } } -
規則解釋及益處:
goto語句會破壞代碼的結構化, 讓代碼的控制流難以追蹤, 降低代碼的可讀性和可維護性。 在大多數情況下, 可以使用if-else, while, for, break, continue等結構化控制語句來代替goto。
-
-
Rule 15.2 (強制): The goto statement shall jump to a label declared later in the same function
-
要求: goto語句應跳轉到同一函數內、goto語句后聲明的標簽。
-
解釋: 這條規則限制了 goto 語句只能向后跳轉,不能向前跳轉。 向前跳轉的 goto 語句會形成循環,可能導致死循環或難以理解的代碼邏輯。
-
非合規代碼示例:
void foo() { int x = 0; loop_start: // 向前跳轉的標簽 x++; if (x < 10) { goto loop_start; // 不允許:向前跳轉 } } -
合規代碼示例:
void foo() { int x = 0; start: if (x < 10) { // x++; goto start; //不允許, 向前跳轉了 } if (x > 10) { goto end; } x = 0; end: return; } //應該避免使用goto, 上述例子只是為了演示規則
-
-
Rule 15.3 (強制): Any label referenced by a goto statement shall be declared in the same block, or in any block enclosing the goto statement
-
要求: goto語句引用的任何標簽,應在相同塊中聲明,或者任何塊中聲明圍住goto語句。
-
解釋: 這條規則限制了 goto 語句只能在同一個代碼塊內跳轉,或者跳轉到外層代碼塊。不允許 goto 語句跳轉到其他代碼塊或內層代碼塊。
-
非合規代碼示例:
void foo() { int x = 0; if (x > 0) { goto label1; // 不允許:跳轉到其他代碼塊 } if (x < 0) { label1: // label1 不在同一個 block x = 0; } while (x < 10) { if (x == 5) { goto label2; // 不允許:跳轉到外層代碼塊 } } label2: x = 0; } -
合規示例:
避免使用goto.
-
-
Rule 15.4 (建議): There should be no more than one break or goto statement used to terminate any iteration statement
-
要求: 建議不應有超過1個break或goto語句終止任意迭代語句。
-
解釋: 在循環語句( for, while, do-while)中, 應該只使用一個break或goto語句來提前終止循環。 使用多個break或goto語句會使循環的控制流變得復雜, 降低代碼的可讀性。
-
非合規代碼示例:
void foo() { int x = 0; while (x < 10) { if (x == 5) { break; // 第一個 break } if (x == 8) { break; // 第二個 break,不允許 } x++; } } -
合規代碼示例:
void foo() { int x = 0; bool done = false; while (x < 10 && !done) { if (x == 5) { done = true; // 使用標志變量控制循環 } else if (x == 8) { done = true; // 使用標志變量控制循環 } else { x++; } } }
-
-
Rule 15.5 (建議): A function should have a single point of exit at the end
-
要求: 建議函數應在末尾有單一退出點。
-
解釋: 意思是說, 每個函數只能有一個return語句。
-
非合規代碼示例:
int process_data(int data) { if (data < 0) { return -1; // 第一個返回點 } // ... 處理數據 ... return result; // 第二個返回點 } -
合規代碼示例:
int process_data(int data) { int result = 0; // 初始化返回值 if (data < 0) { result = -1; // 設置返回值 } else { // ... 處理數據 ... } return result; // 單一返回點 } -
規則解釋及益處:
函數應在末尾有單一退出點。
-
-
Rule 15.6 (強制): The body of an iteration-statement or a selection-statement shall be a compound statement
-
要求: 迭代語句或選擇語句的本體應為復合語句。
-
解釋: 復合語句稱為語句塊, 使用大括號包裹。該條規則意思是循環語句和if語句本體, 需要使用
{}包裹。除了一種特殊情況: if語句后馬上跟著else。 -
非合規代碼示例:
if (x > 0) printf("x is positive\n"); // 單條語句,沒有使用大括號 for (int i = 0; i < 10; i++) printf("%d\n", i); // 單條語句, 沒有使用大括號 while(x > 0) x--; //單條語句, 沒有使用大括號 -
合規代碼示例:
if (x > 0) { printf("x is positive\n"); // 使用大括號 } for (int i = 0; i < 10; i++) { printf("%d\n", i); //使用大括號 } while(x > 0) { x--; } -
規則解釋及益處:
即使if、for、while等控制語句只包含一條語句, 也應該使用大括號將其括起來, 避免因為后續添加語句時忘記添加大括號而導致邏輯錯誤。
-
-
Rule 15.7 (強制): All if . . else if constructs shall be terminated with an else statement
-
要求: 所有if..else,都應該以else終止。
-
解釋: 除非沒有else,只要有else,最終必須以else終止, 處理其他情形。
-
非合規代碼示例:
if (x > 0) { // ... } else if (x < 0) { // ... } // 缺少最后的 else 分支 -
合規代碼示例:
if (x > 0) { // ... } else if (x < 0) { // ... } else { // 處理 x == 0 的情況 }
-
2.2.16 switch 語句 (Switch statements)
-
Rule 16.1 (強制): All switch statements shall be well-formed
-
要求: 所有switch語句應具有良好形式。
-
解釋: 一個結構良好的 switch 語句應符合以下要求:
- 應該有一個 switch 表達式。
- 應該至少有兩個 case 標簽。
- 應該有一個 default 標簽。
- 每個 case 標簽和 default 標簽后應該跟隨一個語句塊(可以是復合語句)。
- 除了有意設計的 fall-through 情況,每個語句塊應該以 break 語句結束。
-
非合規代碼示例:
//缺少break switch(x) { case 1: foo(); case 2: bar(); break; } //缺少default switch(y) { case 1: foo(); break; } -
合規代碼示例:
switch(x) { case 1: foo(); break; case 2: bar(); break; default: break; }
-
-
Rule 16.2 (強制): A switch label shall only be used when the most closely-enclosing compound statement is the body of a switch statement
-
要求: switch label只應用在封閉的復合語句是switch語句的主體的時候。
-
解釋: label是指case, default label。本條規則意思是, label只能出現在switch主體復合語句的最外層, 不能穿插在其他復合語句內。
-
非合規代碼示例:
switch (x) { case 1: if (y > 0) { case 2: // 不允許:case 標簽嵌套在 if 語句中 break; } break; default: break; } -
合規代碼示例:避免此類用法
-
-
Rule 16.3 (強制): An unconditional break statement shall terminate every switch-clause
-
要求: 無條件的break語句應終止每個子句。
-
解釋: 除了有意設計的 fall-through 情況,每個 case 分支的代碼塊應該以 break 語句結束。
-
例外情況: 控制流跳轉到下一個switch子句。
-
非合規代碼示例:
switch (value) { case 1: printf("case 1\n"); // 缺少 break 語句,導致 case 穿透 case 2: printf("case 2\n"); break; default: printf("default\n"); break; } -
合規代碼示例:
switch (value) { case 1: printf("case 1\n"); break; // 添加 break 語句 case 2: printf("case 2\n"); break; default: printf("default\n"); break; } // 如果需要 fall-through,可以使用注釋說明 switch (x) { case 1: case 2: // ... 處理 case 1 和 case 2 的相同邏輯 ... break; // 注意這里只有一個 break case 3: // ... break; default: // ... break; } -
規則解釋及益處:
在switch語句中,如果case分支的代碼塊沒有以break語句結束, 程序會繼續執行下一個case分支的代碼塊, 這被稱為"case 穿透"。 Case 穿透有時可以被用來實現一些特殊的邏輯, 但在大多數情況下, 它會導致邏輯錯誤。 為了避免 case 穿透, 應該在每個case分支的代碼塊末尾添加break語句。
-
-
Rule 16.4 (強制): Every switch statement shall have a default label
- 要求: 每個switch語句應有default標簽。
- 解釋: default 標簽用于處理 switch 表達式的值與所有 case 標簽都不匹配的情況。提供 default 標簽可以確保 switch 語句總是有一個出口,避免意外的行為。
-
Rule 16.5 (強制): A default label shall appear as either the first or the last switch label of a switch statement
- 要求: 默認標簽應作為第一個或最后一個switch標簽出現。
-
Rule 16.6 (強制): Every switch statement shall have at least two switch-clauses
- 要求: 每個switch語句應有至少2個switch子句。
- 解釋: 如果 switch 語句只有一個 case 標簽,那么它可以用 if 語句代替。
-
Rule 16.7 (強制): A switch-expression shall not have essentially Boolean type
- 要求: switch表達式不應具有本質上布爾類型。
- 解釋: switch 語句的表達式應該具有整數類型或枚舉類型,不應該具有布爾類型。如果 switch 表達式具有布爾類型,那么它只有兩種可能的值(true 和 false),這種情況下應該使用 if-else 語句代替。
2.2.17 函數 (Functions)
-
Rule 17.1 (強制): The features of
<stdarg.h>shall not be used- 要求:
<stdarg.h>的功能不應使用。 - 解釋:
<stdarg.h>頭文件提供了處理可變參數列表的功能。但是,可變參數列表的使用會降低代碼的可讀性和可維護性,并且可能導致類型安全問題。
- 要求:
-
Rule 17.2 (強制): Functions shall not call themselves, either directly or indirectly
- 要求: 函數不應直接或間接地調用自己。
- 解釋: 直接或間接的遞歸調用可能導致棧溢出,尤其是在嵌入式系統中,棧空間通常比較有限。
-
Rule 17.3 (強制): A function shall not be declared implicitly
-
強制: 函數不應隱式聲明。
-
解釋: 在 C90 中,如果沒有顯式地聲明函數原型,編譯器會假定函數返回 int 類型,并且對參數類型不做任何假設。這種隱式聲明可能導致類型不匹配和未定義行為。
-
非合規代碼示例:
void foo() { int x = bar(); // bar 函數沒有聲明 } -
合規代碼示例:
int bar(void); //顯示聲明 void foo() { int x = bar(); }
-
-
Rule 17.4 (強制): All exit paths from a function with non-void return type shall have an explicit return statement with an expression
-
強制: 具有非void返回類型的函數的所有退出路徑, 應具有顯式return語句。
-
非合規代碼示例:
int add(int a, int b) { // 缺少 return 語句 a + b; } int foo(int x) { if(x > 0) { return 1; } //缺少 return } -
合規代碼示例:
int add(int a, int b) { return a + b; // 顯式 return 語句 } int foo(int x) { if(x > 0) { return 1; } return 0; } -
規則解釋及益處:
對于非void返回類型的函數, 所有函數退出路徑都應該有顯式的return語句, 并且返回值。
-
-
Rule 17.5 (建議): The function argument corresponding to a parameter declared to have an array type shall have an appropriate number of elements
-
要求: 建議與被聲明為擁有數組類型的參數對應的函數參數,應具有適當的元素數量。
-
解釋: 也就是說, 實參數組元素數量, 必須與函數參數的數組元素數量相匹配。
-
非合規代碼示例:
void process_array(int arr[10]) { // 函數期望一個包含 10 個元素的數組 // ... } void foo() { int my_arr[5]; process_array(my_arr); // 不符合:傳遞了一個只有 5 個元素的數組 } -
合規代碼示例:
void process_array(int arr[10]) { // 函數期望一個包含 10 個元素的數組 // ... } void foo() { int my_arr[10]; process_array(my_arr); // 符合 } //更建議使用指針和長度 void process_array2(int *arr, size_t len) { //可以使用len判斷長度 }
-
-
Rule 17.6 (強制): The declaration of an array parameter shall not contain the static keyword between the
[ ]-
強制: 數組參數的聲明, 不應在
[]中間包含static關鍵字的參數。 -
解釋: 在 C99 中,可以在數組參數的聲明中使用 static 關鍵字來指定數組的最小大小。但是,MISRA C 禁止這種用法,因為它可能導致兼容性問題,并且不是所有編譯器都支持。
-
非合規代碼示例:
void process_array(int arr[static 10]); // 不允許:在數組參數聲明中使用 static
-
-
Rule 17.7 (強制): The value returned by a function having non-void return type shall be used
-
要求: 非void返回類型的函數,必須返回值被使用。
-
解釋: 如果一個函數具有非 void 返回類型,那么它的返回值通常包含重要的信息,例如操作結果、狀態碼或錯誤信息。忽略函數的返回值可能會導致程序邏輯錯誤或資源泄漏。
-
非合規代碼示例:
int get_data(); void foo() { get_data(); // 忽略了 get_data() 的返回值 } -
合規代碼示例:
int get_data(); void foo() { int data = get_data(); // 使用函數的返回值 // ... 使用 data ... } //如果確定不需要返回值, 可以使用(void)強制轉換 void bar() { (void)get_data(); }
-
-
Rule 17.8 (建議): A function parameter should not be modified
-
要求: 建議函數參數不應修改。
-
解釋: 函數參數具有自動存儲周期, C語言允許修改參數, 但這么做會讓人迷惑, 而且與程序員期望沖突。不熟悉C的程序員可能會修改參數, 而期望修改實參。
-
非合規代碼示例:
void foo(int x) { x = x + 1; //修改了形參 } -
合規代碼示例:
void foo(int x) { int local_x = x + 1; //使用局部變量 } //或者使用const void bar(const int x) { //x++; //編譯錯誤 }
-
2.2.18 指針和數組 (Pointers and arrays)
-
Rule 18.1 (強制): A pointer resulting from arithmetic on a pointer operand shall address an element of the same array as that pointer operand
-
要求: 指針操作數的算術運算產生的指針, 應尋址與該指針操作數相同的數組元素。
-
解釋: 也就是說, 指針指向一個數組, 與整型數進行算術運算, 得到的結果是另一個數組元素地址, 或者指向數組末尾元素的下一個位置。
-
非合規代碼示例:
int arr[5] = {1, 2, 3, 4, 5}; int *ptr = &arr[0]; ptr = ptr + 10; // 指針越界, 不允許 int arr2[5]; ptr = arr2; // 指向了不同的數組 -
合規代碼示例:
int arr[5] = {1, 2, 3, 4, 5}; int *ptr = &arr[0]; // 確保指針運算結果在數組范圍內 if (ptr + 2 < &arr[5]) { ptr = ptr + 2; } ptr = &arr[4]; ptr++; //指向 arr[4] 的下一個位置, 可以 ptr++; //不允許, 超出了數組范圍
-
-
Rule 18.2 (強制): Subtraction between pointers shall only be applied to pointers that address elements of the same array
-
要求: 指針之間的減法, 應當僅僅用于尋址相同數組的指針。
-
解釋: 只有當兩個指針指向同一個數組的元素,或者其中一個指針指向數組末尾元素的下一個位置時,才能進行指針減法運算。指針減法的結果是兩個指針之間的元素個數(ptrdiff_t 類型)。
-
非合規代碼示例:
int arr1[5]; int arr2[5]; int *ptr1 = &arr1[0]; int *ptr2 = &arr2[0]; ptrdiff_t diff = ptr2 - ptr1; // 不允許:指向不同數組的指針相減 -
合規代碼示例:
int arr[5]; int *ptr1 = &arr[0]; int *ptr2 = &arr[4]; ptrdiff_t diff = ptr2 - ptr1; // 允許:指向同一數組的指針相減 // diff 的值為 4 ptr2 = &arr[5]; //可以, 指向末尾下一個位置 diff = ptr2 - ptr1; //diff = 5
-
-
Rule 18.3 (強制): The relational operators >, >=, < and <= shall not be applied to objects of pointer type except where they point into the same object
-
要求: 關系運算符 >, >=, <, <=,不應用于對象的指針類型,除了它們指向相同對象。
-
解釋: 只有當兩個指針指向同一個數組(或結構體/聯合體)的元素,或者其中一個指針指向數組(或結構體/聯合體)末尾元素的下一個位置時,才能使用關系運算符 (>, >=, <, <=) 比較指針。
-
非合規代碼示例:
int arr1[5]; int arr2[5]; int *ptr1 = &arr1[0]; int *ptr2 = &arr2[0]; if (ptr1 < ptr2) { // 不允許:比較指向不同數組的指針 // ... } -
合規代碼示例:
int arr[5]; int *ptr1 = &arr[0]; int *ptr2 = &arr[4]; if (ptr1 < ptr2) { // 允許:比較指向同一數組的指針 // ... }
-
-
Rule 18.4 (建議): The +, -, += and -= operators should not be applied to an expression of pointer type
-
要求: 建議+,-,+=,-=運算符不應用于指針類型的表達式。
-
解釋: 注意與Rule 18.1的區別。這條規則是建議不要直接對指針使用這些運算符, 而是建議使用下標或指針遞增/遞減。
-
非合規代碼示例:
int arr[5]; int *ptr = &arr[0]; ptr = ptr + 2; // 不建議直接使用 + -
合規代碼示例:
int arr[5]; int *ptr = &arr[0]; ptr += 2; //建議使用 += ptr = &arr[2]; //建議使用取址 //或者 int index = 0; ptr = &arr[index]; index += 2; ptr = &arr[index]; //使用下標
-
-
Rule 18.5 (建議): Declarations should contain no more than two levels of pointer nesting
-
要求: 建議聲明不應包含超過2級指針嵌套。
-
解釋: 指的是同一處超過2級指針, 而不是同一行代碼。
-
非合規代碼示例:
int ***ptr; // 不允許:超過兩級指針 -
合規代碼示例:
int **ptr; // 允許:兩級指針 int *ptr2; //允許
-
-
Rule 18.6 (強制): The address of an object with automatic storage shall not be copied to another object that persists after the first object has ceased to exist
-
要求: 具有自動存儲的對象地址, 不應拷貝到 在第一個對象不存在之后, 另一個存在的對象。
-
解釋: 通過地址引用已經釋放的對象, 會導致未定義行為。
-
非合規代碼示例:
int *get_pointer() { int local_var = 10; return &local_var; // 返回局部變量的地址,不允許 } void foo() { int *ptr = get_pointer(); // ptr 現在是一個懸空指針 // ... 使用 ptr ... // 未定義行為 } -
合規代碼示例:
// 應該返回變量的值, 而不是地址 int get_value() { int local_var = 10; return local_var; } // 或者,如果確實需要返回地址,則應該使用靜態局部變量或動態分配的內存 int *get_static_pointer() { static int static_var = 10; // 靜態局部變量 return &static_var; }
-
-
Rule 18.7 (強制): Flexible array members shall not be declared
-
要求: 靈活的數組成員不應聲明。
-
解釋: 靈活的數組成員是指彈性數組。這樣會導致sizeof無法正確計算結構體大小。
-
非合規代碼示例:
struct MyStruct { int data_len; int data[]; // 不允許:靈活數組成員 };
-
-
Rule 18.8 (強制): Variable-length array types shall not be used
-
要求: 可變長度數組類型不應使用。
-
解釋: 可變長度數組 (VLA) 是 C99 引入的特性,它允許數組的大小在運行時確定。但是,VLA 的使用會帶來一些問題:
- VLA 的大小可能超出棧的限制,導致棧溢出。
- VLA 的大小可能難以預測,導致安全漏洞。
- 某些編譯器可能不支持 VLA。
-
非合規代碼示例:
void foo(int n) { int arr[n]; // 不允許:可變長度數組 // ... }
-
2.2.19 存儲重疊 (Overlapping storage)
- Rule 19.1 (強制): An object shall not be assigned or copied to an overlapping object
* 強制: 對象不應賦值或拷貝到一個重疊對象。
* 解釋: 存儲重疊,是指兩個對象的存儲空間存在相互重疊的情況,即共用了一段內存。
* 非合規代碼示例:
```c
#include <string.h>
char str[20] = "Hello, world!";
memcpy(str + 5, str, 10); // 內存區域重疊, 結果未定義,不允許
int arr[5] = {1, 2, 3, 4, 5};
arr[0] = arr[0]; //雖然沒有問題, 但是不允許
union Data {
int i;
float f;
} data;
data.i = 10;
data.f = data.i; //不允許, 雖然看起來沒有問題
```
* 合規代碼示例:
```c
#include <string.h>
#include <stdio.h>
int main() {
char str[20] = "Hello, world!";
char temp[20];
// 先將需要拷貝的內容復制到一個臨時緩沖區
strncpy(temp, str, sizeof(temp) -1);
temp[sizeof(temp) - 1] = '\0';
// 再從臨時緩沖區復制到目標位置
strncpy(str + 5, temp, sizeof(str) - 5 -1 );
str[sizeof(str) - 1] = '\0';
printf("%s\n", str); // 輸出: HelloHello, world!
return 0;
}
```
* 規則解釋及益處:
當使用memcpy等內存拷貝函數時, 如果源內存區域和目標內存區域有重疊, 會導致未定義行為。 為了避免這種情況, 可以使用memmove函數, 或者將數據先拷貝到一個臨時緩沖區, 然后再從臨時緩沖區拷貝到目標位置。
-
Rule 19.2 (建議): The union keyword should not be used
- 要求: 建議union關鍵字不應使用。
- 解釋: 聯合體各成員重疊內存, 寫一個成員會導致其他成員也發生改變。union 的使用會降低代碼的可讀性和可維護性,并且可能導致難以發現的錯誤。
2.2.20 預處理指令 (Preprocessing directives)
-
Rule 20.2 (強制): The ' , " or \ characters and the / or // character sequences shall not occur in a header file name*
-
要求:
',"或\字符和/*或//字符序列不應出現在頭文件名中。 -
解釋: 在
#include指令中使用的頭文件名中,不應包含以下字符或字符序列:- 單引號 (')
- 雙引號 (")
- 反斜杠 ()
- C 風格注釋的開始 (/*)
- C++ 風格注釋的開始 (//)
這些字符或字符序列在頭文件名中具有特殊含義,或者可能導致編譯錯誤。包含這些字符可能表明頭文件名不正確,或者試圖進行某種形式的代碼注入。
-
非合規代碼示例:
#include "my_header'.h" // 不允許:頭文件名包含單引號 #include "my_"header.h" // 不允許:頭文件名包含雙引號 #include "my_header\.h" // 不允許:頭文件名包含反斜杠 #include "my_/*comment*/header.h" // 不允許:頭文件名包含注釋 -
合規代碼示例:
#include "my_header.h" // 正確:頭文件名只包含允許的字符 #include <my_header.h> // 正確
-
-
Rule 20.3 (強制): The #include directive shall be followed by either a
<filename>or "filename" sequence-
要求:
#include指令應跟著<文件名>或"文件名"序列。 -
解釋:
#include指令用于包含其他頭文件。它有兩種形式:#include <filename>:用于包含標準庫頭文件或編譯器提供的頭文件。#include "filename":用于包含用戶自定義的頭文件。
這條規則要求#include指令后面必須緊跟這兩種形式之一,不能有其他內容。
-
非合規代碼示例:
#include MY_HEADER // 不允許:缺少尖括號或雙引號 #define HEADER "my_header.h" #include HEADER // 不允許:使用宏代替文件名 -
合規代碼示例:
#include <stdio.h> // 正確:包含標準庫頭文件 #include "my_header.h" // 正確:包含用戶自定義頭文件
-
-
Rule 20.4 (強制): A macro shall not be defined with the same name as a keyword
-
要求: 宏定義不應使用相同的名稱,作為關鍵字。
-
解釋: C 語言的關鍵字具有特殊的含義,不能用作標識符(變量名、函數名、宏名等)。如果將宏定義為與關鍵字同名,會導致編譯錯誤或未定義行為。
-
非合規代碼示例:
#define int my_int // 不允許:宏名與關鍵字 int 同名 #define if my_if //不允許 -
合規代碼示例:避免此類用法
-
-
Rule 20.5 (建議): #undef should not be used
-
要求: 建議#undef不應使用。
-
解釋:
#undef指令用于取消已定義的宏。雖然#undef在某些情況下可能有用,但它會降低代碼的可讀性和可維護性。過度使用#undef可能導致宏的定義和取消定義之間的關系混亂,難以追蹤。 -
非合規代碼示例:
#define MAX_SIZE 100 // ... 使用 MAX_SIZE ... #undef MAX_SIZE // 不建議:取消定義宏 // ... 現在 MAX_SIZE 不再有效 ... -
合規代碼示例:
// 盡量避免使用 #undef // 如果確實需要不同的 MAX_SIZE,可以使用不同的宏名 #define MAX_SIZE_PHASE1 100 // ... #define MAX_SIZE_PHASE2 200
-
-
Rule 20.6 (強制): Tokens that look like a preprocessing directive shall not occur within a macro argument
-
要求: 看起來像預處理指令的令牌, 不應出現在宏參數中。
-
解釋: 如果宏參數中包含看起來像預處理指令的令牌(例如 #ifdef、#include 等),可能會導致宏展開錯誤或意外的行為。
-
非合規代碼示例:
#define MY_MACRO(arg) printf("%s\n", #arg) MY_MACRO(#include <stdio.h>); // 不允許:宏參數中包含 #include -
合規代碼示例:避免此類用法
-
-
Rule 20.7 (強制): Expressions resulting from the expansion of macro parameters shall be enclosed in parentheses
-
要求: 宏參數擴展產生的表達式, 應包含在括號中。
-
解釋: 在宏定義中, 如果宏參數出現在表達式中, 應該將宏參數用括號括起來。 這樣可以避免宏展開后因為運算符優先級問題而導致結果與預期不符。
-
非合規代碼示例:
#define SQUARE(x) x * x int result = SQUARE(2 + 3); // 展開為 2 + 3 * 2 + 3, 結果為 11 -
合規代碼示例:
#define SQUARE(x) ((x) * (x)) int result = SQUARE(2 + 3); // 展開為 ((2 + 3) * (2 + 3)), 結果為 25 -
規則解釋及益處:
在宏定義中, 如果宏參數出現在表達式中, 應該將宏參數用括號括起來。 這樣可以避免宏展開后因為運算符優先級問題而導致結果與預期不符。
-
-
Rule 20.8 (強制): The controlling expression of a #if or #elif preprocessing directive shall evaluate to 0 or 1
-
要求: #if或#elif預處理指令的控制表達式, 應該評估為0或1。
-
解釋:
#if和#elif預處理指令用于條件編譯。它們的控制表達式應該是一個常量表達式,其結果應該是 0 或 1。0 表示條件為假,1 表示條件為真。 -
非合規代碼示例:
#if 2 // 不建議:控制表達式的值不是 0 或 1 // ... #endif #define VALUE 10 #if VALUE > 5 //不建議, 應該定義為bool類型 // #endif -
合規代碼示例:
#if 1 // 正確:控制表達式的值為 1 // ... #endif #if 0 // 正確:控制表達式的值為 0 // ... #endif #define VALUE 1 #if VALUE == 1 // #endif
-
-
Rule 20.9 (強制): All identifiers used in the controlling expression of #if or #elif preprocessing directives shall be #define'd before evaluation
-
要求: 所有用在#if或#elif預處理指定的控制表達式, 應在計算值之前用#define定義。
-
解釋: 在
#if或#elif的控制表達式中使用的所有標識符都應該是已定義的宏。如果使用了未定義的宏,預處理器會將其替換為 0,這可能導致意外的結果。 -
非合規代碼示例:
#if UNDEFINED_MACRO == 1 // 不允許:使用了未定義的宏 // ... #endif -
合規代碼示例:
#define MY_MACRO 1 #if MY_MACRO == 1 // 正確:使用了已定義的宏 // ... #endif
-
-
Rule 20.10 (建議): The # and ## preprocessor operators should not be used
-
要求: 建議
#和##預處理運算符不應使用。 -
解釋:
#(字符串化運算符) 和##(標記粘貼運算符) 是 C 預處理器提供的兩個特殊運算符。#運算符將其操作數轉換為字符串字面量。##運算符將其左右兩側的操作數連接成一個單獨的標記。
MISRA C 建議避免使用這兩個運算符,因為它們會降低代碼的可讀性和可維護性,并且可能導致意外的宏展開結果。
-
非合規代碼示例:
#define STRINGIZE(x) #x const char *str = STRINGIZE(hello); // 使用 # 運算符 #define CONCAT(x, y) x ## y int xy = CONCAT(10, 20); // 使用 ## 運算符 -
合規代碼示例:
//避免使用
-
-
Rule 20.11 (強制): A macro parameter immediately following a # operator shall not immediately be followed by a ## operator
-
要求: 緊跟在
#操作符的宏定義參數,##不應緊隨其后。 -
非合規代碼示例:
#define A(x) #x##B //不允許
-
-
Rule 20.12 (強制): A macro parameter used as an operand to the # or ## operators, which is itself subject to further macro replacement, shall only be used as an operand to these operators
-
要求: 用作
#或##運算符操作數的宏參數,其本身需要進一步的宏替換,只能用作這些運算符的操作數。 -
非合規代碼示例:
#define A(x) #x #define B(x) x #define C(x) B(A(x)) //不符合
-
-
Rule 20.13 (強制): A line whose first token is # shall be a valid preprocessing directive
-
要求: 第一個標記為
#的行, 應為有效預處理指令。 -
解釋: 以
#開頭的行應該是一個有效的預處理指令,例如#include、#define、#ifdef等。如果#后面跟隨的不是有效的預處理指令,會導致編譯錯誤。 -
非合規代碼示例:
# this is not a valid directive // 不允許:# 后面不是有效的預處理指令
-
-
Rule 20.14 (強制): All #else, #elif and #endif preprocessor directives shall reside in the same file as the #if, #ifdef or #ifndef directive to which they are related
-
要求: 所有
#else,#elif和#endif預處理指令應與它們相關的#if,#ifdef或#ifndef指令位于同一文件。 -
解釋: 這條規則是為了保證條件編譯指令的完整性和一致性。
#if、#ifdef、#ifndef、#else、#elif和#endif應該成對出現,并且位于同一個文件中。 -
非合規代碼示例:
// header1.h #ifndef MY_HEADER_H #define MY_HEADER_H // header2.h #else // 不允許:#else 與 #ifndef 不在同一個文件中 #endif -
合規代碼示例:
// header.h #ifndef MY_HEADER_H #define MY_HEADER_H // ... #else // #endif
-
2.2.21 標準庫 (Standard libraries)
-
Rule 21.1 (強制): #define and #undef shall not be used on a reserved identifier or reserved macro name
-
要求:
#define和#undef不應用于保留標識符或者保留宏名稱。 -
解釋: 保留標識符, 保留宏名稱, 是指標準庫用到的標識符或名稱。例如,
__LINE__, errno。 -
非合規代碼示例:
#define errno my_errno // 不允許:重定義標準庫中的標識符 #undef NULL //不允許
-
-
Rule 21.2 (強制): A reserved identifier or macro name shall not be declared
-
要求: 保留標識符或宏名稱不應聲明。
-
解釋: 直接
#include對應頭文件即可, 不應再單獨聲明。 -
非合規代碼示例:
extern int errno; // 不允許:聲明標準庫中的保留標識符 int NULL; //不允許
-
-
Rule 21.3 (強制): The memory allocation and deallocation functions of
<stdlib.h>shall not be used- 要求:
<stdlib.h>內存分配和回收函數, 不應使用。 - 解釋: 指malloc/free/calloc/realloc系列函數。 動態內存分配(malloc, free等)可能導致內存泄漏, 碎片, 以及難以預測的程序行為。 在安全關鍵的嵌入式系統中, 應該避免使用動態內存分配。
- 要求:
-
Rule 21.4 (強制): The standard header file
<setjmp.h>shall not be used- 要求: 標準頭文件
<setjmp.h>不應使用。 - 解釋: 該頭文件包含setjmp, longjmp指令。setjmp 和 longjmp 函數用于實現非局部跳轉,它們會破壞程序的結構化,使代碼難以理解和維護, 并且可能導致資源泄漏。
- 要求: 標準頭文件
-
Rule 21.5 (強制): The standard header file
<signal.h>shall not be used- 要求: 標準頭文件
<signal.h>不應使用。 - 解釋: 該頭文件包含信號處理設施。信號處理機制是操作系統提供的, 用于處理異步事件。 在嵌入式系統中, 信號處理可能會導致時序問題和不可預測的行為。
- 要求: 標準頭文件
-
Rule 21.6 (強制): The Standard Library input/output routines shall not be used.
- 要求: 標準庫輸入輸出例程不應使用。
- 解釋: 該規則用于
<stdio.h>提供的函數, 對于寬字符版本是<wchar.h>。標準庫 I/O 函數(如 printf、scanf、fopen、fclose 等)可能會帶來安全風險和不可預測的行為,特別是在資源受限的嵌入式系統中。例如:- printf 和 scanf 的格式化字符串漏洞。
- 文件操作可能導致資源泄漏或競爭條件。
- 標準 I/O 函數可能依賴于底層操作系統,降低代碼的可移植性。
-
Rule 21.7 (強制): The atof, atoi, atol and atoll functions of
<stdlib.h>shall not be used-
要求:
<stdlib.h>的atof, atoi, atol和atoll函數, 不應使用。 -
解釋: 當不能轉換時, 這些函數有未定義行為。這些字符串轉換函數在遇到無效輸入時會產生未定義行為,例如:
- 輸入字符串不能轉換為有效的數字。
- 轉換結果超出目標類型的范圍。
應該使用 strtol、strtoul、strtod 等函數代替,這些函數可以提供更可靠的錯誤處理。
-
非合規代碼示例:
char *str = "abc"; int x = atoi(str); // 未定義行為 -
合規代碼示例:
#include <stdlib.h> #include <errno.h> char *str = "123"; char *endptr; long value = strtol(str, &endptr, 10); // 10 表示十進制 if (*endptr != '\0') { // 處理轉換錯誤 } if ((errno == ERANGE && (value == LONG_MAX || value == LONG_MIN))) { //處理溢出 }
-
-
Rule 21.8 (強制): The library functions abort, exit, getenv and system of
<stdlib.h>shall not be used- 要求:
<stdlib.h>的庫函數abort, exit, getenv和system, 不應使用。 - 解釋:
- abort():立即終止程序,可能不會進行正常的清理操作(如刷新緩沖區、關閉文件等)。
- exit():正常終止程序,但可能依賴于底層操作系統。
- getenv():獲取環境變量,可能導致安全風險(如泄露敏感信息)或依賴于不可移植的平臺特性。
- system():執行系統命令,可能導致安全風險(如命令注入)和不可預測的行為。
- 要求:
-
Rule 21.9 (強制): The library functions bsearch and qsort of
<stdlib.h>shall not be used- 要求:
<stdlib.h>的庫函數bsearch和qsort, 不應使用。 - 解釋: bsearch 和 qsort 函數是通用的搜索和排序函數,它們依賴于函數指針來實現自定義的比較邏輯。在安全關鍵的嵌入式系統中,函數指針的使用可能會帶來風險。此外,qsort 函數的實現可能不是穩定的,并且在最壞情況下可能具有 O(n^2) 的時間復雜度。
- 要求:
-
Rule 21.10 (強制): The Standard Library time and date routines shall not be used
- 要求: 標準庫時間和日期例程, 不應使用。
- 解釋:
<time.h>中等設施, 都不能使用。時間和日期函數沒有指定、定義和實現定義的行為。在嵌入式系統中,時間處理可能非常關鍵,標準庫的時間和日期函數可能無法滿足特定的需求,或者可能依賴于底層操作系統,降低代碼的可移植性。
-
Rule 21.11 (強制): The standard header file
<tgmath.h>shall not be used- 要求: 標準頭文件
<tgmath.h>不應使用。 - 解釋:
<tgmath.h>頭文件提供了類型泛型數學函數,它可以根據參數類型自動選擇合適的數學函數(如 sin、cos、sqrt 等)。但是,這種類型泛型機制可能會導致代碼的可讀性降低,并且可能引入意外的類型轉換。
- 要求: 標準頭文件
-
Rule 21.12 (建議): The exception handling features of
<fenv.h>should not be used- 要求: 建議
<fenv.h>的異常處理功能, 不應使用。 - 解釋:
<fenv.h>頭文件提供了對浮點環境的訪問和控制,包括浮點異常、舍入模式等。但是,浮點異常處理機制可能依賴于底層硬件和操作系統,并且可能導致代碼的行為難以預測。
- 要求: 建議
2.2.22 資源 (Resources)
-
Rule 22.1 (強制): All resources obtained dynamically by means of Standard Library functions shall be explicitly released
-
要求: 應明確釋放通過標準庫功能動態獲得的所有資源。
-
解釋: 資源包括:
- 動態分配的內存 (雖然 MISRA C 禁止使用動態內存分配,但如果使用了,則必須正確釋放)。
- 打開的文件。
- 創建的互斥鎖、信號量等同步對象。
- 其他通過標準庫函數獲得的系統資源。
如果程序獲得了這些資源但沒有及時釋放,會導致資源泄漏,最終可能導致程序崩潰或系統不穩定。
-
示例:
#include <stdio.h> int main() { FILE *fp = fopen("myfile.txt", "w"); // 打開文件 if (fp == NULL) { // 處理錯誤 return -1; } // ... 使用文件 ... fclose(fp); // 關閉文件,釋放資源 return 0; }
-
-
Rule 22.2 (強制): A block of memory shall only be freed if it was allocated by means of a Standard Library function
-
強制: 只有通過標準庫函數分配內存塊時,才能釋放內存塊。
-
解釋: malloc/free 配套使用(如果使用了動態內存分配)。
-
非合規代碼示例:
int x; int *p = &x; free(p); //錯誤, 不能釋放局部變量的地址
-
-
Rule 22.3 (強制): The same file shall not be open for read and write access at the same time on different streams
-
要求: 相同文件不應打開, 在同一時間不同流上進行讀寫訪問。
-
解釋: 同時讀寫同一文件, 可能導致文件數據異常。
-
非合規代碼示例:
#include <stdio.h> int main() { FILE *fp1 = fopen("data.txt", "r"); FILE *fp2 = fopen("data.txt", "w"); // 不允許:同時以讀寫模式打開同一文件 // ... fclose(fp1); fclose(fp2); return 0; } -
合規代碼示例:
#include <stdio.h> int main() { //先讀取 FILE *fp1 = fopen("data.txt", "r"); //使用 fp1 fclose(fp1); //再寫入 FILE *fp2 = fopen("data.txt", "w"); //使用fp2 fclose(fp2); return 0; }
-
-
Rule 22.4 (強制): There shall be no attempt to write to a stream which has been opened as read-only
-
強制: 不應嘗試寫作為只讀打開的流。
-
非合規代碼示例:
#include <stdio.h> int main() { FILE *fp = fopen("data.txt", "r"); if(fp != NULL) { fprintf(fp, "Trying to write"); // 錯誤:嘗試寫入只讀文件 } fclose(fp); return 0; }
-
-
Rule 22.5 (強制): A pointer to a FILE object shall not be dereferenced
-
強制: 指向 FILE 對象的指針不應解引用。
-
解釋: FILE 對象是標準庫用于表示文件流的結構體。FILE 對象的內部結構是實現定義的,不應該直接訪問其成員。這樣做會導致代碼不可移植,并且可能破壞 FILE 對象的內部狀態。
-
非合規代碼示例:
#include <stdio.h> int main() { FILE *fp = fopen("data.txt", "r"); if(fp != NULL) { //int fd = fp->_fileno; // 錯誤:嘗試訪問 FILE 對象的內部成員, 不同平臺成員可能不一樣 //int ch = *fp; // 錯誤, 解引用 FILE 指針 } fclose(fp); return 0; } -
合規示例:
#include <stdio.h> int main() { FILE *fp = fopen("myfile.txt", "r"); if (fp == NULL) { perror("fopen"); return 1; } int ch; while ((ch = fgetc(fp)) != EOF) { // 使用標準 I/O 函數 putchar(ch); } if (ferror(fp)) { perror("fgetc"); } fclose(fp); return 0; }
-
-
Rule 22.6 (強制): The value of a pointer to a FILE shall not be used after the associated stream has been closed
-
強制: 關聯流關閉后, FILE 指針不應再使用。
-
解釋: 一旦使用 fclose 函數關閉了文件流,與之關聯的 FILE 指針將變為無效。任何嘗試使用該無效指針的行為都是未定義的,可能導致程序崩潰或產生不可預測的結果。
-
非合規代碼示例:
#include <stdio.h> int main() { FILE *fp = fopen("data.txt", "r"); // ... fclose(fp); fprintf(fp, "Trying to write"); // 錯誤:在文件關閉后使用 FILE 指針 return 0; }
-
-
Rule 22.7 (強制): The macro EOF shall only be used with the unmodified return value of any Standard Library function capable of returning EOF
-
要求: 宏 EOF 只得與任何能夠返回 EOF 的標準庫函數的未修改返回值比較。
-
解釋: EOF 是一個宏,通常定義為 -1,表示文件結束或輸入錯誤。許多標準 I/O 函數(如 fgetc、fgets、fscanf 等)在遇到文件結束或發生錯誤時會返回 EOF。這條規則要求:
- 只能將 EOF 與這些函數的返回值進行比較。
- 不能修改這些函數的返回值,然后再與 EOF 比較。
- 不能將EOF與其他值進行比較。
-
非合規代碼示例:
#include <stdio.h> int main() { int ch; FILE *fp = fopen("myfile.txt", "r"); if (fp == NULL) { return 1; } ch = fgetc(fp); if (ch + 1 == EOF + 1) { // 錯誤:修改了返回值 // ... } if (1 == EOF) { //錯誤, 不應該與其他值比較 } fclose(fp); return 0; } -
合規代碼示例:
#include <stdio.h> int main() { int ch; FILE *fp = fopen("myfile.txt", "r"); if(fp == NULL) { return 1; } while ((ch = fgetc(fp)) != EOF) { // 正確:直接比較返回值和 EOF putchar(ch); } fclose(fp); return 0; }
-
-
Rule 22.8 (強制): The value of errno shall be set to zero prior to a function call which sets errno on failure
-
要求: 在調用 errno 設置函數之前必須將 errno 值設置為零。
-
解釋: errno 是一個全局變量,用于存儲最近一次發生的錯誤代碼。許多標準庫函數在發生錯誤時會設置 errno 的值。這條規則要求在調用可能設置 errno 的函數之前,先將 errno 清零。這是因為 errno 的值不會被自動清除,如果不清零,可能會錯誤地認為發生了錯誤。
-
非合規代碼示例:
#include <stdio.h> #include <errno.h> #include <math.h> int main() { double x = -1.0; double result = sqrt(x); // 產生錯誤, 設置 errno if (errno != 0) { // 可能錯誤地認為發生了錯誤, 因為 errno 可能在之前就被設置了 perror("sqrt"); } return 0; } -
合規代碼示例:
#include <stdio.h> #include <errno.h> #include <math.h> int main() { errno = 0; // 清零 errno double x = -1.0; double result = sqrt(x); if (errno != 0) { // 正確:檢查 errno perror("sqrt"); } return 0; }
-
-
Rule 22.9 (強制): The value of errno shall be tested against zero after calling a function which is permitted to set errno
-
要求: 調用 errno 設置函數后必須檢測 errno 值是否為零。
-
解釋: 在調用可能設置 errno 的函數之后,應該立即檢查 errno 的值是否為非零。如果 errno 為非零,則表示發生了錯誤,應該進行相應的錯誤處理。
-
非合規代碼示例:
#include <math.h> int main() { double x = -1.0; double result = sqrt(x); // 可能會設置 errno // 沒有檢查 errno } -
合規代碼示例:
#include <stdio.h> #include <errno.h> #include <math.h> int main() { errno = 0; // 清零 errno double x = -1.0; double result = sqrt(x); if (errno != 0) { // 正確:檢查 errno perror("sqrt"); // 處理錯誤 } return 0; }
-
-
Rule 22.10 (強制): The value of errno shall only be tested when the last function to be called was a Standard Library function which is permitted to set errno
-
要求: 只有上一個被調用的函數是允許設置 errno 的標準庫函數的情況下才能檢測 errno 值。
-
解釋:
并非所有標準庫函數都會設置 errno。在調用非標準庫函數或不設置 errno 的標準庫函數后,errno 的值可能不再反映最近一次發生的錯誤。
因此,只有在調用了允許設置 errno 的標準庫函數后,立即檢查 errno 的值才有意義。可以查閱相關函數的文檔, 確認是否會設置errno. -
非合規代碼示例:
#include <stdio.h> #include <errno.h> #include <math.h> int my_function() { // ... 自定義函數,不設置 errno ... return 0; } int main() { errno = 0; double x = -1.0; double result = sqrt(x); //sqrt會設置errno my_function(); // 調用了自定義函數 if (errno != 0) { // 錯誤:在調用了不設置 errno 的函數后檢查 errno // ... } return 0; } -
合規代碼示例:
#include <stdio.h> #include <errno.h> #include <math.h> int main() { errno = 0; // 清零 errno double x = -1.0; double result = sqrt(x); if (errno != 0) { // 正確:在調用可能設置 errno 的函數后立即檢查 perror("sqrt"); } return 0; }
-
3. MISRA C:2012 的執行強度
MISRA C:2012 標準中的指令和規則根據其重要性和執行方式,被分為不同的類別:
- 強制 (Mandatory): 最嚴格的類別,必須嚴格遵守,任何違反強制規則的代碼都應被視為不合格。
- 要求 (Required): 同樣需要嚴格遵守,但相比強制規則,可能在某些特定情況下可以進行偏差解釋 (deviation)。
- 建議 (Advisory): 最佳實踐建議,鼓勵盡可能遵守,但可以根據項目具體情況進行權衡和調整。
此外,規則還分為 可靜態檢查 (Statically Checkable) 和 不可靜態檢查 (Non-Statically Checkable) 兩類。可靜態檢查的規則可以通過靜態代碼分析工具自動檢測,而不可靜態檢查的規則則需要人工代碼審查或運行時監控來驗證。
4. 如何應用 MISRA C:2012
在實際項目中應用 MISRA C:2012 標準,需要從項目初期就將 MISRA C 規范納入開發流程中:
- 制定 MISRA C 執行策略: 明確項目需要遵守的 MISRA C 規則級別 (例如,全部強制和要求規則,部分建議規則),以及偏差解釋的流程和標準。
- 選擇并配置靜態代碼分析工具: 選擇支持 MISRA C:2012 標準的靜態代碼分析工具 (例如, PC-lint, Klocwork, Coverity, QA-C, Polyspace 等),并將其集成到開發環境和持續集成流程中,實現代碼的自動化 MISRA C 檢查。
- 代碼審查: 對于靜態代碼分析工具無法檢測的規則,或者需要進行偏差解釋的情況,需要進行人工代碼審查。代碼審查應該由熟悉 MISRA C 標準的資深工程師進行。
- 使用支持MISRA C的庫: 盡可能使用已經符合MISRA C標準的庫函數。
- 代碼生成: Simulink,Etas,EB Tresos,Vector全家桶等相關軟件支持生成符合MISRA C標準的代碼。
5. 總結
MISRA C:2012 標準是一套嚴謹而全面的 C 語言編碼規范,是提升嵌入式系統代碼質量、保障系統安全性的重要工具,此套標準我認為不僅僅只能用于汽車行業,而是應該推廣出來,最后附上速查手冊。
附錄:MISRA C:2012 規則速查手冊
MISRA C:2012 規則覆蓋情況
| 所有類別 | 支持 | 總計 | 可判定 | 支持 | 總計 | 不可判定 | 支持 | 總計 | 覆蓋比例 |
|---|---|---|---|---|---|---|---|---|---|
| 強制 (Mandatory) | 121 | 121 | 36 | 54 | 157 | 175 | 90% | ||
| 要求 (Required) | 88 | 88 | 23 | 32 | 111 | 120 | 93% | ||
| 建議 (Advisory) | 28 | 28 | 2 | 11 | 30 | 39 | 77% |
MISRA C:2012 規則列表
| Analyze 編碼規則 | 規則編號 | 規則名稱 | 類別 | 可判定性 | 是否支持 |
|---|---|---|---|---|---|
| C2301 | Dir 1.1 | 如果程序的輸出取決于某實現定義的行為,則必須記錄并理解該行為 | 要求 | 不可判定 | 否 |
| C2302 | Dir 2.1 | 所有源文件編譯過程中不得有編譯錯誤 | 要求 | 不可判定 | 否 |
| C2303 | Dir 3.1 | 所有代碼必須可被追溯到文檔化的需求 | 要求 | 不可判定 | 否 |
| C2304 | Dir 4.1 | 必須盡量減少運行錯誤 | 要求 | 不可判定 | 否 |
| C2305 | Dir 4.2 | 應記錄所有匯編語言的使用 | 建議 | 不可判定 | 否 |
| C2306 | Dir 4.3 | 匯編語言必須被封裝并隔離 | 要求 | 不可判定 | 否 |
| C2307 | Dir 4.4 | 不應“注釋掉”代碼段 | 建議 | 不可判定 | 否 |
| C2308 | Dir 4.5 | 在同一命名空間內,應確保外形重合的標識符的排版不易混淆 | 建議 | 不可判定 | 否 |
| C2309 | Dir 4.6 | 應使用表示大小和符號性的類型定義(typedef)代替基本數據類型 | 建議 | 不可判定 | 否 |
| C2310 | Dir 4.7 | 如果函數返回了錯誤信息,那么必須檢測該錯誤信息 | 要求 | 不可判定 | 否 |
| C2311 | Dir 4.8 | 如果一個翻譯單元內,指向結構體或聯合體的指針永不被解引用,那么 應該隱藏該對象的實現 | 建議 | 不可判定 | 否 |
| C2312 | Dir 4.9 | 在可以使用函數或類函數宏的情況下,應優先使用函數 | 建議 | 不可判定 | 否 |
| C2313 | Dir 4.10 | 應采取措施預防頭文件的內容被多次包含 | 要求 | 不可判定 | 否 |
| C2314 | Dir 4.11 | 必須檢查傳遞給庫函數的值的有效性 | 要求 | 不可判定 | 否 |
| C2315 | Dir 4.12 | 不得使用動態內存分配 | 要求 | 不可判定 | 是 |
| C2316 | Dir 4.13 | 應以適當順序調用對資源進行運算的函數 | 建議 | 不可判定 | 否 |
| C2317 | Dir 4.14 | 應檢查來源于外部的值的有效性 | 要求 | 不可判定 | 否 |
| C2201 | Rule 1.1 | 程序不得違反C語言標準語法和約束,不得超出實現的翻譯限制 | 要求 | 可判定 | 是 |
| C2202 | Rule 1.2 | 不應使用語言擴展 | 建議 | 不可判定 | 否 |
| C2203 | Rule 1.3 | 不得出現未定義或嚴重的未指定行為 | 要求 | 不可判定 | 否 |
| C2204 | Rule 1.4 | 不得使用新涌現的語言特性 | 要求 | 可判定 | 是 |
| C2007 | Rule 2.1 | 項目不得含有不可達代碼 | 要求 | 不可判定 | 是 |
| C2006 | Rule 2.2 | 不得有死代碼 | 要求 | 不可判定 | 是 |
| C2005 | Rule 2.3 | 項目不應含有未使用的類型聲明 | 建議 | 可判定 | 是 |
| C2004 | Rule 2.4 | 項目不應含有未使用的標簽(tag)聲明 | 建議 | 可判定 | 是 |
| C2003 | Rule 2.5 | 項目不應含有未使用的宏聲明 | 建議 | 可判定 | 是 |
| C2002 | Rule 2.6 | 項目不應含有未使用的標記(label)聲明 | 建議 | 可判定 | 是 |
| C2001 | Rule 2.7 | 函數中不應有未使用的形參 | 建議 | 可判定 | 是 |
| C2102 | Rule 3.1 | 不得在注釋中使用字符序列/*和// | 要求 | 可判定 | 是 |
| C2101 | Rule 3.2 | 不得在//注釋中使用行拼接 | 要求 | 可判定 | 是 |
| C1002 | Rule 4.1 | 八進制和十六進制轉義序列必須被終止 | 要求 | 可判定 | 是 |
| C1001 | Rule 4.2 | 不應使用三字母詞(trigraphs) | 建議 | 可判定 | 是 |
| C1109 | Rule 5.1 | 不得使用重名的外部標識符 | 要求 | 可判定 | 是 |
| C1108 | Rule 5.2 | 在同一作用域和命名空間內聲明的標識符不得重名 | 要求 | 可判定 | 是 |
| C1107 | Rule 5.3 | 內部作用域聲明的標識符不得隱藏外部作用域聲明的標識符 | 要求 | 可判定 | 是 |
| C1106 | Rule 5.4 | 宏標識符不得重名 | 要求 | 可判定 | 是 |
| C1105 | Rule 5.5 | 標識符不得與宏的名稱重名 | 要求 | 可判定 | 是 |
| C1104 | Rule 5.6 | 類型定義(typedef)名稱必須是唯一標識符 | 要求 | 可判定 | 是 |
| C1103 | Rule 5.7 | 標簽名稱必須是唯一標識符 | 要求 | 可判定 | 是 |
| C1102 | Rule 5.8 | 必須使用唯一標識符定義含有外部鏈接的對象或函數 | 要求 | 可判定 | 是 |
| C1101 | Rule 5.9 | 應使用唯一標識符定義含有內部鏈接的對象或函數 | 建議 | 可判定 | 是 |
| C0702 | Rule 6.1 | 只得使用合適的類型來聲明位域(bit-fields) | 要求 | 可判定 | 是 |
| C0701 | Rule 6.2 | 用一位命名的位域不得為有符號類型 | 要求 | 可判定 | 是 |
| C0904 | Rule 7.1 | 不得使用八進制常量 | 要求 | 可判定 | 是 |
| C0903 | Rule 7.2 | 所有表現為無符號類型的整型常量都必須使用“u”或“U”后綴 | 要求 | 可判定 | 是 |
| C0902 | Rule 7.3 | 小寫字母“I”不得用作字面量后綴 | 要求 | 可判定 | 是 |
| C0901 | Rule 7.4 | 不得將字符串字面量賦值給對象,除非對象類型為“指向 const 修飾的 char 的指針” | 要求 | 可判定 | 是 |
| C0514 | Rule 8.1 | 必須明確指定類型 | 要求 | 可判定 | 是 |
| C0513 | Rule 8.2 | 函數類型必須為帶有命名形參的原型形式 | 要求 | 可判定 | 是 |
| C0512 | Rule 8.3 | 對同一對象或函數的所有聲明必須使用同樣的名字和類型修飾符 | 要求 | 可判定 | 是 |
| C0511 | Rule 8.4 | 對含有外部鏈接的對象或函數進行定義時,必須有可見的兼容聲明 | 要求 | 可判定 | 是 |
| C0510 | Rule 8.5 | 外部對象或函數只得在一個文件里聲明一次 | 要求 | 可判定 | 是 |
| C0509 | Rule 8.6 | 含有外部鏈接的標識符必須有且只有一個外部定義 | 要求 | 可判定 | 是 |
| C0508 | Rule 8.7 | 不應使用外部鏈接定義僅在一個翻譯單元中引用的函數和對象 | 建議 | 可判定 | 是 |
| C0507 | Rule 8.8 | 對含有內部鏈接的對象和函數進行的所有聲明都必須使用靜態(static) 存儲類說明符 | 要求 | 可判定 | 是 |
| C0506 | Rule 8.9 | 如果對象標識符只在一個函數中出現,那么應該在塊作用域(block scope)中定義該對象 | 建議 | 可判定 | 是 |
| C0505 | Rule 8.10 | 必須使用靜態存儲類別聲明內聯函數 | 要求 | 可判定 | 是 |
| C0504 | Rule 8.11 | 對含有外部鏈接的數組進行定義時,應顯式指定其大小 | 建議 | 可判定 | 是 |
| C0503 | Rule 8.12 | 枚舉列表里一個隱式指定的枚舉常量的值應是唯一的 | 要求 | 可判定 | 是 |
| C0502 | Rule 8.13 | 指針應盡量指向const修飾的類型 | 建議 | 不可判定 | 否 |
| C0501 | Rule 8.14 | 不得使用restrict類型修飾符 | 要求 | 可判定 | 是 |
| C1205 | Rule 9.1 | 對于具有自動存儲周期的對象,不得在設定它的值之前讀取它的值 | 強制 | 不可判定 | 是 |
| C1204 | Rule 9.2 | 聚合或聯合體的初始化器應包含在大括號“{}”內 | 要求 | 可判定 | 是 |
| C1203 | Rule 9.3 | 不得對數組進行部分初始化 | 要求 | 可判定 | 是 |
| C1202 | Rule 9.4 | 最多只得初始化一次對象的元素 | 要求 | 可判定 | 是 |
| C1201 | Rule 9.5 | 對數組對象進行指定初始化時,必須顯式指定數組大小 | 要求 | 可判定 | 是 |
| C0808 | Rule 10.1 | 操作數不得為不合適的基本類型 | 要求 | 可判定 | 是 |
| C0807 | Rule 10.2 | 不得在加減運算中不恰當地使用基本字符類表達式 | 要求 | 可判定 | 是 |
| C0806 | Rule 10.3 | 表達式的值不得賦給更窄的基本類型,也不得賦給不同的基本類型類別 | 要求 | 可判定 | 是 |
| C0805 | Rule 10.4 | 執行尋常算術轉換的運算符的兩個操作數必須屬于同一基本類型類別 | 要求 | 可判定 | 是 |
| C0804 | Rule 10.5 | 表達式的值不應強制轉換為不合適的基本類型 | 建議 | 可判定 | 是 |
| C0803 | Rule 10.6 | 復合表達式的值不得賦給具有更寬基本類型的對象 | 要求 | 可判定 | 是 |
| C0802 | Rule 10.7 | 尋常算術轉換中,如果運算符的一個操作數為復合表達式,則另一個操 作數不得具有更寬類型 | 要求 | 可判定 | 是 |
| C0801 | Rule 10.8 | 復合表達式的值不得強制轉換為不同基本類型類別或更寬類型 | 要求 | 可判定 | 是 |
| C1409 | Rule 11.1 | 指向函數的指針和任何其他類型之間不得相互轉換 | 要求 | 可判定 | 是 |
| C1408 | Rule 11.2 | 指向不完整類型的指針和任何其他類型之間不得相互轉換 | 要求 | 可判定 | 是 |
| C1407 | Rule 11.3 | 指向對象類型的指針和指向不同對象類型的指針之間不得強制轉換 | 要求 | 可判定 | 是 |
| C1406 | Rule 11.4 | 指向對象的指針和整數類型之間不應相互轉換 | 建議 | 可判定 | 是 |
| C1405 | Rule 11.5 | 指向void的指針不應轉換為指向對象的指針 | 建議 | 可判定 | 是 |
| C1404 | Rule 11.6 | 指向void的指針和算術類型之間不得強制轉換 | 要求 | 可判定 | 是 |
| C1403 | Rule 11.7 | 指向對象的指針和非整數類型的算術類型之間不得強制轉換 | 要求 | 可判定 | 是 |
| C1402 | Rule 11.8 | 強制轉換不得移除指針所指向類型的任何const或volatile修飾 | 要求 | 可判定 | 是 |
| C1401 | Rule 11.9 | 宏NULL必須為整數類型空指針常量的唯一允許形式 | 要求 | 可判定 | 是 |
| C0605 | Rule 12.1 | 應明確表達式中操作數的優先級 | 建議 | 可判定 | 是 |
| C0604 | Rule 12.2 | 移位運算符的右操作數的范圍下限為零,上限須比左操作數的基本類型 的位寬度小一 | 要求 | 不可判定 | 是 |
| C0603 | Rule 12.3 | 不得使用逗號運算符(,) | 建議 | 可判定 | 是 |
| C0602 | Rule 12.4 | 對常量表達式進行求值不應導致整數回繞 | 建議 | 可判定 | 是 |
| C0601 | Rule 12.5 | sizeof運算符的操作數不得是聲明為“數組類型”的函數形參 | 強制 | 可判定 | 是 |
| C1606 | Rule 13.1 | 初始化器列表不得含有持續的副作用(persistent side effect) | 要求 | 不可判定 | 是 |
| C1605 | Rule 13.2 | 采用不同的求值順序時(只要允許采用該順序),表達式的值和表達式 的持續的副作用必須相等 | 要求 | 不可判定 | 是 |
| C1604 | Rule 13.3 | 含有一個自增(++)或自減(--)運算符的完整表達式,除因自增或自 減運算符引起的副作用外,不應含有其他潛在副作用 | 建議 | 可判定 | 是 |
| C1603 | Rule 13.4 | 不得使用賦值運算符的結果 | 建議 | 可判定 | 是 |
| C1602 | Rule 13.5 | 邏輯與(&&)和邏輯或(運算符的右操作數不得含有持續的副作用) | 要求 | 不可判定 | 是 |
| C1601 | Rule 13.6 | sizeof運算符的操作數不得包含任何有潛在副作用的表達式 | 強制 | 可判定 | 是 |
| C1704 | Rule 14.1 | 循環計數器不得為基本浮點類型 | 要求 | 不可判定 | 是 |
| C1703 | Rule 14.2 | for循環必須格式良好 | 要求 | 不可判定 | 是 |
| C1702 | Rule 14.3 | 控制表達式不得為不變量 | 要求 | 不可判定 | 是 |
| C1701 | Rule 14.4 | if語句和迭代語句的控制表達式必須為基本布爾類型 | 要求 | 可判定 | 是 |
| C1807 | Rule 15.1 | 不應使用goto語句 | 建議 | 可判定 | 是 |
| C1806 | Rule 15.2 | 同一函數中,goto語句只得跳轉到在其后聲明的標記(label) | 要求 | 可判定 | 是 |
| C1805 | Rule 15.3 | goto語句引用的標記必須在同一代碼塊或上級代碼塊中聲明 | 要求 | 可判定 | 是 |
| C1804 | Rule 15.4 | 對于任何迭代語句,最多只應使用一個break或goto語句進行終止 | 建議 | 可判定 | 是 |
| C1803 | Rule 15.5 | 函數結尾應只有一個退出點 | 建議 | 可判定 | 是 |
| C1802 | Rule 15.6 | 迭代語句或分支語句的主體必須為復合語句 | 要求 | 可判定 | 是 |
| C1801 | Rule 15.7 | 所有if ... else if構造都必須以一個else語句終止 | 要求 | 可判定 | 是 |
| C1907 | Rule 16.1 | 所有switch語句必須格式良好(well-formed) | 要求 | 可判定 | 是 |
| C1906 | Rule 16.2 | switch標記只得出現在形成switch語句主體的復合語句最外層 | 要求 | 可判定 | 是 |
| C1905 | Rule 16.3 | 每個switch子句(switch-clause)都必須以一個無條件break語句終止 | 要求 | 可判定 | 是 |
| C1904 | Rule 16.4 | 每個switch語句都必須有default標記 | 要求 | 可判定 | 是 |
| C1903 | Rule 16.5 | 在switch語句中,default標記必須是第一個或最后一個switch標記 | 要求 | 可判定 | 是 |
| C1902 | Rule 16.6 | 每個switch語句都必須有兩個或以上switch子句 | 要求 | 可判定 | 是 |
| C1901 | Rule 16.7 | switch表達式不得是基本布爾類型 | 要求 | 可判定 | 是 |
| C1508 | Rule 17.1 | 不得使用<stdarg.h>的特性 |
要求 | 可判定 | 是 |
| C1507 | Rule 17.2 | 函數不得直接或間接調用自身 | 要求 | 不可判定 | 是 |
| C1506 | Rule 17.3 | 不得隱式聲明函數 | 強制 | 可判定 | 是 |
| C1505 | Rule 17.4 | 所有函數退出路徑,如果為非空(non-void)返回類型,則必須有一個 包含表達式的顯式return語句 | 強制 | 可判定 | 是 |
| C1504 | Rule 17.5 | 如果函數形參聲明為數組類型,其對應的實參必須具有適當數量的元素 | 建議 | 不可判定 | 是 |
| C1503 | Rule 17.6 | 聲明數組形參時,[]內不得包含關鍵字static |
強制 | 可判定 | 是 |
| C1502 | Rule 17.7 | 函數返回值若不為非空返回類型(non-void return type),則必須被使 用 | 要求 | 可判定 | 是 |
| C1501 | Rule 17.8 | 不應修改函數形參 | 建議 | 不可判定 | 是 |
| C1308 | Rule 18.1 | 對指針操作數進行算術運算得來的指針只得用于尋址同一數組的元素 | 要求 | 不可判定 | 是 |
| C1307 | Rule 18.2 | 指針之間的減法運算只得用于尋址同一數組元素的指針 | 要求 | 不可判定 | 是 |
| C1306 | Rule 18.3 | 大小比較運算符>、>=、<和<=不得用于指針類型的對象,除非兩個指針 指向同一對象 |
要求 | 不可判定 | 是 |
| C1305 | Rule 18.4 | +、-、+=和-=運算符不得用于指針類型的表達式 |
建議 | 可判定 | 是 |
| C1304 | Rule 18.5 | 聲明應含有最多兩層嵌套指針 | 建議 | 可判定 | 是 |
| C1303 | Rule 18.6 | 不得將自動存儲對象的地址復制給在該對象不復存在后仍然存在的另一 個對象 | 要求 | 不可判定 | 是 |
| C1302 | Rule 18.7 | 不得聲明靈活數組成員(flexible array members) | 要求 | 可判定 | 是 |
| C1301 | Rule 18.8 | 不得使用變長數組(variable-length array) | 要求 | 可判定 | 是 |
| C0302 | Rule 19.1 | 不得將對象賦值或復制給與其重疊的對象 | 強制 | 不可判定 | 是 |
| C0301 | Rule 19.2 | 不應使用關鍵字union | 建議 | 可判定 | 是 |
| C0114 | Rule 20.1 | #include指令之前僅應出現預處理指令或注釋 |
建議 | 可判定 | 是 |
| C0113 | Rule 20.2 | 頭文件名中不得出現字符'、"或\、以及字符序列/*和// |
要求 | 可判定 | 是 |
| C0112 | Rule 20.3 | #include指令后面必須是<filename>或"filename"序列 |
要求 | 可判定 | 是 |
| C0111 | Rule 20.4 | 定義宏名稱時不得與關鍵字同名 | 要求 | 可判定 | 是 |
| C0110 | Rule 20.5 | 不得使用#undef |
建議 | 可判定 | 是 |
| C0109 | Rule 20.6 | 宏實參中不得有形似預處理指令的詞符 | 要求 | 可判定 | 是 |
| C0108 | Rule 20.7 | 宏形參擴展得到的表達式必須在括號內 | 要求 | 可判定 | 是 |
| C0107 | Rule 20.8 | 預處理指令#if或#elif的控制表達式求值結果必須為0或1 |
要求 | 可判定 | 是 |
| C0106 | Rule 20.9 | 預處理指令#if或#elif的控制表達式中的所有標識符必須被#define定義才 能求值 |
要求 | 可判定 | 是 |
| C0105 | Rule 20.10 | 不應使用預處理運算符#和## |
建議 | 可判定 | 是 |
| C0104 | Rule 20.11 | 如果宏形參后面緊跟#運算符,則不得再緊跟##運算符 |
要求 | 可判定 | 是 |
| C0103 | Rule 20.12 | 用作#或##運算符的操作數的宏形參,如果自身需要進一步進行宏替 換,則只得作為#或##的操作數使用 |
要求 | 可判定 | 是 |
| C0102 | Rule 20.13 | 以#開始的代碼行必須為有效預處理指令 |
要求 | 可判定 | 是 |
| C0101 | Rule 20.14 | 所有預處理指令#else、#elif和#endif都必須和它們對應的#if、#ifdef和 #ifndef指令位于同一文件中 |
要求 | 可判定 | 是 |
| C0420 | Rule 21.1 | #define和#undef不得用于保留標識符(reserved identifier)或保留宏名 稱(reserved macro name) |
要求 | 可判定 | 是 |
| C0419 | Rule 21.2 | 不得聲明保留標識符(reserved identifier) 和保留宏名稱(reserved macro name) | 要求 | 可判定 | 是 |
| C0418 | Rule 21.3 | 不得使用<stdlib.h>提供的內存分配和回收(deallocation) 函數 |
要求 | 可判定 | 是 |
| C0417 | Rule 21.4 | 不得使用標準頭文件<setjmp.h> |
要求 | 可判定 | 是 |
| C0416 | Rule 21.5 | 不得使用標準頭文件<signal.h> |
要求 | 可判定 | 是 |
| C0415 | Rule 21.6 | 不得使用標準庫輸入/輸出函數 | 要求 | 可判定 | 是 |
| C0414 | Rule 21.7 | 不得使用<stdlib.h>提供的標準庫函數atof,atoi, atol,以及atoll函數 |
要求 | 可判定 | 是 |
| C0413 | Rule 21.8 | 不得使用<stdlib.h>提供的標準庫終止函數(termination function) |
要求 | 可判定 | 是 |
| C0412 | Rule 21.9 | 不得使用<stdlib.h>提供的標準庫函數bsearch和qsort函數 |
要求 | 可判定 | 是 |
| C0411 | Rule 21.10 | 不得使用標準庫中的時間(time)和日期(date)函數 | 要求 | 可判定 | 是 |
| C0410 | Rule 21.11 | 不得使用標準頭文件<tgmath.h> |
要求 | 可判定 | 是 |
| C0409 | Rule 21.12 | 不應使用<fenv.h>的異常處理特性 |
建議 | 可判定 | 是 |
| C0408 | Rule 21.13 | 傳遞給<ctype.h>函數的值必須能夠表示為無符號char或EOF類型 |
強制 | 不可判定 | 是 |
| C0407 | Rule 21.14 | 不得使用標準庫函數 memcmp 比較空終止字符串(null terminated string) | 要求 | 不可判定 | 是 |
| C0406 | Rule 21.15 | 指向標準庫函數memcpy,memmove和memcmp的指針實參必須全部為 指向兼容類型的限定或非限定版本的指針 | 要求 | 可判定 | 是 |
| C0405 | Rule 21.16 | 標準庫函數memcmp的指針實參必須指向指針類型,或者指向基本有符 號類型,基本布爾類型或基本枚舉類型 | 要求 | 可判定 | 是 |
| C0404 | Rule 21.17 | 使用<string.h>提供的字符串處理函數(string handling functions)產生 的訪問不得超越指針形參引用的對象的邊界 |
強制 | 不可判定 | 是 |
| C0403 | Rule 21.18 | size_t實參若傳遞給任意<string.h>提供的函數,則必須有恰當的值 |
強制 | 不可判定 | 是 |
| C0402 | Rule 21.19 | 標準庫函數localeconv, getenv, setlocale或strerror返回的指針只得作 為const修飾類型的指針使用 | 強制 | 不可判定 | 是 |
| C0401 | Rule 21.20 | 標準庫函數 asctime / ctime / gmtime / localtime / localeconv / getenv / setlocale / strerror 返回的指針后面不得緊跟對同一函數的調用 | 強制 | 不可判定 | 是 |
| C0421 | Rule 21.21 | 不得使用<stdlib.h>中的標準庫函數system |
要求 | 可判定 | 是 |
| C0210 | Rule 22.1 | 通過標準庫函數動態獲取的所有資源都必須被顯式釋放 | 要求 | 不可判定 | 是 |
| C0209 | Rule 22.2 | 只有通過標準庫函數分配的內存塊才能被釋放 | 強制 | 不可判定 | 是 |
| C0208 | Rule 22.3 | 不得在不同文件流上同時打開同一文件進行讀寫訪問 | 要求 | 不可判定 | 是 |
| C0207 | Rule 22.4 | 不得對只讀文件流進行寫入操作 | 強制 | 不可判定 | 是 |
| C0206 | Rule 22.5 | 不得解引用(dereference)指向FILE對象的指針 | 強制 | 不可判定 | 是 |
| C0205 | Rule 22.6 | 不得在相關文件流關閉后使用FILE指針的值 | 強制 | 不可判定 | 是 |
| C0204 | Rule 22.7 | 宏EOF只得與任何能夠返回EOF的標準庫函數的未修改返回值比較 | 要求 | 不可判定 | 是 |
| C0203 | Rule 22.8 | 在調用errno設置函數之前必須將errno值設置為零 | 要求 | 不可判定 | 是 |
| C0202 | Rule 22.9 | 調用errno設置函數后必須檢測errno值是否為零 | 要求 | 不可判定 | 是 |
| C0201 | Rule 22.10 | 只有上一個被調用的函數是errno設置函數的情況下才能檢測errno值 | 要求 | 不可判定 | 是 |

浙公網安備 33010602011771號