MISRA C 2012指令4.1下库函数忽略指针检查的合规性问询
MISRA C 2012 Directive 4.1要求尽量减少运行时故障,针对指针解引用进一步明确:除非已知指针非NULL,否则解引用前必须检查其是否为NULL。
编写执行简单操作的底层库时,每个函数都做输入指针检查会导致代码体积膨胀,还会降低简单操作的可读性。本文以控制器中计算向量平方欧几里得范数的库为例,给出两种实现方案:
方案1:指针检查实现
库代码
/* file vector.c */ bool_t bVectorNormSq(float32_t pf32Vec[], uint8_t u8Len, float32_t *pf32NormSq) { uint8_t u8n; bool bRet = false; /* Check pointers */ if( (pf32Vec != NULL) && (pf32NormSq != NULL) ) { *pf32NormSq = 0.0f; for(u8n = 0U; u8n < u8Len; u8n++) { *pf32NormSq += (pf32Vec[u8n] * pf32Vec[u8n]); } bRet = true; } else { /* Do not alter pf32NormSq (unknown if valid pointer) */ bRet = false; } return bRet; } /* EOF */
调用方代码
/* file controller.c */ /* ... */ bool_t bControllerStep(void) { float32_t pf32MyVec[3] = { 0 }; float32_t f32MyNorm = 0.0f; /* ... */ /* MISRA C 2012 Rule 17.7, Call will always be successful, thus return value not checked */ (void)bVectorNormSq(pf32MyVec, 3U, &f32MyNorm); /* ... */ } /* EOF */
方案2:调用方保证输入有效性,库仅做开发期断言
库代码
/* file vector.c */ /** * @note This library assumes that valid pointers will be supplied, * pointers are NOT checked before they are used. */ /* Assert macro expands to "(void)(CONDITION)" for NDEBUG defined */ #ifdef NDEBUG VECTOR_ASSERT( CONDITION ) (void)(CONDITION) #else /* ... */ #endif /* NDEBUG */ float32_t f32VectorNormSq(float32_t pf32Vec[], uint8_t u8Len) { float32_t f32Norm = 0.0f; uint8_t u8n = 0U; VECTOR_ASSERT(pf32Vec!=NULL); for(u8n = 0U; u8n < u8Len; u8n++) { f32NormSq += (pf32Vec[u8n] * pf32Vec[u8n]); } } /* EOF */
调用方代码
/* file controller.c */ /* ... */ bool_t bControllerStep(void) { float32_t pf32MyVec[3] = { 0 }; float32_t f32MyNorm = 0.0f; /* ... */ f32MyNorm = f32VectorNormSq(pf32MyVec, 3U); /* ... */ } /* EOF */
方案对比与问题引出
方案1中,调用方bControllerStep()能确定传入的数组pf32MyVec是本地栈上定义的,不可能为NULL,因此直接忽略了库函数的返回值。这种场景在库的使用中很常见,会导致调用方频繁忽略返回值,久而久之容易让程序员产生懈怠——哪怕遇到真正需要处理的错误(比如除零),也会习惯性忽略返回值。
方案2则明确假设调用方会提供有效指针,通过注释告知使用者这一前提,仅在开发阶段用断言检查NULL指针,部署时禁用断言。这种设计如果保留布尔返回值,能提醒程序员:库操作的失败可能源于指针无效之外的其他原因(比如除零),使用计算结果前必须谨慎,避免懈怠风险。
技术问题
- 在示例场景下,库忽略指针或其他简单输入检查是否符合MISRA C规范?
- 除了在源码中记录缺失的输入检查外,是否还需要满足其他条件,比如提交正式偏差申请?
背景说明
本人是航空航天控制方向的学生,希望通过遵循MISRA C等标准编写控制算法,理解实际控制系统的正确编码方式。假设代码运行在故障等级为灾难性(故障会导致人员死亡)的实际系统中。本人了解SAE ARP4754、SAE ARP4761、DO-178C等软硬件工程标准,本次仅关注硬件上执行的指令,不涉及冗余异构硬件、需求、评审、测试等内容。
补充说明:涉事库位于代码栈底层,外部输入(如传感器数据)已做净化处理,希望避免盲目遵循“始终检查所有输入”的规则,陷入盲目模仿编程(Cargo cult programming)的误区。
问题解答
问题1:是否符合MISRA C规范?
符合,但有严格前提:
- 必须明确证明输入指针不可能为NULL:示例中调用方传入的是栈上数组的地址,本身不可能是NULL,且库处于底层、外部输入已做净化,所有调用场景都能保证输入指针有效。
- MISRA C 2012 Directive 4.1的核心是“已知指针非NULL时可跳过检查”,只要能通过静态分析、代码评审或设计文档证明指针必然有效,就不违反规范。
- 开发阶段的断言是合规的补充手段:断言能在调试期捕获意外的无效指针,同时不影响部署后的代码性能和体积,符合MISRA对调试与发布代码的区分要求。
问题2:是否需要提交正式偏差申请?
需要提交偏差申请,原因如下:
- 尽管场景满足“已知指针非NULL”的前提,但MISRA要求对任何偏离规范的显式行为(包括跳过强制的输入检查)都要记录并申请偏差。
- 偏差申请中需要明确:
- 跳过检查的具体场景(如底层库、输入已净化、调用方均为内部模块且能保证指针有效)
- 验证手段(静态分析报告、代码评审记录、调用方的输入保证机制)
- 风险控制措施(开发期断言、测试用例覆盖所有调用场景)
- 对于灾难性故障等级的系统,偏差申请是合规流程的必要环节,能确保所有决策都有可追溯的依据。
内容的提问来源于stack exchange,提问作者VictorS

