【问题标题】:Why does C support negative array indices?为什么 C 支持负数组索引?
【发布时间】:2013-08-11 14:18:06
【问题描述】:

从 SO 中的 this 帖子中可以看出,C 支持负索引。

  1. 为什么要在程序中支持这种潜在的内存冲突?

  2. 编译器不应该至少抛出一个负索引警告吗? (我正在使用 GCC)

  3. 或者这个计算是在运行时完成的?

编辑 1: 谁能暗示它的用途?

编辑 2: 用于 3。)在数组/指针的 [] 中使用循环计数器表示索引的运行时计算。

【问题讨论】:

  • 你可以合法地获取数组的第 N 个元素的地址,然后负索引到 -N。
  • @ 1) :为避免负索引,您可以使用无符号类型作为索引,它还具有 failing fast 的优点。
  • @wildplasser:不,他们不会更快地失败。在典型实现中,将SIZE_MAX 添加到指针(具有UB)与将-1 添加到该指针具有相同的效果。
  • 是的,他们确实失败得更快:负索引可能会默默地破坏内存,而(unsigned) (~0) 的索引将立即导致越界违规。跨度>
  • 如果unsignedsize_t 大小相同,则x[-1]x[(unsigned) ~0] 在常见实现中通常具有相同的效果。如果size_tunsigned 宽(这在“64 位”实现中并不少见),通常会有区别;前者通常在阵前,后者距离很远。但是,对于索引,通常应避免使用intunsigned,而更喜欢ptrdiff_t。使用ptrdiff_t 时,通常没有区别。

标签: c arrays pointers


【解决方案1】:

计算在运行时完成。

负索引不一定会导致违规,并且有其用途。

例如,假设您有一个当前指向数组中第 10 个元素的指针。现在,如果您需要在不更改指针的情况下访问第 8 个元素,您可以使用负索引 -2 轻松完成。

char data[] = "01234567890123456789";
char* ptr = &data[9];
char c = ptr[-2]; // 7

【讨论】:

  • 嘿,要访问第 8 个元素,我不会使用*(data+7)吗?
  • 这只是一个例子。在某些情况下,数据可能不可用。假设您将 ptr 作为参数传递给其他函数。在函数内部,您可能会使用负索引来访问像这样的先前元素。无论如何,这只是一个例子。只是说负索引有合法用途。
【解决方案2】:

这是一个使用示例。

无限脉冲响应过滤器部分地根据最近的先前输出值计算得出。通常,会有一些输入值数组和一个要放置输出值的数组。如果当前输出元素是 yi,那么 yi 可以计算为 yi = a0• xi + a1•xi–1 +a2•yi–1 +a3•yi–2.

为此编写代码的一种自然方式是:

void IIR(float *x, float *y, size_t n)
{
    for (i = 0; i < n; ++i)
        y[i] = a0*x[i] + a1*x[i-1] + a2*y[i-1] + a3*y[i-2];
}

注意当i 为零时,y[i-1]y[i-2] 具有负索引。在这种情况下,调用者负责创建一个数组,将最初的两个元素设置为输出的“起始值”(通常为零或从前一个缓冲区保留的值),并将指针传递给第一个新值的位置是要写的。因此,这个例程IRR 通常接收指向数组中间的指针并使用负索引来寻址某些元素。

【讨论】:

  • 用一个真实的例子很好的回答。但我认为,您应该在 i = 2 处开始循环,以便清楚地表明您没有试图超出范围。
【解决方案3】:

为什么要在程序中支持这种潜在的内存冲突?

因为它遵循指针算法,并且在某些情况下可能有用。

编译器不应该至少抛出一个负索引警告吗? (我正在使用 GCC)

当数组只有 10 个元素时,当您访问 array[10] 时编译器不会警告您的原因与此相同。因为它将这项工作留给了程序员。

或者这个计算是在运行时完成的?

是的,计算是在运行时完成的。

【讨论】:

    【解决方案4】:

    详细阐述泰蒙的回答:

    float arr[10];
    float *p = &arr[2];
    
    p[-2]
    

    现在完全没问题。我还没有看到负索引的良好用途,但是如果您通常无法确定您是否指向有效范围之外,那么为什么标准应该将其排除在外。

    【讨论】:

    • "我还没有看到负索引的好用法" - 在 this tutorial 中,哈希函数考虑了字符串中的当前 前一个字符,即代码包含hash += data * p[-1](或类似的东西)。
    • 重点应该放在“善用”上。自然地,可以在始终传递第 0 个元素和使用类型为“size_t”的变量来索引和传递第 n 个元素和使用负索引这两种风格之间切换。通过良好的使用,我希望负索引有一个很好的解释,即它在正在实现的算法中暴露了一些有趣的东西。这是一个可判定的标准:如果我在原始 TeX 源或 Standford GraphBase(Knuth 发布的任何代码)中发现任何负索引的使用,我将更改声明:)。
    【解决方案5】:

    OP:为什么要支持……潜在的内存违规?

    它有潜在的用途,因为正如 OP 所说,这是一个 潜在 违规,而不是特定的内存违规。 C 是关于允许用户做很多事情,包括他们需要上吊的所有绳子。

    OP:...抛出负索引警告...

    如果担心,请使用unsigned 索引或更好的索引,请使用size_t

    OP ...在运行时完成计算?

    是的,经常像在a[i] 中一样,其中i 不是一个常数。

    OP:暗示它的用途?

    示例:一个人正在处理点数组 (Pt) 中的一个点,并且想要确定该中点是否是移除的候选对象,因为它是重合的。假设调用函数已经确定Mid 既不是第一个点也不是最后一个点。

    static int IsCoincident(Pt *Mid) {
      Pt *Left = &Mid[-1];   // fixed negative index
      Pt *Right = &Mid[+1]; 
      return foo(Left, Mid, Right);
    } 
    

    【讨论】:

      【解决方案6】:

      数组下标只是语法糖,用于解除指向内存中任意位置的指针。编译器无法就负索引向您发出警告,因为它在编译时不知道指针将指向的位置。任何给定的指针算术表达式可能会或可能不会导致内存访问的有效地址。

      【讨论】:

      • 如果你正在做类似a[i] 的事情,那么它当然是在运行时完成的。编译器在编译时无法知道i的值,因为每次执行代码都会不同。
      • 您应该更改“数组只是指针的语法糖”,因为数组和指针完全不同。 “数组下标[] 只是语法糖......”会更好。
      【解决方案7】:

      a[b]*(a+b) 做同样的事情。由于后者允许否定b,所以前者也允许。

      【讨论】:

        【解决方案8】:

        使用负数组索引的示例。

        我使用负索引来检查消息协议。例如,一种协议格式如下所示:

        <nnn/message/f>
        

        或者,同样有效:

        <nnn/message>
        

        参数f 是可选的,如果提供,则必须是单个字符。

        如果我想得到字符f的值,我首先得到一个指向&gt;字符的指针:

        char * end_ptr = strchr(msg, '>');
        char f_char = '1';  /* default value */    
        

        现在我检查是否提供了 f 并提取它(这里是使用负数组索引的地方):

        if (end_ptr[-2] == '/')
        {
            f_char = end_ptr[-1];
        }
        

        请注意,我省略了错误检查和其他与此示例无关的代码。

        【讨论】:

          猜你喜欢
          • 2011-04-29
          • 1970-01-01
          • 2022-12-23
          • 1970-01-01
          • 2018-04-27
          • 2018-07-13
          • 2017-06-29
          • 1970-01-01
          相关资源
          最近更新 更多