【问题标题】:Writing a compiler - bound-checked arrays with integer expression bounds (best practice)编写编译器 - 具有整数表达式边界的边界检查数组(最佳实践)
【发布时间】:2014-05-13 18:44:44
【问题描述】:

我正在用 C++ (C++98) 手动编写编译器,并正在寻找反馈/想法应该如何处理以下情况(请参阅标题,了解数组的语法应该是什么样的):

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

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

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

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

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

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

【问题讨论】:

  • 没有 C++99。 C99?
  • @deviantfan: :) C++11 之前的版本(我的 gcc 对 C++11 的支持参差不齐 - 我正在运行并且没有更新大约 Ubuntu 12.04 时间版本的 gcc)跨度>
  • C++11 之前的版本(不包括 TR)是 C++03。但是:C++ 没有可变长度数组(C99 有。(不禁止在 C++ 中添加它们))。 ...您应该知道您正在为哪种语言制作编译器...
  • @deviantfan:(1)目标语言类似于Java; (2) 开发语言为C++; (3) 所有对 C(或除上述两种语言之外的任何语言)的引用都只是示例。

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


【解决方案1】:

几乎不可能在每种情况下都说出来,例如,考虑一下:

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 不是来自外部源的输入- 在这种情况下,如果它不进行范围检查,它可能是写得很糟糕的代码,但是很多代码会很高兴地接受输入是“好的”)。

所以,你必须采取以下两条路线之一:

  1. 选择假设“合理初始化”的变量确实已初始化,并且仅在确定某些内容未初始化时才发出警告。

  2. 选择假设“合理初始化”的变量确实没有初始化,并给出警告。

GCC 至少有时会在可能的情况下发出警告,因为人类推断它不可能未初始化(因为它总是采用多种路径之一)。我们有时会发现这一点,其中某段代码在一个配置中编译得很好(优化级别较低)并在更高的优化级别下失败,并且鉴于我们使用 -Werror,构建失败。在这种特殊情况下,使用额外的初始化并没有太多开销,但有时也会变得烦人/效率低下。你永远不会取悦所有人(但你也许可以允许“更加偏执”的选项,并始终警告它似乎未初始化)。

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

或者总是初始化未初始化为“疯狂值”的变量(例如 0xdeaddead 或类似的) - 这在数组中使用时几乎总是会导致崩溃。

当然,通过编译阶段尽可能多地捕获总是更好 - 这只是您是否可以可靠地做到这一点(以及需要多少努力/时间)的问题。在编译代码之后,您在测试期间检测到的任何东西都“花费”更多来查找和修复。

【讨论】:

    【解决方案2】:

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

    考虑当a[b*m] 的元素被有条件地 初始化时会发生什么,即a 的元素被初始化取决于输入参数。您不仅要跟踪“影子数组副本”中的初始化 bit,还要跟踪整个条件执行图,以确保覆盖所有执行路径。最终这在图灵机上是一个无法确定的问题,甚至;要解决这个问题,您必须确定halting problem(以判断执行图的任何特定子图是否完成执行)。

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

    【讨论】:

      【解决方案3】:

      您的标题与您的问题文本不匹配。

      假设您的问题是:“如何检测未定义变量的使用”,如果您的语言不适合极端性能,您始终可以为表示“未定义”的值定义一个位模式(-2^31 非常适合32 位有符号整数),并生成检查提取时未定义值的代码。这很容易。

      如果您的问题是“如何检测超出范围的数组访问”,特别是考虑到您的语言没有指针,每个数组都可以携带自己的数组边界,并且数组访问可以检查索引是在限度之内。这很容易。

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

      【讨论】:

      • 当然,反对者不会解释他不喜欢我的回答的什么地方。
      猜你喜欢
      • 1970-01-01
      • 2012-06-23
      • 2011-01-17
      • 2020-03-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多