Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.2 希尔伯特之梦——ISO 26262的精神祖先

1900年夏,巴黎

1900年8月8日,巴黎索邦大学,第二届国际数学家大会。

上午九点,一个38岁的德国人走上讲台。他个子不高,头顶已经微秃,戴着圆框眼镜。他的名字叫大卫·希尔伯特。在座的听众包括当时地球上几乎所有的一流数学家:庞加莱、皮亚诺、罗素。

希尔伯特清了清嗓子,开始宣读一份史无前例的学术清单:23个问题。这些问题覆盖了数学的每一个分支,从数论到几何,从代数到分析。他相信,解决了这23个问题,数学体系的大厦就封顶了。

第2个问题是这样的:“证明算术系统的一致性”,即证明“从算术的公理出发,你永远不会推导出一个矛盾。“1=1“和“1≠1“永远不会同时被证明。这是整个数学大厦的地基。如果这道题解不出来,一切数学推理都悬在半空中。

希尔伯特的梦想不止于此。他的完整计划有三个步骤:第一步:把全部数学公理化,写成一个形式系统。第二步:证明这个系统的一致性(不存在矛盾)。第三步:找到一个通用判定算法(Entscheidungsproblem):输入任何一个数学命题,算法输出“真“或“假“,像一台自动打分的阅卷机。

“我们必须知道,我们必将知道。” 这是希尔伯特的墓志铭。

三十年后,他错了。而且错的方式极其微妙:不是因为有人找到了推翻算术一致性的矛盾,而是因为有人证明了“一致性证明本身是不可能的“。打碎这个梦的25岁年轻人叫库尔特·哥德尔。ISO 26262就是这个被打碎的梦在汽车安全领域最认真、最系统、也最清醒的继承。

哥德尔1931:致命的“自指“

哥德尔的证明被誉为“整个逻辑学史最深刻的洞见“。它的结构可以用一句话概括:他在算术系统里构造了一个自指的悖论。

先看一个通俗版本。如果我说“我说的这句话是假的“——那么这句话是真还是假?假设它为真,那它说的“这句话是假的“就成立,它就应该是假的。假设它为假,那它说的就是假的,所以它应该是真的。无论怎么推都矛盾。这就是“说谎者悖论“:一个句子谈论自身的真假,把自己锁进了死循环。

哥德尔做的事情,本质上就是把说谎者悖论翻译进了数学。你不是在自然语言里说自己假——你是在算术系统里构造了一个命题,说自己不可证。

哥德尔把算术的符号(0、S、+、×)编码成自然数,叫“哥德尔数“。一个命题“1+1=2“不仅是一个数学语句,它现在也是一个自然数。一套推理证明不仅是一串符号,它现在也是一串可以计算的自然数。他把“可证性“这个概念本身,拉进了系统内部。系统现在能说话:“命题X在系统内是可证的。”

然后他做了一个哥德尔式的追问:如果系统能谈论自己的“可证性“,它能不能构造一个 “我不可证” 的命题?

能。

哥德尔构造了一个命题G,在系统里可以写出来的、合法的公式,但它说的是“G不可证“。你翻开任何逻辑书,这个构造的伪代码是很简短的几句话:

G 的哥德尔数 = "命题(哥德尔数为G)不可证"
即:G = "G 不可证"

现在来推。假设G是真的,“G不可证“是真的,则数学体系里至少有一个真命题无法被证明。这个体系是不完备的。假设G是假的,“G不可证“是假的,那意味着G可以被证明。但我们证明了一个假的命题,这体系是不一致的。数学不能是不一致的,所以G只能是真的但不可证。

任何包含基本算术的、一致的形式系统,都必然是不完备的:存在真的命题,系统无法证明。

希尔伯特的第2个问题,证明算术的一致性,在哥德尔找到的G面前,被判定为“如果你在算术里证明了算术的一致性,那你就证明了一个假命题(因为如果算术一致则G不可证但真,而G说’我不可证’),所以体系不一致。“

这不是数学家无能。这是自指结构的不可逃逸性。任何能“谈论自身“的系统,无论是算术的形式体系还是一个人反观自身,都必然有一个观察不到的死角。就像你的眼睛能看到世界,但看不到自己的视网膜,除非用第二面镜子。自指在20世纪的数学里被反复证明是“完整性“的绝对天敌。图灵的停机问题、罗素悖论、哥德尔不完备定理,核心结构都是同一个:系统试图判定自身,必然产生矛盾。

从哥德尔到ISO 26262——缩小范围的智慧

希尔伯特想用一套形式系统覆盖全部数学真理。哥德尔证明这不可能:任何足够强大的系统必然有不可判定的命题。

ISO 26262的工程实践在做什么?

它把自己的命题范围缩小到了极致。 不证明“任意程序的任意性质“,赖斯定理已经把这判了死刑。只证明:“这个特定的ECU软件,在给定的硬件资源限制和约束条件下,其行为不会违反特定的安全目标。”

这听起来是一种退缩。其实是极其高明的范围限定。ISO 26262不要求你证明“软件没有bug“,这是哥德尔禁区。它要求你证明“软件没有安全相关的bug“,而且“安全相关“的定义是明确写在HARA表里的。

HARA(危害分析与风险评估)就是这个“缩小范围“的核心工具。它不问你“这个ECU软件会不会出任何错“,它问的是一个狭窄得多的问题:具体什么场景下、发生了什么故障、可能导致多严重的危害。 答案的格式是一个 ASIL 等级:A、B、C、D,每一级对应一套预定义的安全需求集。QM(质量管理)级别的功能不需要安全论证:“足够好“的软件工程就够。

这个策略叫约束回归。如果全自动证明不了所有安全属性,就用约束把问题空间收窄到分析工具能覆盖的范围。MISRA C 就是一个典型的约束回归:它不是形式化验证标准,而是一套编码规则——通过限制语言特性的使用(禁止递归,Rule 17.2;禁止动态内存分配,Rule 21.3;禁止 goto 乱跳,Rule 15.1),把程序空间压进一个静态分析工具能处理的子集。可读性、可维护性是它的副产品,核心动机是“让代码可以被分析“。

from-sand-to-ruts 的第1.2节里,这个策略被总结为一句极其精准的话:“我们能证明的,我们说’安全’。我们证明不了的,我们用约束让它不发生。“这不是认输。这是清醒。

状态空间与“有涯求无涯“

假设你的ECU有100个配置参数,每个参数在运行时可能取100个不同的值。状态空间是100^100,这是一个比可观测宇宙中的原子数还大得多的数字。你永远不可能穷举这个空间,ISO 26262 Part 6的测试方法论也不要求你穷举。它要求你使用等价类划分、边界值分析、最坏情况测试:把无限的输入海洋缩小成一个可游的游泳池,然后在这个池子里保证每一个分支都有覆盖。

这就是哥德尔定理在工程实践中的直接映射:确定性是不完整的,完备性是不可能的,但你不需要确定性来保证安全。你需要的是“在给定的有限范围里,找出所有已知的危险路径“。

约束回归的一个具象案例——一段被MISRA驯服的C代码

让我们从抽象的数学落到一行行的C代码上。这是ECU上一段“正常的“代码,没有任何故意的错误,只是图了工程上的方便:

void handle_sensor_data(int *buf, int count) {
    int *ptr = malloc(count * sizeof(int));  // MISRA 规则 21.3: 禁动态分配
    if (ptr == NULL) return;

    for (int i = 0; i < count; i++) {
        *ptr++ = buf[i] * scale_factor(i);  // scale_factor 可能递归?
    }
    process(ptr - count, count);
    free(ptr);
}

这段代码有三个让静态分析工具头疼的问题。 第一: malloc:堆分配的大小取决于运行时参数count,在最坏情况下栈+堆所需大小可能撞上SRAM的物理上限。分析工具无法对所有的count值验证malloc不会返回NULL。 第二: scale_factor(i):如果是递归函数(比如用递归算多项式系数),调用深度取决于i,在最坏情况下可能爆栈。静态分析工具必须跟踪递归调用图,而这本身就是不可判定的(停机问题告诉我们的)。 第三: 指针算术 ptr++ptr - count:在循环里修改指针,然后在循环外用。你必须证明ptr - count指向原始ptr的起始位置,这个证明需要跟踪整个循环的指针状态,复杂度是指数级的。

在MISRA C的约束下,这段代码必须被重写成:

void handle_sensor_data(int *buf, int count) {
    int local[MAX_SENSOR_COUNT];           // 静态分配,编译期确定大小
    for (int i = 0; i < count && i < MAX_SENSOR_COUNT; i++) {
        local[i] = buf[i] * scale_table[i]; // 查表替代函数调用
    }
    process(local, count);
}

现在静态分析工具可以验证什么? 第一: local的大小是编译期常量,栈使用量是确定的,不需要分析“所有可能的count值“。 第二: scale_table[i]是数组查表不是函数调用,没有递归,调用图深度=0。 第三: 用数组下标代替指针算术:local[i]的访问范围被MAX_SENSOR_COUNT硬限制,越界访问可以被静态分析工具检测到(因为边界是编译期常量)。

你每写一条MISRA C合规代码,就是在执行一次约束回归。 你不是在“遵守无聊的规则“,你是在把代码从一个“分析工具无法覆盖“的区域拽进一个“分析工具可以给出保证“的区域。这和希尔伯特试图把所有数学装进形式系统的逻辑如出一辙,区别是希尔伯特失败了(因为目标太大),而你的MISRA约束成功了(因为目标是足够小的子集)。

自指的幽灵——在ECU软件里长什么样

哥德尔悖论的核心结构是自指,系统审视自己。你应该问:这个自指结构在我写的ECU代码里有没有物理对应?

有。而且不止一个地方有。 第一个例子:看门狗本身。看门狗的定时器是一个硬件外设,独立于CPU的时钟源,独立于你写的任何代码。但如果看门狗的超时处理函数是软件,谁来监控这个“监控者“没有跑飞?这是软件看门狗的自指困境:监控系统本身的代码也需要被监控,无限递归。解决方案是硬件独立看门狗:监控器和被监控对象不在同一个“逻辑系统里“。 哥德尔的自指悖论破坏的是“同一个形式系统内的一致性“。你用“第二个形式系统“(硬件看门狗、独立MCU)来打破自指,这和数学家跳出算术系统用更强的系统(ZFC集合论)证明算术一致性是同一个手法。

第二个例子:内存保护单元(MPU)。MPU是CPU的一部分,它和你写的C代码在同一片硅上。但如果MPU的配置寄存器被单比特翻转打坏了,谁来保护MPU?MPU自己,它没法做到。所以ASIL D系统里通常有一个独立的外部安全监控MCU,和主MCU不同晶片、不同时钟、不同电源域。主MCU通过SPI定期报告自己的状态,外部监控MCU独立验证。这是用物理分离来打破自指的另一个实例。

哥德尔的自指悖论在工程中的应对策略,不是解决它,是绕过它。独立看门狗、外部监控MCU、硬件MPU,这些都是“在系统之外再建立另一个观察者“。工程不追求逻辑完备性,它追求物理隔离。物理世界不讲自指悖论:两个独立的电源域就是隔离的,不需要证明。

为什么这跟你有关

你现在调试一块ECU,遇到一个偶发的bit flip,0.001%概率的race condition,特定温区下某个外设的寄存器异常。你可能永远修不好它,因为底层的物理波动性本身在宣告:确定性是有限的。

这恰恰是哥德尔定理的物理对应:在物理层面,确定性是近似的。在逻辑层面,完备性是不可能的。两个层次上,终极确定性都不存在。

希尔伯特和哥德尔的故事讲的其实不是一个数学定理的成败。它讲的是:人类设定一个伟大的目标,然后以同样伟大的诚实,证明这个目标不可能实现。 希尔伯特想把所有真理装进一个形式系统。他失败了。但这个失败的过程,催生了现代计算机科学的基础,以及哥德尔、丘奇、图灵这一代天才的涌现。

你的ECU软件不能保证“绝对不出错“。哥德尔说这个保证在数学上就不可能。但你可以保证:在给定的ASIL级别要求的覆盖率范围里,所有的安全目标都被分析、测试、论证过了。 这是从“证明一切“降维到“证明足够安全“的工程智慧。

ISO 26262 就是希尔伯特之梦在汽车安全领域的继承,范围被极大地缩小了,目标从“证明所有数学真理“变成“证明这个ECU不会杀人“。范围缩小之后,不可判定的禁区大部分被绕开了。留下的是“有界环境下的可判定问题“,而这恰好是工程师可以拿着测试向量和静态分析工具去处理的。

本篇小结

  1. 希尔伯特的梦想是构造一个能证明一切数学真理的形式系统。哥德尔用自指悖论证明这不可能:任何足够强的系统必然不完备。
  2. ISO 26262继承了希尔伯特的方法论,但收窄了目标:不证明“没有bug“,只证明“没有安全相关的bug“。
  3. MISRA C的禁止性规则(禁递归、禁动态内存、禁goto)是约束回归:把程序空间压进一个分析工具能覆盖的区域。
  4. 哥德尔的幽灵仍在:100^100的状态空间永远无法穷举。等价类、边界值、最坏情况测试是对不完备性的统计性补偿。

【下集预告】: 希尔伯特和哥德尔在数学世界里发现了“完美证明“的极限。但工程师没有选择:当软件必须控制放射治疗机、汽车的刹车和转向时,他们必须在不完备的理论基础上,设计出在现实中足够安全的系统。有些时候出错的代价是真实的生命。下一节回到1985年的德克萨斯,看一个竞态条件如何穿透层层安全假设,以及它留下的教训如何被写进了今天的安全标准。