Misra 2012 背后的基本原理不允许在不同指针之间进行转换
Posted
技术标签:
【中文标题】Misra 2012 背后的基本原理不允许在不同指针之间进行转换【英文标题】:rationale behind Misra 2012 not allowing cast between different pointers 【发布时间】:2016-02-17 19:19:08 【问题描述】:我目前正在从事一个要求代码符合 Misra 2012 的项目。在整个项目中,我们有很多必需的 misra 警告,告诉我们不能将指向一种类型的指针转换为指向另一种类型的指针。像void *memcpy(void *to, const void *from, size_t n)
这样简单的事情会产生两个 Misra Required 警告,因为 to 和 from 都需要分别类型转换为 void* 和 const void*。
从 void* 转换为指向任何其他类型的指针也会给出 Misra 警告。
我的问题是,Misra 如何期望 malloc 和其他一切都可以正常工作而不会引发任何警告?即使将 void* 缓冲区转换为 uint8_t* 缓冲区以逐字节解析缓冲区并填充结构结构的所有元素也会引发大量警告?
除了这些警告之外,它是否可以不只是显示使用注释或信息要求我们仔细检查包装、对齐和其他任何可能出错的地方?
【问题讨论】:
MISRA 是否禁止隐式转换、显式转换或两者都禁止? (术语说明:强制转换是显式运算符;隐式转换不是强制转换。)调用memcpy
或malloc
不需要强制转换。
“Misra 如何期望 malloc 和其他所有东西都能在没有任何警告的情况下正常工作?” 根据我的发现,您不应该使用 malloc
,意思是答案将是“它没有”。
您可以在misra.org.uk查看官方文档。每条规则都有其基本原理。
您不应将void *
转换为 C 中的其他指针或从其他指针转换。C 不是 C++。而且 AFIK MISRA 甚至根本不允许(或至少强烈反对)使用动态内存分配。
C 中没有“隐式转换”之类的东西,标准也没有使用术语“类型转换”。单词“cast”仅适用于显式运算符,写为带括号的类型名称。你能用实际要求的报价更新问题吗? (如果禁止隐式指针转换,我看不出你如何使用malloc
或memcpy
。)
【参考方案1】:
我想回到 OP 提出的问题并弄清楚一些事情。 首先,调用 void *memcpy(void *to, const void *from, size_t n) 没有问题,因为将指向对象的指针转换为 void 指针不违反任何 MISRA-C:2012 准则。换句话说,任何产生违规行为的工具都是错误的。
其次,在得出任何结论之前,重要的是阅读规则 11.5(相关的 MISRA-C:2012 指南)的实际内容,即:
规则 11.5 不应从指向 void 的指针执行转换 指向对象的指针 品类咨询 分析可判定,单翻译单元 适用于 C90、C99 基本原理 将指向 void 的指针转换为指向对象的指针可能会导致 在未正确对齐的指针中,导致未定义 行为。应尽可能避免但可能是必要的, 例如在处理内存分配函数时。如果 使用从指向对象的指针到指向 void 的指针的转换, 应注意确保产生的任何指针不 引起规则 11.3 中讨论的未定义行为。观察:
-
这是一条建议性规则(即既非强制也非强制性),因此可以偏离,MISRA 定义了正确的偏离流程;
将指向对象的指针转换为指向 void 的指针很好:反之则有问题;
基本原理明确提到了内存分配功能(是的,使用动态内存分配的程序可以符合 MISRA-C:2012);
基本原理提供了在将指向对象的指针转换为指向 void 的指针时要做什么的指导,完全符合 OP 希望拥有的(“信息要求我们仔细检查打包、对齐和任何其他可能出错的内容")。
【讨论】:
【参考方案2】:这并没有回答你的问题,这是关于基本原理的。相反,它指出您一开始就不应该处于这种情况。
在您最喜欢的搜索引擎中输入“misra malloc”会导致我们:
http://www.misra.org.uk/forum/viewtopic.php?t=260
问:
根据规则,我们不应该使用 malloc()、free()、calloc() 等函数。但是 malloc() 是一个非常常见的要求。大多数嵌入式系统应用程序都使用自己的应用程序级内存管理器,以便快速分配和解除分配。你有什么建议来解决这个问题(如果我们不能使用 malloc,任何其他方式)?
答案是:
我们被问及 MISRA C1 和 MISRA C2 中禁止的各种事情的解决方案和变通方法,例如使用 malloc、calloc 等进行动态内存分配。 MISRA 或 MISRA C 工作组的任何成员都不会对任何偏差或“变通办法”提供任何指导或批准。
您要求代码符合某个标准。您正在使用不符合该标准的机制。要么找出一种有原则的和稳健的合规方式,要么就如何处理故意不合规制定明确的政策。
例如,您提到了 memcpy。这是不合规的。所以退后一步,问“假设我没有任何 memcpy 实现。我将如何编写自己的符合要求的 memcpy?”您正在使用 memcpy 解决问题;没有它就解决问题。
现在对 malloc 做同样的事情。有朝一日 malloc 不存在,有人不得不在没有 malloc 的情况下编写它。您有一个由 malloc 解决的问题。如果你没有malloc,你会怎么解决? malloc 中没有什么是神奇的。您可以自己编写比 malloc 做得更好的代码,其中“更好”是指“符合您的要求”。
【讨论】:
@thunderbird:经常遵守并非针对您的特定应用程序量身定制的标准会在工作量和效率方面带来成本。成本是否值得收益的问题是由您和项目利益相关者共同决定,基于对成本和收益的知情评估。合规本身并不好;这是因为有好的结果。如果您没有以合理的成本获得这些好的结果,那就退后吧。 @SergeyA:对,有一个很好的例子。如果性能成本超过了安全收益,那么您可以提出一个原则性的、数据驱动的论点,即序列化代码应该隔离到其自己的封闭子系统中,该子系统经过仔细审查,并免除您的要求政策。政策是我们的仆人;让你的工作实现你的目标。 @SergeyA:谁说过要写一个通用的内存管理器?您需要一个整数块,编写一个专门用于为您制作一个整数块的内存管理器。通用的内存管理器是一堆危险。该标准的全部意义在于摆脱这种危险,而不是重新实施它。 @SergeyA:这是您的选择:(1) 使用具有适当类型系统和运行时的语言,以确保内存安全,并支付与之相关的性能成本。 (2) 使用带有垃圾类型系统的语言,该系统允许您将任何引用转换为任何其他引用,编写使用 void* 作为任何类型的代理的代码,支付错误的代价,这些错误太可怕了,以至于让您的头发脱落,或者(3) 使用带有垃圾类型系统的语言,避免使用 void*,编写大量无聊的代码。我选择(1),但那是因为这样的语言能满足我的需要。 @SergeyA:我坦率地承认我不是 C++ 专家,甚至从未编写过一个 C++ 编译器;我只用 C++ 为 other 语言编写过编译器。直到你为它编写了几个编译器之后,你才真正了解一门语言。话虽如此,我想我偶尔可以为这个主题增加一些价值。【参考方案3】:MISRA-C:2012 在指针转换方面实际上有些松懈。大多数规则都是合理的,涉及强制您遵循 C 标准,不调用未定义的行为,或者做普遍的坏事,例如从指针中丢弃 const 限定符。您不应该对此有任何反对意见。
唯一有争议的规则是 11.5:
不应将指向 void 的指针转换为 指向对象的指针。
我认为这是导致您头痛的原因。基本原理是对齐问题和不兼容指针类型之间的转换,这将导致未定义的行为。
该规则确实间接禁止使用许多基本库函数,例如memcpy
。在 2012 年审查期间,我对 MISRA 的个人建议是:
(非常不同意)“当需要进行 void 指针强制转换时,有太多的泛型 C 编程案例。这条规则不实用,弊大于利。相反,制定一条规则,禁止该规则尝试的特定危险防止,即“指向 x 的指针,强制转换为 void*,强制转换为指向 y 的指针”。”
但他们没有听,你有它:一个无用的规则,它迫使每个工具喷出大量的误报。这意味着 MISRA 的每个用户都必须忽略此规则。它是建议性的,因此您可以忽略它而无需提出任何偏差程序。只需在您的静态分析器中阻止它并继续。
MISRA 委员会尚未意识到误报本身就是一种安全隐患,因为它可能会导致人们开始重新编写完美的代码,从而引入错误。
Misra 如何期望 malloc
MISRA 希望您根本不要使用 malloc,它被指令 4.12 明确禁止,原因很充分。但这是另一个话题。
【讨论】:
@SergeyA 动态分配导致分段、非确定性分配时间、内存泄漏等。在任何小型嵌入式系统中,它都是 100% 多余的、不必要的臃肿功能。在任务关键型软件中,动态分配也是完全没有意义的,因为不存在不确定的属性。另请注意,static type_t pool[n]
仅用于 n
类型为 type_t
的对象,仅此而已。而 malloc 是完全类型/大小通用的,因此必须使用分段。
等一下。您是否还建议对我程序中的每个 不同类型的对象使用固定池?因此,如果我在我的应用程序中使用大约 500 个 types,我最终会得到 500 个池吗?此外,malloc 在从所述池分配泄漏方面有何不同?
@SergeyA 对于您使用的每个 ADT。每当您需要在 C 中使用真正的私有封装时,这应该是 ADT 需要进行分配的唯一情况,而不是调用者。 malloc
是不同的,因为那些为了节省内存而使用它的人也必须主动调用free
,这就是你可以得到错误的地方。但是动态“节省内存”在单进程系统中没有任何意义,因为您不与任何人共享内存。您的堆必须始终足够大以适应最坏的情况,因此您不妨静态分配确切的内存量。
Lundin,我会使用 malloc,因为它被测试的次数超过了宇宙可观测部分的恒星(我对估计的疯狂猜测)。如果您不想释放资源,请不要释放它们,没有人强迫您这样做。 malloc()
也是其他库函数使用的函数,例如strdup
。所以,你也禁止strdup
?
@SergeyA:问题不在于 malloc 是否经过良好测试。问题不在于它是否可靠地按照合同履行。问题是 malloc 的合同一开始就很糟糕。 Malloc 将测试负担从确保 malloc 完成其工作转移到确保使用它的程序在所有可能耗尽内存的代码路径中都是健壮的。为这种情况编写强大的软件几乎是不可能的。以上是关于Misra 2012 背后的基本原理不允许在不同指针之间进行转换的主要内容,如果未能解决你的问题,请参考以下文章
MISRA C ++(规则18-4-1)和动态内存分配 - 是否允许使用std :: string?
MISRA C 2012 - 规则 21.1 - 以下划线开头的宏