【问题标题】:How can elusive 64-bit portability issues be detected?如何检测难以捉摸的 64 位可移植性问题?
【发布时间】:2011-06-13 21:10:37
【问题描述】:

我在为 64 位端口准备的一些 (C++) 代码中发现了与此类似的 sn-p。

int n;
size_t pos, npos;

/* ... initialization ... */

while((pos = find(ch, start)) != npos)
{
    /* ... advance start position ... */

    n++; // this will overflow if the loop iterates too many times
}

虽然我严重怀疑这是否会在内存密集型应用程序中引起问题,但从理论上讲还是值得研究的,因为可能会出现类似的错误, 会导致问题。 (将上例中的n 更改为short,即使是小文件也可能溢出计数器。)

静态分析工具很有用,但它们不能直接检测到这种错误。 (反正还没有。)计数器n 根本不参与while 表达式,所以这不像其他循环那么简单(类型转换错误会消除错误)。任何工具都需要确定循环将执行超过 231 次,但这意味着它需要能够估计表达式 (pos = find(ch, start)) != npos 将评估为真的次数——这可不小!即使一个工具可以确定循环可以执行超过 231 次(比如说,因为它识别出 find 函数正在处理一个字符串),但怎么可能呢?它知道循环不会执行超过 264 次,也会溢出 size_t 值?

似乎很清楚,要最终识别和修复此类错误需要人眼,但是否有模式可以泄露此类错误以便手动检查?我应该注意哪些类似的错误?

编辑 1: 由于 shortintlong 类型本质上是有问题的,因此可以通过检查这些类型的每个实例来发现这种错误。但是,鉴于它们在遗留 C++ 代码中无处不在,我不确定这对于大型软件是否实用。还有什么可以消除这个错误?每个while 循环是否可能会出现这样的错误? (for 循环当然不能幸免!)如果我们不处理像 short 这样的 16 位类型,这种错误有多严重?

编辑 2:这是另一个示例,显示了此错误如何出现在 for 循环中。

int i = 0;
for (iter = c.begin(); iter != c.end(); iter++, i++)
{
    /* ... */
}

这本质上是同一个问题:循环依赖于一些从不直接与更广泛的类型交互的变量。变量仍然可以溢出,但没有编译器或工具检测到转换错误。 (严格来说,没有。)

编辑 3:我正在使用的代码非常很大。 (仅 C++ 就有 10-1500 万行代码。)检查所有这些是不可行的,所以我对自动识别这类问题(即使它导致高误报率)的方法特别感兴趣。

【问题讨论】:

    标签: c++ 64-bit portability 32bit-64bit


    【解决方案1】:

    代码审查。让一群聪明人看代码。

    使用shortintlong 是一个警告标志,因为标准中没有定义这些类型的范围。大多数用法应更改为<stdint.h> 中的新int_fastN_t 类型,处理序列化的用法为intN_t。好吧,实际上这些<stdint.h> 类型应该用于typedef 新的应用程序特定类型。

    这个例子真的应该是:

    typedef int_fast32_t linecount_appt;
    linecount_appt n;
    

    这表达了一个设计假设,即行数适合 32 位,并且如果设计要求发生变化,也可以很容易地修复代码。

    【讨论】:

    • 如果有 >10M 行代码要查看怎么办?查看每一个 short、int 和 long 是否可行?换一种方式问:对于 64 位可移植性,是否总有比使用 short、int 或 long 更好的选择?
    • <stdint.h> 中的新类型目前不在标准 C++ 中,并且很可能在正在使用的实现中不可用。此外,我有哲学上的例外,即必须在任何地方以位为单位指定大小。
    • @David:这就是我建议使用特定于应用程序的 typedef 的原因,因此您不必在任何地方都以位为单位指定大小,而只是一个可以根据需要更改的 typedef。
    • @David Thornley,<stdint.h> 类型在 Visual Studio 中确实不可用。您能否推荐(可能作为另一个答案)一些处理有问题的 short、int 和 long 的方法?
    【解决方案2】:

    很明显,您需要一个智能的“范围”分析器工具来确定计算的值的范围与存储这些值的类型。 (您的根本反对意见是智能范围分析仪是一个人)。您可能需要一些额外的代码注释(手动放置良好的 typedef 或提供显式范围约束的断言)来实现良好的分析,并处理其他明显任意大的用户输入。

    您需要特殊检查来处理 C/C++ 说算术是 合法 但愚蠢的地方(例如,假设您不希望 [twos 补码] 溢出)。 对于您的 n++ 示例(相当于 n_after=n_before+1),n_before 可以是 2^31-1(因为您对字符串的观察),所以 n_before+1 可以是 2^32,这是溢出的。 (我认为标准 C/C++ 语义表示溢出到 -0 没有抱怨是可以的)。

    我们的DMS Software Reengineering Toolkit 实际上内置了范围分析机制……但它目前还没有连接到 DMS 的 C++ 前端;我们只能这么快地兜售:-{ [我们已经在 COBOL 程序中使用它来解决涉及范围的不同问题]。

    在没有这种范围分析的情况下,您可能会检测到具有这种依赖流的循环的存在; n 的值显然取决于循环计数。我怀疑这会让程序中的每个循环都有副作用,这可能没有太大帮助。

    另一张海报建议以某种方式使用特定于应用程序的类型(例如,*linecount_appt*)重新声明所有类似 int 的声明,然后对这些声明进行类型定义,以便为您的应用程序工作。为此,我认为您必须将每个类似 int 的声明分类(例如,“这些声明都是 *linecount_appt*”)。通过手动检查 10M SLOC 来执行此操作似乎非常困难并且非常容易出错。查找从“相同”值源接收(通过赋值)值的所有声明可能是获取有关此类应用程序类型所在位置的提示的一种方法。您希望能够机械地找到此类声明组,然后使用某些工具自动将实际声明替换为指定的应用程序类型(例如,*linecount_appt*)。这可能比进行精确的范围分析要容易一些。

    【讨论】:

    • 我同意静态分析可以帮助找出特定领域的变量应该具有哪些类型(基本上是一种维度分析)。实际上,如果将强类型定义添加到语言中,编译器将执行此操作而无需单独的工具。范围分析在此示例中实际上不起作用,因为范围是由用户输入定义的。我真的很喜欢静态分析的想法,计算用户输入的行数似乎是一个需要设计处理的案例,如果没有一些手动注释就无法分析。
    • @Ben Voight:不确定您所说的“强类型定义”是什么意思。如果这是对 C++ 语言的提议添加,那么它不太可能很快帮助 OP。关于范围分析:是的,某些值仅由用户输入的内容定义;对于这些值,在没有任何限制的情况下,范围分析可能会将它们识别为需要无限精度。这可以通过一些手动放置的 typedef 来约束这些值并再次运行范围分析来解决。但这里的重点是 OP 任务的规模,某种静态分析可能是必要的。
    • @Ben Voight:感谢您的链接。我认为这些不在即将发布的 C++ 标准中?
    • 这里更新版本:open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2141.html 不在 C++0x 中,工作组将其归类为“尚未为 C++0x 做好准备,但可以在未来重新提交”
    【解决方案3】:

    有一些工具可以帮助找到此类问题。我不会在这里给出任何链接,因为我知道的那些是商业的,但应该很容易找到。

    【讨论】:

    • 我一直在寻找,商业不是问题。能举几个例子吗?
    • 我看过这个工具,找不到这种错误。它只检测不适当的类型作为数组索引或错误的类型转换之类的东西。这是一个更复杂的错误,因为有溢出风险的变量(分别在上述示例中为 ni)不直接参与任何具有其他不兼容类型的表达式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    相关资源
    最近更新 更多