我已经看过它多次断言现在C ++标准不允许使用以下代码:
int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
在此上下文中是&array[5]
合法的C ++代码吗?
如果可能的话,我希望得到一个参考标准的答案。
知道它是否符合C标准也很有趣。如果它不是标准的C ++,为什么决定以不同于array + 5
或&array[4] + 1
的方式对待它?
答案 0 :(得分:39)
是的,这是合法的。来自C99 draft standard:
§6.5.2.1,第2段:
后缀表达式后跟方括号
[]
中的表达式是下标 指定数组对象的元素。下标运算符[]
的定义 是E1[E2]
与(*((E1)+(E2)))
相同。由于转换规则 应用于二进制+
运算符,如果E1
是一个数组对象(等效地,指向 数组对象的初始元素)和E2
是一个整数,E1[E2]
表示E2
- thE1
的元素(从零开始计算)。
§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))实际上没有指向一个对象,标准确实说明了结果应该是什么,除非它确实如此。
如其他地方所述,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算法依赖于明确定义的行为这一事实,这是标准委员会已经提到过的一种暗示,我确信有一些内容可以明确地涵盖这一点。
(请阅读:但它很长,我们俩最终都会感到高兴)
这是违法的,因为第5.7条第5款
当向指针添加或从指针中减去具有整数类型的表达式时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向偏离原始元素的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i - 第n个元素,只要它们存在。此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者一个过去 数组对象的最后一个元素,评估不应产生溢出;否则,行为未定义。
虽然该部分是相关的;它没有显示未定义的行为。我们所讨论的数组中的所有元素要么在数组内,要么在结束之后(由上段明确定义)。
下面介绍的第二个参数是:*
是去参照运算符
虽然这是用于描述'*'运算符的常用术语;标准中有意避免使用该术语,因为术语“去引用”在语言方面没有很好地定义,对于底层硬件意味着什么。
虽然访问超出数组末尾的内存肯定是未定义的行为。我不相信unary * operator
访问内存(读取/写入内存)在此上下文中(不是标准定义的方式)。在此上下文中(由标准定义(见5.3.1.1))unary * operator
返回lvalue referring to the object
。在我对语言的理解中,这不是对底层内存的访问。然后,unary & operator
运算符立即使用此表达式的结果,该运算符返回lvalue referring to the object
引用的对象的地址。
介绍了许多其他对维基百科和非规范来源的引用。所有这一切我觉得无关紧要。 C ++由标准定义。
我愿意承认标准的许多部分我可能没有考虑过,可能证明我的上述论点是错误的。 NON 在下面提供。如果你给我看一个标准参考,表明这是UB。我会
这不是一个论点:
并非整个世界中的所有内容都是由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)
它应该是未定义的行为,原因如下:
尝试访问越界元素会导致未定义的行为。因此,该标准不禁止在该情况下抛出异常的实现(即,在访问元素之前检查边界的实现)。如果& (array[size])
被定义为begin (array) + size
,则在越界访问的情况下抛出异常的实现将不再符合标准。
如果数组不是数组而是一个任意的集合类型,则不可能使这个结果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的模板类就完成了这个:它会得到一个指针(这里作为迭代器),它指向一个元素超过数组的末尾。