文章总结: JSFAVC++编码标准是洛克希德·马丁为F-35项目开发的高度受限C++子集,旨在确保航空航天等安全关键系统的软件可靠性、可移植性和可维护性。标准通过禁用大多数C++特性(如异常、多重继承)来换取稳定性,本质上是将C++写成CWithClass。文档详细规定了代码大小与复杂度限制(如函数不超过200行、圈复杂度不超过20)、环境要求(如仅允许基本源字符集、禁止三字母和双字母)以及库使用规则(如禁止errno、setjmp和stdio库),并定义了Should、Will、Shall三种规则等级。 综合评分: 80 文章分类: 安全开发
【第一部分】写出军规级代码:JSF AV C++编码标准
原创
crackme.net crackme.net
crackme安全实验室
2026年7月5日 14:28 河南
在小说阅读器读本章
去阅读
JSF AV 全程 Joint Strike Fighter Air Vehicle(联合攻击战斗机空中飞行器)。它是洛克希德·马丁公司为 F-35“肥电 II” 联合攻击战斗机项目开发的一种高度受限的 C++ 编程语言子集。
该标准的主要目标是确保为航空航天、国防以及其他安全关键型嵌入式系统开发出安全、可靠、可测试且行为确定的软件。由于标准 C++ 存在未定义行为和不可预测的内存管理机制,JSF AV 标准系统地禁止或严格限制特定的语言特性。
总体而言,这套规则所体现的理念本质上是对 C++ 相对于 C 的语言哲学的延伸。即通过为“不安全”的功能提供“更安全”的替代方案,来避免低级特性中已知的问题。从本质上讲,程序是在一个超集的“更安全”子集中编写的。
~~看不懂上文的翻译腔长难句吧,其实我也看不懂。简而言之,这套标准本质上是禁用 C++ 的大多数特性以换取稳定性,也就是把 C++ 写成 C With Class~~
一般设计
高质量的代码应具备以下核心特性:
- 1. 可靠性 (Reliability):执行代码需以可预测的方式始终满足所有需求。
- 2. 可移植性 (Portability):源代码不应依赖于特定的编译器或链接器。
- 3. 可维护性 (Maintainability):代码应保持一致、易读、设计简洁且易于调试。
- 4. 可测试性 (Testability):通过最小化每个软件模块的代码规模、复杂度和静态路径数,来促进测试和维护。
- 5. 可重用性 (Reusability):鼓励设计可重用的组件,以减少冗余的开发和测试工作,从而降低成本。
- 6. 可扩展性 (Extensibility):系统设计应支持扩展,以便在需求变更时通过局部调整而非大规模修改来应对。
- 7. 可读性 (Readability):代码应易于阅读、理解和掌握。
耦合与内聚
- • 内聚 (Cohesion) 衡量的是模块内部各部分结合的紧密程度。它关注模块内的元素是否适合组合在一起。
- • 耦合 (Coupling) 衡量的是系统不同模块之间的交互程度。它关注模块之间连接的紧密性。
高耦合的软件具有以下特征:
- • 许多函数紧密绑定硬件或其他外部软件源。
- • 许多函数访问全局数据。
虽然高耦合有时不可避免,但应遵循以下指南将其最小化:
- • 将硬件和外部软件接口限制在少量函数中。
- • 减少使用全局数据。
- • 最小化暴露实现细节。
规则等级说明
该标准中三种不同的规则等级:Should、Will 和 Shall。这些规则等级通过加粗字体在文本中标识,代表了不同的约束力和验证要求。
- • Should 通常表示建议性规则,违反此类规则只需软件工程负责人批准。(AV Rule 4)
- • Will 表示强制性要求规则,违反此类规则需要工程负责人和产品经理的双重审批(AV Rule 5 & 7)
- • Shall 标准绝对强制性要求规则,违反此类规则需要工程负责人和产品经理的双重审批,且必须在代码文件中记录偏离原因(AV Rule 5, 6 & 7)
术语
~~八股面经这一块~~
| 术语 | 说明 |
| — | — |
| Abstract Base Class (抽象基类) | 不能直接实例化,仅作为派生其他类的基类;通常包含至少一个纯虚函数。 |
| Abstract Data Type (抽象数据类型) | 内部实现被隐藏,仅通过访问函数进行创建和操作,便于修改实现而不影响外部代码。 |
| Accessor Function (访问函数) | 用于获取数据成员值的函数(即 getter)。 |
| Catch Clause (捕获子句) | 以 catch 关键字开头,用于处理特定类型异常的代码块。 |
| Class (类) | 用户自定义类型,封装了数据成员和操作这些数据的成员函数。 |
| Class Template (类模板) | 定义一族类的蓝图,通过传入类型或常量参数来生成具体的类。 |
| Compilation Unit (编译单元) | 预处理后提交给编译器进行语法检查和编译的源代码。 |
| Concrete Type (具体类型) | 不含虚函数的类型,对象可直接在栈上分配和操作,无需指针或引用。 |
| Constant Member Function (常量成员函数) | 承诺不修改任何数据成员的成员函数。 |
| Constructor (构造函数) | 用于初始化对象的特殊成员函数。 |
| Copy Constructor (拷贝构造函数) | 第一个参数为同类型引用的构造函数,用于基于已有对象创建新对象。 |
| Dead Code (死代码) | 因设计错误导致无法执行或使用的代码/数据,且不对应任何系统需求。 |
| Declaration (声明) | 告知编译器变量或函数的名称与类型,但不提供具体实现。 |
| Default Constructor (默认构造函数) | 无需传入参数即可调用的构造函数。 |
| Definition (定义) | 提供函数或变量的完整实现细节。 |
| Enumeration Type (枚举类型) | 一组显式声明的符号化整数常量集合。 |
| Exception (异常) | 运行时检测到的程序异常状况,通过异常处理机制统一管理。 |
| Forwarding Function (转发函数) | 仅负责调用另一个函数,自身不包含其他逻辑的函数。 |
| Function Template (函数模板) | 定义一族函数的蓝图,通过传入类型或常量参数来生成具体的函数。 |
| Identifier (标识符) | 用于命名变量、常量、函数或类型的名称,可包含前缀、名称和后缀。 |
| Iterator (迭代器) | 用于遍历数据结构中元素的对象。 |
| Macro (宏) | 通过 #define 定义的文本替换标记,编译前会被替换为指定字符串。 |
| Multiple Inheritance (多重继承) | 一个新类同时从多个基类派生。 |
| Mutator Function (修改函数) | 用于设置数据成员值的函数(即 setter)。 |
| One Definition Rule (单一定义规则) | 程序中每个实体必须有且仅有一个定义;若头文件导致重复,所有定义必须完全一致。 |
| Overloaded Function Name (重载函数名) | 同一名称对应多个参数类型不同的函数。 |
| Overridden Member Function (重写成员函数) | 在派生类中重新定义的基类虚函数。 |
| Built-in Data Type (内置数据类型) | 语言本身预定义的类型,如 int。 |
| Protected Members (保护成员) | 仅可在派生类的成员函数中访问的数据或函数。 |
| Public Members (公有成员) | 可通过类实例在任何地方访问的数据或函数。 |
| Pure Virtual Function (纯虚函数) | 声明中带 = 0 的虚函数,使类变为抽象类,且必须在派生类中重写。 |
| Reference (引用) | 已有变量的别名,在类型后使用 & 声明。 |
| Scope (作用域) | 名称(如变量)可见且可用的代码上下文范围。 |
| Side Effect (副作用) | 表达式求值过程中附带产生的变量状态改变。 |
| Structure (结构体) | 用户自定义类型,其成员默认为公有(public)。 |
| Typedef (类型别名) | 为已有数据类型指定的另一个名称。 |
| Unqualified Type (未限定类型) | 不带 const 或 volatile 限定符的类型。 |
| User-defined Data Type (用户自定义类型) | 由程序员通过 class、struct、union、enum 或模板实例化定义的类型。 |
代码大小和复杂度
AV Rule 1:函数长度限制
规定(Will):任何单个函数(或方法)的逻辑源代码行数(L-SLOCs)不得超过 200 行。
理由:过长的函数往往复杂度高,导致难以理解、难以测试。
AV Rule 2:禁止自修改代码
规定(Shall):严禁使用自修改代码(Self-modifying code)。
理由:自修改代码容易出错,且极难阅读、测试和维护。
AV Rule 3:圈复杂度限制
规定(Shall):所有函数的圈复杂度(Cyclomatic Complexity number)不得超过 20。
理由:限制函数的复杂性以提高质量。
例外情况:包含多个 case 标签的 switch 语句所在的函数可以超过此限制。
环境
语言
AV Rule 8:C++ 语言标准限制
规定(Shall):所有代码必须符合 ISO/IEC 14882:2002(E) C++ 标准(C++03)。
理由:ISO/IEC 14882 是定义 C++ 编程语言的国际标准,遵循该标准可确保所有代码行为明确且定义良好。
字符集
AV Rule 9:源字符集限制
规定(Shall):仅允许使用 C++ 基本源字符集中指定的字符(共96个),包括空格、水平制表符、垂直制表符、换页符、换行符以及91个图形字符。
a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
0 1 2 3 4 5 6 7 8 9
_ { } [ ] # ( ) < > % : ; . ? * + -
/ ^ & | ~ ! = , \ " ’
理由:这是保证代码兼容性和可读性的最小必需字符集。
例外情况:如果输入/输出用途涉及外语(例如向飞行员显示信息),则本节规则可能需要相应修改。
AV Rule 10:字符类型值范围限制
规定(Will):字符类型的值必须限制在 ISO 10646-1 标准的一个已定义且有文档记录的子集内。
理由:ISO 10646-1 是字符映射的国际标准,对于基本源字符集,其映射对应于 ASCII 映射,可确保字符编码的一致性。
AV Rule 11 & 12:禁止使用三字母(Trigraph)和双字母(Digraphs)
三字母:早期 C/C++ 标准为缺少部分标点符号的老式键盘设计:由 ?? 开头的三个字符,在编译时直接转义成单个符号。
| 三字母 | 转义后字符 | | — | — | | ??= | # | | ??/ | \ | | ??’ | ^ | | ??( | [ | | ??) | ] | | ??< | { | | ??> | } | | ??! | | | | ??- | ~ |
双字母同理。
| 双字母 | 转义后字符 | | — | — | | <: | [ | | :> | ] | | <% | { | | %> | } | | %: | # | | %:%: | ## |
规定(Will):代码中不得使用三字母和双字母。
理由:为了提高代码的可读性;三字母和双字母虽用于替代某些终端缺失的字符,但会显著降低代码清晰度。
备注:通常可通过编译器标志禁用,如 Green Hills 编译器的 –no_alternative_tokens
AV Rule 13:禁止使用多字节字符和宽字符串字面量
规定(Will):不得使用多字节字符和宽字符串字面量。
理由:多字节字符和宽字符可能由多个字节组成,且多字节字符的某些行为是由具体实现定义的,会导致不可移植和未定义风险。
AV Rule 14:字面量后缀使用大写字母
规定(Shall):字面量后缀必须使用大写字母而非小写字母。
理由:为了提高可读性,避免小写后缀与数字混淆(例如 l 易被误认为数字 1)。
const int64 fs_frame_rate = 64l; // 这是 64l 还是 641?
const int64 fs_frame_rate = 64L; // 最佳实践
运行时检查
AV Rule 15:运行时检查机制
规定(Shall):必须提供运行时检查机制(防御性编程)。
理由:对于 SEAL 1 或 SEAL 2 级软件,必须采取措施以确保软件和系统功能的正确运行。
库
AV Rule 16:第三方库准入要求
规定(Shall):与安全关键(即 SEAL 1)代码一起使用的库,必须是通过 DO-178B A 级认证或符合 SEAL 1 标准的 C/C++ 库。这包括运行时库函数以及 C/C++ 标准库函数;若为内部开发库,则必须采用与其他所有安全关键软件相同的软件开发流程进行开发。
理由:确保安全性,保证所依赖的库满足安全关键系统的严格质量与验证要求。
例外情况:未来当出现通过认证的 C++ 标准库版本时,这些经认证的库也将被允许在本规则下使用。
标准库
AV Rule 17:禁止使用 errno 错误指示器
规定(Shall):不得使用错误指示器 errno。
理由:errno 的行为通常是实现定义的或依赖于本地环境,容易导致不可预测的错误和移植性问题。
例外情况:如果没有其他合理方式向应用程序传达错误条件(例如第三方数学库使用 errno 报告下溢/溢出或超出范围/域条件),且其设计和实现已得到明确定义和文档记录,则允许使用。
AV Rule 18:禁止使用 offsetof 宏
规定(Shall):不得使用 <stddef.h> 库中的 offsetof 宏。
理由:该宏的行为可能是未指定的或实现定义的,容易引发内存布局相关的错误。
AV Rule 19:禁止使用 locale 相关设施
规定(Shall):不得使用 <locale.h> 头文件及 setlocale 函数。
理由:本地化(Locale)功能具有强烈的本地特定性,会导致代码行为随运行环境变化,增加不确定性和错误风险。
AV Rule 20:禁止使用 setjmp 和 longjmp
规定(Shall):不得使用 setjmp 宏和 longjmp 函数。
理由:非局部跳转会破坏正常的程序控制流,导致资源泄漏、状态不一致等未定义或难以追踪的行为。
AV Rule 21:禁止使用信号处理设施
规定(Shall):不得使用 <signal.h> 中的信号处理设施。
理由:信号处理机制的行为高度依赖实现且异步特性使其极易引入竞态条件和未定义行为。
AV Rule 22:禁止使用 stdio 输入输出库
规定(Shall):不得使用 <stdio.h> 输入/输出库。
理由:标准I/O库的行为可能包含未指定或实现定义的部分,且在安全关键系统中通常缺乏必要的确定性和可控性。
AV Rule 23:禁止使用字符串转数值函数
规定(Shall):不得使用 <stdlib.h> 中的 atof、atoi 和 atol 库函数。
理由:这些函数在字符串转换失败时的行为定义不良(如不报告错误、精度丢失),容易导致静默数据错误。
例外情况:若确需使用,必须在其设计和实现已被充分定义并文档化(特别是关于精度和转换失败处理)的前提下方可使用。
AV Rule 24:禁止使用进程与环境控制函数
规定(Shall):不得使用 <stdlib.h> 中的 abort、exit、getenv 和 system 库函数。
理由:这些函数涉及进程终止、环境变量访问和外部命令执行,其行为高度依赖运行环境且可能引发未定义或不可控的系统状态。
AV Rule 25:禁止使用时间处理函数
规定(Shall):不得使用 <time.h> 库中的时间处理函数。
理由:时间函数的行为常受本地时区、系统时钟实现等因素影响,具有本地特定性和实现依赖性,易导致不可靠的时间计算。
预处理指令
AV Rule 26:预处理指令白名单限制
规定(Shall):仅允许使用以下四种预处理指令:
- •
#ifndef - •
#define - •
#endif - •
#include
理由:预处理器不理解 C++ 语义,应将预处理器的使用严格限制在必要的场景中,凡是能用 C++ 语言特性实现的功能均不应使用预处理器。
ifndef 和 endif 预处理指令
AV Rule 27:头文件多重包含防护机制
规定(Will):必须使用 #ifndef、#define 和 #endif 组合来防止同一头文件被多次包含,不得使用其他技术(如 #pragma once 等)来实现此目的。
理由:以标准化的方式消除同一头文件的重复包含问题,确保跨编译器和跨平台的一致性。
#ifndef Header_filename
#define Header_filename
// Header declarations…
#endif
AV Rule 28:条件编译指令用途限定
规定(Will):#ifndef 和 #endif 预处理指令仅能按 AV Rule 27 的规定用于防止头文件多重包含,不得用于其他任何形式的条件代码编译。
理由:条件代码编译应保持在最低限度,因为其会显著增加测试和维护的难度与复杂性。
define 预处理指令
AV Rule 29:禁止使用宏定义内联函数
规定(Shall):不得使用 #define 预处理指令创建内联宏,应使用内联函数(inline functions)替代。
理由:内联函数不需要文本替换,且在带参数调用时表现良好(例如会执行类型检查),避免了宏替换带来的副作用和类型安全问题。
AV Rule 30:禁止使用宏定义常量值
规定(Shall):不得使用 #define 预处理指令定义常量值,应在变量声明中使用 const 限定符来指定常量。
理由:const 变量遵循作用域规则,接受类型检查,且不需要可能引起混淆或误导的文本替换。
例外情况:仅当常量由第三方模块通用定义时允许例外(例如标准头文件中通常使用 #define 定义 NULL,为兼容第三方工具可将其视为宏处理)。
AV Rule 31:限定 define 的唯一合法用途
规定(Will):#define 预处理指令仅能作为防止同一头文件被多次包含的技术手段的一部分来使用。
理由:虽然 #define 可用于条件编译、内联宏和常量定义,但本规则明确限定其唯一允许的用途仅为配合 AV Rule 27 防止头文件重复包含,其他用途均已被前述规则禁止。
include 预处理指令
AV Rule 32:限定 include 指令的包含对象
规定(Will):#include 预处理指令仅能用于包含头文件。
理由:为了保持代码清晰,.cpp 文件中包含的文件应仅限于相关的头文件,避免包含非标准扩展或其他源文件导致编译依赖混乱。
例外情况:对于模板类或模板函数的定义,若代码被分割为独立的头文件和实现文件,则允许在头文件中包含该实现文件。此时实现文件在逻辑上属于头文件的一部分,且不可单独编译。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:crackme安全实验室 crackme.net crackme.net《【第一部分】写出军规级代码:JSF AV C++编码标准》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论