【问题标题】:Pointer to [-1]th index of array指向数组第 [-1] 个索引的指针
【发布时间】:2010-03-02 09:07:27
【问题描述】:

指向数组第[-1]个索引的指针如何每次都产生合法的输出。指针赋值中到底发生了什么?

#include<stdio.h>
int main()
{
        int realarray[10];
        int *array = &realarray[-1];

        printf("%p\n", (void *)array);
        return 0;
}

代码输出:

manav@workstation:~/knr$ gcc -Wall -pedantic ptr.c
manav@workstation:~/knr$ ./a.out
0xbf841140

编辑:如果这种情况是有效的,那么我可以用它来定义一个索引从1而不是0开始的数组,即:array[1],array[2],.. .

【问题讨论】:

标签: c++ c arrays pointers


【解决方案1】:

您只是得到一个包含该“虚构”位置地址的指针,即第一个元素 &amp;realarray[0] 的位置减去一个元素的大小。

这是undefined behavior,例如,如果您的机器具有分段内存架构,则可能会严重损坏。它之所以有效,是因为编译器编写者选择实现上述算术;这可能随时发生变化,另一个编译器的行为可能完全不同。

【讨论】:

  • 为什么 gcc 没有针对这种类型的分配提出任何警告?
  • 应该为int *p = realarray+5; int *q = p-6; 这样做吗?在 C 中,您必须进行边界检查。 :-)
  • @Manav:对于动态分配的数据,这些程序在返回数据前后分配几个字节,并将它们设置为可预测的值。如果值发生变化,则意味着您的程序正在写入不应该写入的位置。但是这种技术不适用于您的示例,它没有动态分配,甚至不写入数组的任何元素。
  • OTOH,1 索引数组完全可以作为一个重载 operator[]() 的类。
  • @SF。不在 C 中。这种重载在 C++ 中是否良好存在争议。
【解决方案2】:

a[b] 定义为*(a+b)

因此a[-1]*(a-1)

a-1 是否是一个有效的指针,因此取消引用是否有效取决于使用代码的上下文。

【讨论】:

  • 如果a-1 对取消引用无效(因为它超出范围),那么即使您不取消引用,仅进行算术运算也是未定义的行为。当然,大多数没有分段内存的健全系统的编译器都会忽略这一点,并允许您执行越界指针运算并获得自然结果,只要您不取消引用任何越界的东西。
【解决方案3】:

行为未定义。

您观察到的情况可能发生在您的特定编译器和配置中,但任何情况都可能发生在不同的情况下。你根本不能依赖这种行为。

【讨论】:

    【解决方案4】:

    行为未定义。你只能计算一个指向数组中任何元素的指针,或者一个过去的指针,但仅此而已。您只能取消引用指向数组任何元素的指针(而不是过去的指针)。查看您的变量名称,您似乎是在向this C FAQ 提问。我觉得FAQ上的回答很好。

    【讨论】:

    • @Alok:"...one past" 背后的逻辑是什么。为什么我们可以访问过去的一个元素?
    • @eSKay:如果数组a有10个元素,可以计算指针a+0a+10,可以访问元素a[0]通过a[9]。您无法访问a[10],因此“过去”部分仅适用于计算指针值时,而不是取消引用它。
    • 所以,基本上“过去”意味着引用“之后”a 可能存在的任何内容,对吧?
    【解决方案5】:

    尽管正如其他人所指出的,在这种情况下它是未定义的行为,但它编译时不会发出警告,因为一般foo[-1] 可能是有效的。

    例如,这很好:

    int realarray[10] = { 10, 20, 30, 40 };
    int *array = &realarray[2];
    
    printf("%d\n", array[-1]);
    

    【讨论】:

    • 有没有办法事先检查数组边界,尤其是在使用指针算法时。
    【解决方案6】:

    在 C 和 C++ 中,在运行时不检查数组索引。您正在执行的指针算术可能会或可能不会最终给出定义的结果(不在此处)。

    但是,在 C++ 中,您可以使用提供边界检查的数组类,例如 boost::arraystd::tr1::array(将添加到 C++0x 中的标准库中):

    #include <cstdio>
    #include <boost/array.hpp>
    
    int main()
    {
        try {
            boost::array<int, 10> realarray;
            int* p =  &realarray.at(-1);
            printf("%p\n", (void *)p);
        } catch (const std::exception& e) {
            puts(e.what());
        }
    }
    

    输出:

    数组:索引超出范围

    还会产生编译器警告:

    8 test.cpp [警告] 通过否定 值-0x000000001' for converting 1 ofT& boost::array::at(size_t) [with T = int, unsigned int N = 10u]'

    【讨论】:

      【解决方案7】:

      它只是指向内存中数组前面的项目的地址。

      数组可以简单地被认为是一个指针。然后将其简单地减一。

      【讨论】:

      • 虽然你的评论对于这个编译器/实现/配置是正确的,但它并没有被定义为总是如此。 (见丹尼尔的回答)
      • 是的,但问题是在指针分配中发生了什么,而不是应该发生什么;)正如一本教科书令人难忘地说,它很可能对怀特岛发动核攻击,直到标准去...
      【解决方案8】:

      这里你只是进行指针运算,它会得到relarray的第一个索引地址

      看,如果你 &relarray[+1] ,你会得到数组的第二个元素地址。因为

      &relarray[0] 指向第一个索引地址。

      【讨论】:

        【解决方案9】:

        array 指向realarray 起始地址之前的一个位置。然而,令我困惑的是为什么编译时没有任何警告。

        【讨论】:

        • 虽然编译器在这个简单的场景中发现似乎微不足道,但总的来说并不简单。只需假设数组被传递(作为指针)到一个函数中,并且在函数中完成了算术运算。编译器如何知道函数不是用&amp;realarray[1] 调用的,因此计算是否有效?很容易创建除非在运行时才能发现的场景。作为 C 或 C++ 程序员,无论如何您都必须注意这些错误。 IMO 编译器编写者应该把资源花在更重要的事情上。
        • 在这种情况下,数组不会作为参数传递给函数。它的范围是本地的,在这种情况下检查边界是编译器最应该做的事情,因为通过堆栈分配的数组进行堆栈粉碎一直是几个程序中的一个安全漏洞。这种事情只能在你知道堆栈是什么样子的代码中有用,这取决于几乎所有东西和月球的相位,最好在汇编程序中完成,至少你可以控制堆栈的使用你的功能。
        • 指针可以指向您喜欢的任何位置,无论您是否保留了该内存。只有当您读取或写入该地址的内容时,它才会成为问题,因为您几乎肯定会遇到分段错误。尝试更改您的示例以读取指针位置处的 int,然后观察火花飞舞!
        【解决方案10】:

        您只是指向位于数组之前的 4 个字节。

        【讨论】:

          【解决方案11】:

          这是非常明确的定义。您的代码保证被所有编译器接受,并且不会在运行时崩溃。 C/C++ 指针是一种遵循算术规则的数值数据类型。 加法和减法工作,括号符号 [] 只是一种奇特的加法语法。 NULL 字面意思是整数 0。

          这就是 C/C++ 危险的原因。编译器将允许您创建指向任何地方的指针,而无需抱怨。 取消引用您的示例中的野指针*array = 1234; 会产生未定义的行为,从微妙的损坏到崩溃。

          是的,您可以使用它从 1 开始索引。不要这样做! C/C++ 习惯用法是始终从 0 开始索引。其他看到从 1 开始索引的代码的人会很想将其“修复”为从 0 开始索引。

          【讨论】:

          • 实际上它不是有效的 C。单纯的算术导致未定义的行为。
          【解决方案12】:

          如果是以下实验,可能提供的线索不多。而不是将指针值打印为

          printf("%p\n", (void *)array);
          

          ,打印数组元素值

          printf("%d\n", *array);
          

          那是因为用 %p 打印一个指针总是会产生一些输出(没有任何不当行为),但不能从中推断出任何东西。

          【讨论】:

          • 阿伦,不一定。即使评估这样的指针也是不允许的。
          • @Alok:我明白了,谢谢,我可能错过了。可以举个例子吗?
          • 来自 C 常见问题解答:“...如果在减去偏移量时生成了非法地址(可能是因为地址试图“环绕”某个内存的开头),则可能会失败部分)。” c-faq.com/aryptr/non0based.html
          • @Alok:嗯……再次感谢。虽然我可能永远不会这样做,但知道这绝对是件好事。为链接 +1。
          猜你喜欢
          • 2013-01-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-08
          • 2021-02-07
          • 2012-03-25
          • 1970-01-01
          • 2016-07-30
          相关资源
          最近更新 更多