我已经看到它多次断言 C++ 标准不允许以下代码:
int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
在这种情况下是&array[5]
合法的 C++ 代码吗?
如果可能的话,我想要一个参考标准的答案。
知道它是否符合 C 标准也会很有趣。如果它不是标准的 C++,为什么决定将它与array + 5
or区别对待&array[4] + 1
?
我已经看到它多次断言 C++ 标准不允许以下代码:
int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
在这种情况下是&array[5]
合法的 C++ 代码吗?
如果可能的话,我想要一个参考标准的答案。
知道它是否符合 C 标准也会很有趣。如果它不是标准的 C++,为什么决定将它与array + 5
or区别对待&array[4] + 1
?
是的,这是合法的。来自C99 标准草案:
§6.5.2.1,第 2 段:
后缀表达式后跟方括号
[]
中的表达式是数组对象元素的下标指定。下标运算符的定义[]
与E1[E2]
相同(*((E1)+(E2)))
。由于适用于二元+
运算符的转换规则,ifE1
是一个数组对象(相当于,一个指向数组对象的初始元素的指针)并且E2
是一个整数,E1[E2]
指定E2
第一个元素E1
(从零开始计数)。
§6.5.3.2,第 3 段(强调我的):
一元运算
&
符产生其操作数的地址。如果操作数的类型为 ''<em>type'',则结果的类型为 ''pointer to type ''。如果操作数是一元运算符的结果,则该运算*
符和该&
运算符都不会被计算并且结果就像两者都被省略了,除了对运算符的约束仍然适用并且结果不是左值。类似地,如果操作数是运算符的结果[]
,则 & 运算符和*
隐含的一元都不会[]
被计算,结果就像&
删除了运算符并将[]
运算符更改为+
运算符. 否则,结果是指向其操作数指定的对象或函数的指针。
§6.5.6,第 8 段:
当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式
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)
,它指向数组末尾之后的位置。这不会导致取消引用(通过 6.5.3.2),因此它是合法的。
您的示例是合法的,但这仅仅是因为您实际上并没有使用越界指针。
让我们首先处理越界指针(因为这就是我最初解释你的问题的方式,在我注意到该示例使用了一个过去的指针之前):
通常,您甚至不允许创建越界指针。指针必须指向数组中的一个元素,或者指向end之后的一个元素。无处。
甚至不允许指针存在,这意味着您显然也不允许取消引用它。
以下是标准对这个主题的看法:
5.7:5:
当具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于积分表达式。换句话说,如果表达式 P 指向数组对象的第 i 个元素,则表达式 (P)+N(等效于 N+(P))和 (P)-N(其中 N 的值为 n)指向分别到数组对象的第 i+n 个和第 i-n 个元素,前提是它们存在。此外,如果表达式 P 指向数组对象的最后一个元素,表达式 (P)+1 指向数组对象的最后一个元素,如果表达式 Q 指向数组对象的最后一个元素,则表达式 (Q)-1 指向数组对象的最后一个元素. 如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。
(强调我的)
当然,这是针对 operator+ 的。因此,为了确定,以下是标准对数组下标的说明:
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]
取消引用 array+5 (据我所知,这是合法的,并导致“数组元素类型的不相关对象”,如上所述),然后获取该元素的地址,这似乎也足够合法。所以他们做的事情并不完全相同,尽管在这种情况下,最终结果是一样的。
这是合法的。
根据 C++ 的 gcc 文档,&array[5]
是合法的。在 C++和 C 中,您可以安全地处理数组末尾之后的元素——您将获得一个有效的指针。所以&array[5]
作为一个表达式是合法的。
但是,尝试取消引用指向未分配内存的指针仍然是未定义的行为,即使指针指向有效地址。因此,即使指针本身是有效的,尝试取消引用由该表达式生成的指针仍然是未定义的行为(即非法)。
但在实践中,我想它通常不会导致崩溃。
编辑:顺便说一句,这通常是 STL 容器的 end() 迭代器的实现方式(作为指向过去的指针),因此这是合法实践的一个很好的证明。
编辑:哦,现在我看到你并不是真的在问持有指向该地址的指针是否合法,而是获取指针的确切方式是否合法。我会听从其他回答者的意见。
我相信这是合法的,这取决于发生的“左值到右值”转换。最后一行核心问题232具有以下内容:
我们同意标准中的方法似乎没问题:p = 0;*p; 本质上不是错误。左值到右值的转换会给它未定义的行为
虽然这是一个稍微不同的例子,但它确实表明“*”不会导致左值到右值的转换,因此,假设表达式是“&”的直接操作数,它需要一个左值,那么行为就被定义了。
我不认为这是非法的,但我确实相信 &array[5] 的行为是未定义的。
5.2.1 [expr.sub] E1[E2] 与 *((E1)+(E2)) 相同(根据定义)
5.3.1 [expr.unary.op] 一元 * 运算符 ...结果是一个左值,引用表达式指向的对象或函数。
此时您有未定义的行为,因为表达式 ((E1)+(E2)) 实际上并未指向对象,并且标准确实说明了结果应该是什么,除非它确实如此。
如其他地方所述,array + 5
并且&array[0] + 5
是获取超出数组末尾的指针的有效且定义明确的方法。
除了上述答案之外,我还要指出 operator& 可以被类覆盖。因此,即使它对 POD 有效,但对您知道无效的对象执行此操作可能不是一个好主意(就像首先覆盖 operator&() 一样)。
这是合法的:
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 的左值(无取消引用)。一元运算符 '&' 然后获取左值的地址。
只要没有解除对越界指针的引用,那么该操作就完全被标准覆盖并且所有行为都被定义。因此,通过我的阅读,上述内容是完全合法的。
许多 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))返回 a 。在我对语言的理解中,这不是对底层记忆的访问。然后,该表达式的结果将立即由返回由 . 引用的对象的地址的运算符使用。unary * operator
lvalue referring to the object
unary & operator
lvalue referring to the object
还提供了对 Wikipedia 和非规范来源的许多其他参考。所有这些我都觉得无关紧要。C++ 由标准定义。
我愿意承认标准的许多部分我可能没有考虑过,并且可能证明我的上述论点是错误的。NON在下面提供。如果你给我看一个显示这是 UB 的标准参考。我会
这不是一个论点:
并非全世界的所有事物都是由 C++ 标准定义的。敞开心扉。
就算是合法的,为什么要背离惯例呢?无论如何,array + 5 更短,并且在我看来,更具可读性。
编辑:如果你想通过对称你可以写
int* array_begin = array;
int* array_end = array + 5;
工作草案(n2798):
“一元 & 运算符的结果是指向其操作数的指针。操作数应该是左值或限定 ID。在第一种情况下,如果表达式的类型是“T”,则结果的类型是“指向 T 的指针。””(第 103 页)
据我所知,array[5] 不是限定 ID(列表在第 87 页);最接近的似乎是标识符,但虽然数组是标识符,但数组 [5] 不是。它不是左值,因为“左值指的是对象或函数。”(第 76 页)。array[5] 显然不是一个函数,并且不能保证引用一个有效的对象(因为 array + 5 在最后分配的数组元素之后)。
显然,它可能在某些情况下有效,但它不是有效的 C++ 或安全的。
注意:在数组中添加一个是合法的(第 113 页):
“如果表达式 P [a pointer] 指向数组对象的最后一个元素,则表达式 (P)+1 指向数组对象的最后一个元素,如果表达式 Q 指向数组对象的最后一个元素,则数组对象,表达式 (Q)-1 指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则求值不会产生溢出”
但是使用 & 这样做是不合法的。
它应该是未定义的行为,原因如下:
Trying to access out-of-bounds elements results in undefined behaviour. Hence the standard does not forbid an implementation throwing an exception in that case (i.e. an implementation checking bounds before an element is accessed). If & (array[size])
were defined to be begin (array) + size
, an implementation throwing an exception in case of out-of-bound access would not conform to the standard anymore.
It's impossible to make this yield end (array)
if array is not an array but rather an arbitrary collection type.
C++ 标准,5.19,第 4 段:
地址常量表达式是一个指向左值的指针......该指针应显式创建,使用一元 & 运算符......或使用数组 (4.2)......类型的表达式。下标运算符 []... 可用于创建地址常量表达式,但不能通过使用这些运算符访问对象的值。如果使用下标运算符,则其操作数之一应为整数常量表达式。
在我看来 &array[5] 是合法的 C++,是一个地址常量表达式。
如果您的示例不是一般情况而是特定情况,那么它是允许的。您可以合法地,AFAIK,将一个移过分配的内存块。它不适用于一般情况,即您尝试访问距离数组末尾更远的元素 1。
刚刚搜索 C-Faq :链接文本
这是完全合法的。
当您调用 myVec.end() 时,stl 中的 vector<> 模板类正是这样做的:它为您提供一个指针(此处为迭代器),该指针指向数组末尾的一个元素。