编写带有整数表达式边界的编译器绑定检查数组(最佳实践)

时间:2014-05-13 18:44:45

标签: c++ arrays compiler-construction compiler-warnings

我正在用C ++(C ++ 98)编写一个编译器,我正在寻找反馈/想法如何处理以下情况(参见标题对于数组应该是什么样的语法):< / p>

{
    int b;
    int m, n;
    int a[n];

    b = a[b*m]; (1)
}

绑定检查,运行时堆栈扩展(以及离开范围时缩小)等,可以在我的框架中很好地处理。

我的问题涉及在使用未初始化变量的情况下发出警告,如上面(1)中所述。如果 a 是在初始化之前使用的整数变量,我允许它,但会发出警告消息。因为我想让编译器准备好以后可能有一个链接器,向后计算b和m并且实际上检查a的这个索引是否已经被初始化了(例如,在最一般的情况下,变量可以在一个不同的文件)。如上所述,我知道如何在运行时发出代码来进行绑定检查;但是......

... 当使用未初始化的变量时,扩展发出警告的最佳做法是什么?此变量的形式为[(expr)]?因为(expr)不必是整数值(仅仅是数字;它只需要是整数类型),而不是评估(expr)(我不想像上面所说的那样做),我不能比如说,一个带有标记为已初始化的条目的阴影数组。在(gcc)C中,简单地忽略了这样的情况:发出了b和m都未初始化的警告,但没有一个与使用未初始化的变量a [b * m]有关。这显然符合C的数组视图,并且在C语言中(但不是我正在研究的语言,其中没有指针的概念)表达式是明确定义的,除了b和m没有被初始化,并且访问(可能)超出限制(即将发生的堆栈溢出)。

最佳做法是在使用前不检查[(expr)]是否已初始化;发出代码;并等待运行时错误,如果有的话?还是...?

3 个答案:

答案 0 :(得分:4)

我认为尝试初始化 - 检查一切都会导致痛苦。

考虑当a[b*m]的元素有条件地初始化时会发生什么,即初始化a的元素取决于输入参数。您不仅要跟踪&#34; shadow array copy&#34;中的初始化,还要跟踪整个条件执行图,以确保涵盖所有执行路径。最终,这在图灵机上是一个不可判定的问题,甚至;为了解决这个问题,你必须决定halting problem(告诉执行图的任何特定子图是否完成执行)。

你可以做一些启发式方法来警告只有未初始化案例的某些子集,但这只是意味着你的编译器有时会发出警告而不是其他时间。作为一名程序员,你应该知道这种看似不确定的行为是多么令人愤怒。

答案 1 :(得分:4)

在每种情况下几乎不可能分辨,例如,考虑一下:

void foo(int &x, int y)
{
   switch(y)
   {
      case 1:
         x = 11;
         break;
      case 2:
         x = 42;
         break;
      ...  // numbers 3-9 elided for brevity
      case 10:
         x = 97;
         break;
   }
}

int bar(int z)
{
    int a;
    foo(a, z);
}

a是否已初始化?那么,取决于z的价值。如果您拥有可用的所有代码,至少在理论上,您可以按照所有路径查看z可能具有哪些值(假设z当然没有作为来自外部源的输入 - 在这种情况下,如果它没有进行范围检查,它可能是编写得很糟糕的代码,但是很多代码只是乐意接受输入是&#34; ok&#34; )。

所以,你必须采取两种途径之一:

  1. 选择假设&#34;合理初始化的变量&#34;确实已初始化,只有在确定某些内容未初始化时才会发出警告。

  2. 选择假设&#34;合理初始化的变量&#34;确实没有初始化,并发出警告。

  3. GCC确实,至少有时会警告你可以做的事情,因为人类推断它不可能是未初始化的(因为它总是需要几条路径中的一条)。我们有时会发现这种情况,其中某个代码段在一个配置中编译得很好(优化级别低)并且在更高的优化级别上失败,并且假设我们使用-Werror,则构建失败。在这种特殊情况下,使用额外的初始化没有太多开销,但有时候也会产生烦人/低效。你永远不会取悦所有人(但是你可能会允许选择&#34;特别偏执&#34;并且在任何时候都警告它可能是未初始化的)。

    当然,如果它是您自己的语言,并且您不太关心性能(可能&#34;启用检查时#34;),您可以添加额外的元素每个变量指示它是否已被初始化,并且在表达式评估期间确定它是否已被初始化。然而,每次使用变量时,它需要更多的指令来检查布尔值[或者在第一次使用时,如果你能确定 - 但请记住可能有分支!]

    或者始终初始化未初始化为&#34;疯狂值的变量&#34; (例如0xdeaddead或其他一些) - 当在数组中使用时,这几乎总会导致崩溃。

    当然,在编译阶段尽可能多地捕捉它总是更好 - 这只是你能否可靠地做到这一点(以及需要花费多少精力/时间)。代码编译完成后在测试期间检测到的任何内容&#34;成本&#34;更多的发现和修复。

答案 2 :(得分:0)

您的标题与您的问题文字不符。

假设您的问题是:&#34;如何检测未定义变量的使用&#34;,如果您的语言不是为了极端性能,您可以随时定义值的位模式,这意味着&#34;未定义&#34; (-2 ^ 31非常适合32位有符号整数),并生成用于检查fetch上未定义值的代码。这很容易。

如果您的问题是,&#34;如何检测越界数组访问&#34;,特别是鉴于您的语言没有指针,每个数组都可以携带自己的数组边界,并且数组访问可以检查索引是否在限制范围内。这很容易。

如果您需要高性能语言,那么其他两种技术可能过于昂贵。您需要对编译器中的表达式实施范围分析,以估计索引访问的范围。这很难。