通过C ++标准的下标:legal来获取一个过去的数组元素的地址?

时间:2009-06-12 18:08:51

标签: c++ c standards language-lawyer

我已经看过它多次断言现在C ++标准不允许使用以下代码:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];

在此上下文中是&array[5]合法的C ++代码吗?

如果可能的话,我希望得到一个参考标准的答案。

知道它是否符合C标准也很有趣。如果它不是标准的C ++,为什么决定以不同于array + 5&array[4] + 1的方式对待它?

13 个答案:

答案 0 :(得分:39)

是的,这是合法的。来自C99 draft standard

§6.5.2.1,第2段:

  

后缀表达式后跟方括号[]中的表达式是下标   指定数组对象的元素。下标运算符[]的定义   是E1[E2](*((E1)+(E2)))相同。由于转换规则   应用于二进制+运算符,如果E1是一个数组对象(等效地,指向   数组对象的初始元素)和E2是一个整数,E1[E2]表示E2 - th   E1的元素(从零开始计算)。

§6.5.3.2,第3段(强调我的):

  

一元&运算符生成其操作数的地址。如果操作数的类型为'' type '',   结果有类型''指向 type ''的指针。如果操作数是一元*运算符的结果,   该运算符和&运算符都不会被评估,结果就像两者都一样   省略,除了对运算符的约束仍然适用且结果不是a   左值。同样,如果操作数是[]运算符的结果,则&运算符和*隐含的一元[]被评估,结果就像&运算符一样   已删除,[]运算符已更改为+运算符。否则,结果是   指向由其操作数指定的对象或函数的指针。

§6.5.6,第8段:

  

当指针中添加或减去具有整数类型的表达式时,   result具有指针操作数的类型。如果指针操作数指向的元素   一个数组对象,并且该数组足够大,结果指向一个偏移的元素   原始元素使得结果和原始的下标不同   数组元素等于整数表达式。换句话说,如果表达式P指向   数组对象的i - 元素,表达式(P)+N(等效地,N+(P))和   (P)-N(其中N的值为n)分别指向i+n - 和i−n - 元素   数组对象,只要它们存在。此外,如果表达式P指向最后一个   数组对象的元素,表达式(P)+1指向一个超过最后一个元素的元素   数组对象,如果表达式Q指向一个数组对象的最后一个元素,   表达式(Q)-1指向数组对象的最后一个元素。如果两个指针   操作数和结果指向同一个数组对象的元素,或者指向最后一个数组对象的元素   数组对象的元素,评估不得产生溢出;否则,   行为未定义。如果结果指向一个超过数组对象的最后一个元素的那个,那么   不得用作被评估的一元*运算符的操作数。

请注意,标准明确允许指针将一个元素指向数组的末尾,只要它们未被解除引用。在6.5.2.1和6.5.3.2中,表达式&array[5]等同于&*(array + 5),它相当于(array+5),它指向一个超过数组末尾的{{1}}。这不会导致取消引用(6.5.3.2),因此它是合法的。

答案 1 :(得分:36)

你的例子是合法的,但只是因为你实际上并没有使用越界指针。

让我们首先处理超出界限的指针(因为这是我最初解释你的问题的方式,之前我注意到这个例子使用的是一个过去的结束指针):

一般情况下,您甚至不允许创建越界指针。指针必须指向数组中的元素,或指向结尾的。其他地方。

甚至不允许指针存在,这意味着你显然也不允许取消引用它。

以下是该标准对该主题的评价:

5.7:5:

  

当一个表达式有积分时   类型被添加到或从中减去   指针,结果有类型   指针操作数。如果指针   操作数指向一个元素   数组对象,数组很大   够了,结果指向了   元素偏离原始   元素这样的差异   结果和的下标   原始数组元素等于   积分表达。换一种说法,   如果表达式P指向第i个   数组对象的元素,   表达式(P)+ N(等效地,   N +(P))和(P)-N(其中N具有   值n)分别指向   第i + n和第i-n个元素   数组对象,只要它们存在。   而且,如果表达P点   到数组的最后一个元素   对象,表达式(P)+1分   一个超过数组的最后一个元素   对象,如果表达式Q指向   一个超过数组的最后一个元素   对象,表达式(Q)-1指向   数组对象的最后一个元素。   如果指针操作数和   结果指向相同的元素   数组对象,或者是最后一个对象   数组对象的元素,   评估不得产生   溢流; 否则,行为是   理解过程科幻奈德

(强调我的)

当然,这是针对运营商+的。所以,为了确保,这是标准关于数组下标的内容:

5.2.1:1:

  

表达式E1[E2]*((E1)+(E2))

相同(通过定义)

当然,有一个明显的警告:你的例子实际上并没有显示越界指针。它使用“一个结束”指针,这是不同的。允许指针存在(如上所述),但据我所知,标准对解除引用它没​​有任何说明。我能找到的最接近的是3.9.2:3:

  

[注意:例如,将考虑超过数组末尾的地址(5.7)   指向可能位于该地址的数组元素类型的不相关对象。 - 后注]

在我看来,暗示是的,你可以合法地取消引用它,但读取或写入该位置的结果是未指定的。

感谢ilproxyil在这里纠正最后一位,回答你问题的最后部分:

  • array + 5实际上并非如此 简单地说,取消引用任何东西 创建一个指向结尾的指针 array
  • &array[4] + 1解除引用 array+4(非常安全), 获取该左值的地址,并且 在该地址中添加一个,即 导致一个过去的结束指针 (但指针永远不会得到 解除引用。
  • &array[5]取消引用数组+5 (据我所知,这是合法的, 并导致“一个无关的对象 数组的元素类型“,作为 上面说的,然后拿走了 该元素的地址,也是 似乎足够合法。

所以他们不会做同样的事情,尽管在这种情况下,最终结果是一样的。

答案 2 :(得分:17)

合法。

According to the gcc documentation for C++&array[5]是合法的。在C ++ and in C中,您可以安全地在数组末尾处理一个元素 - 您将获得一个有效的指针。所以&array[5]作为表达是合法的。

但是,即使指针指向有效地址,尝试取消引用指向未分配内存的指针仍然是未定义的行为。因此,即使指针本身有效,尝试取消引用该表达式生成的指针仍然是未定义的行为(即非法)。

在实践中,我想它通常不会导致崩溃。

编辑:顺便说一下,这通常是如何实现STL容器的end()迭代器(作为指向一端的指针),这样就可以很好地证明这种做法是合法的。 / p>

编辑:哦,现在我看到你并没有真正询问是否持有指向该地址的指针是合法的,但是如果获得指针的确切方法是合法的。我会按照其他问题答复。

答案 3 :(得分:9)

我认为这是合法的,这取决于'左值'价值转换的发生。最后一行核心问题232具有以下内容:

  

我们同意标准中的方法似乎没问题:p = 0; * P;本质上不是一个错误。左值到右值的转换会给它带来未定义的行为

虽然这是一个稍微不同的例子,但它所表明的是'*'不会导致左值转换为左值,因此,表达式是'&'的直接操作数。这需要一个左值,然后定义行为。

答案 4 :(得分:8)

我不相信这是非法的,但我确实认为& array [5]的行为是未定义的。

  • 5.2.1 [expr.sub] E1 [E2]与*((E1)+(E2))相同(按照定义)

  • 5.3.1 [expr.unary.op] unary * operator ...结果是一个左值,引用表达式指向的对象或函数。

此时你有未定义的行为,因为表达式((E1)+(E2))实际上没有指向一个对象,标准确实说明了结果应该是什么,除非它确实如此。

  • 1.3.12 [defns.undefined]当本国际标准忽略任何明确行为定义的描述时,也可能会出现未定义的行为。

如其他地方所述,array + 5&array[0] + 5是获得超出数组末尾的指针的有效且定义明确的方法。

答案 5 :(得分:6)

除了上述答案外,我还要指出运营商&可以为类重写。因此,即使它对POD有效,对于你知道无效的对象也许不是一个好主意(就像首先重写operator&())。

答案 6 :(得分:4)

这是合法的:

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
  

第5.2.1节订阅表达式E1 [E2]与*((E1)+(E2))相同(通过定义)

所以我们可以说array_end也是等价的:

int *array_end = &(*((array) + 5)); // or &(*(array + 5))
  

第5.3.1.1节一元运算符'*':一元*运算符执行间接:它所应用的表达式应该是指向对象类型的指针,或者   指向函数类型的指针,结果是指向表达式指向的对象或函数的左值。   如果表达式的类型是“指向T的指针”,则结果的类型为“T”。[注意:指向不完整类型的指针(其他)   比cv void)可以被解除引用。由此获得的左值可以以有限的方式使用(以初始化参考,用于   例);这个左值不能转换为右值,见4.1。 - 结束说明]

上述重要部分:

  

'结果是一个引用对象或函数的左值'。

一元运算符'*'返回一个引用int(没有de-refeference)的左值。一元运算符'&'然后获取左值的地址。

只要没有对一个越界指针的反引用,那么该标准就完全覆盖了该操作,并且定义了所有行为。所以通过我的阅读,上述内容是完全合法的。

许多STL算法依赖于明确定义的行为这一事实,这是标准委员会已经提到过的一种暗示,我确信有一些内容可以明确地涵盖这一点。

下面的评论部分提供了两个参数:

(请阅读:但它很长,我们俩最终都会感到高兴)

参数1

这是违法的,因为第5.7条第5款

  

当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i - 第n个元素,只要它们存在。此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者一个过去   数组对象的最后一个元素,评估不应产生溢出;否则,行为未定义。

虽然该部分是相关的;它没有显示未定义的行为。我们所讨论的数组中的所有元素要么在数组内,要么在结束之后(由上段明确定义)。

参数2:

下面介绍的第二个参数是:*是去参照运算符 虽然这是用于描述'*'运算符的常用术语;标准中有意避免使用该术语,因为术语“去引用”在语言方面没有很好地定义,对于底层硬件意味着什么。

虽然访问超出数组末尾的内存肯定是未定义的行为。我不相信unary * operator访问内存(读取/写入内存)在此上下文中(不是标准定义的方式)。在此上下文中(由标准定义(见5.3.1.1))unary * operator返回lvalue referring to the object。在我对语言的理解中,这不是对底层内存的访问。然后,unary & operator运算符立即使用此表达式的结果,该运算符返回lvalue referring to the object引用的对象的地址。

介绍了许多其他对维基百科和非规范来源的引用。所有这一切我觉得无关紧要。 C ++由标准定义。

结论:

我愿意承认标准的许多部分我可能没有考虑过,可能证明我的上述论点是错误的。 NON 在下面提供。如果你给我看一个标准参考,表明这是UB。我会

  1. 留下答案。
  2. 全部上限这是愚蠢的,我错了所有人都读。
  3. 这不是一个论点:

      

    并非整个世界中的所有内容都是由C ++标准定义的。敞开心扉。

答案 7 :(得分:2)

即使它是合法的,为什么要脱离惯例呢?无论如何,数组+5更短,在我看来,更具可读性。

编辑:如果你想通过对称来编写

int* array_begin = array; 
int* array_end = array + 5;

答案 8 :(得分:2)

工作草案(n2798):

  

“一元&运算符的结果是   指向其操作数的指针。操作数   应为左值或限定标准。   在第一种情况下,如果是类型的   表达式是“T”,类型   结果是“指向T的指针”“(第103页)

array [5]不是我能说的最合格的id(列表在第87页);最接近的似乎是标识符,但是数组是标识符数组[5]不是。它不是左值,因为“左值是指对象或函数。”(第76页)。 array [5]显然不是函数,并且不保证引用有效对象(因为array + 5在最后一个分配的数组元素之后)。

显然,它可能在某些情况下有效,但它不是有效的C ++或安全。

注意:添加一个经过数组是合法的(第113页):

  

“如果表达式P [指针]   指向数组的最后一个元素   对象,表达式(P)+1分   一个超过数组的最后一个元素   对象,如果表达式Q指向   一个超过数组的最后一个元素   对象,表达式(Q)-1指向   数组对象的最后一个元素。   如果指针操作数和   结果指向相同的元素   数组对象,或者是最后一个对象   数组对象的元素,   评估不得产生   溢流“

但使用&。

这样做是不合法的

答案 9 :(得分:1)

它应该是未定义的行为,原因如下:

  1. 尝试访问越界元素会导致未定义的行为。因此,该标准不禁止在该情况下抛出异常的实现(即,在访问元素之前检查边界的实现)。如果& (array[size])被定义为begin (array) + size,则在越界访问的情况下抛出异常的实现将不再符合标准。

  2. 如果数组不是数组而是一个任意的集合类型,则不可能使这个结果end (array)

答案 10 :(得分:0)

C ++标准,5.19,第4段:

地址常量表达式是指向左值的指针....指针应使用一元&显式创建。 operator ...或使用数组(4.2)...类型的表达式。下标运算符[] ...可用于创建地址常量表达式,但不能通过使用这些运算符来访问对象的值。如果使用了下标运算符,则其操作数之一应为整数常量表达式。

在我看来,& array [5]是合法的C ++,是一个地址常量表达式。

答案 11 :(得分:-1)

如果您的示例不是一般情况而是特定情况,那么它是允许的。您可以合法地,AFAIK,移动一个已分配的内存块。 它对于一般情况不起作用,即你试图从数组的末尾访问更远的元素。

刚刚搜索过C-Faq:link text

答案 12 :(得分:-2)

这完全合法。

向量<>当你调用myVec.end()时,来自stl的模板类就完成了这个:它会得到一个指针(这里作为迭代器),它指向一个元素超过数组的末尾。