通过C ++标准,通过下标:合法的方式来获取一个最后一个数组元素的地址?
我已经看到它现在断言了几次,下面的代码是不允许的C + +标准:
int array[5]; int *array_begin = &array[0]; int *array_end = &array[5];
在这种情况下是否是&array[5]
合法的C ++代码?
如果可能的话,我想要一个参考标准的答案。
知道它是否符合C标准也是有趣的。 如果它不是标准的C ++,那么为什么决定将它与array + 5
或&array[4] + 1
区别开来呢?
你的例子是合法的,但只是因为你实际上并没有使用超出界限的指针。
让我们首先处理出界指针(因为这是我最初解释你的问题的方式,在我注意到这个例子使用了一个过去的指针的情况下):
一般来说,你甚至不允许创build一个超出界限的指针。 指针必须指向数组内的一个元素,或者指向结尾的一个元素。 无处。
指针甚至不允许存在,这意味着你显然不允许解引用它。
以下是关于这个问题的标准:
5.7:5:
当具有整数types的expression式被添加到指针或从指针中减去时,结果具有指针操作数的types。 如果指针操作数指向数组对象的一个元素,并且该数组足够大,则结果指向与原始元素偏移的元素,使得结果数组元素和原始数组元素的下标之差等于整数expression式。 换句话说,如果expression式P指向数组对象的第i个元素,则expression式(P)+ N(等价地,N +(P))和(P)-N(其中N具有值n)到数组对象的第i + n和第i-n个元素,只要它们存在。 此外,如果expression式P指向数组对象的最后一个元素,则expression式(P)+1将指向数组对象的最后一个元素,如果expression式Q指向数组对象的最后一个元素之后,expression式(Q)-1指向数组对象的最后一个元素。 如果指针操作数和结果指向相同数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为是不确定的 。
(重点是我的)
当然,这是为运营商+。 所以可以肯定的是,这里是关于数组下标的标准:
5.2.1:1:
expression式
E1[E2]
与*((E1)+(E2))
是相同的(通过定义*((E1)+(E2))
当然,有一个明显的警告:你的例子实际上并没有显示出界限指针。 它使用了一个“过去的结尾”指针,这是不同的。 指针允许存在(如上所述),但是就我所知,标准没有提及引用它。 我能find的最接近的是3.9.2:3:
[注意:例如,数组(5.7)末尾的地址将被视为指向可能位于该地址的数组元素types的不相关对象。 – 注意]
在我看来,这意味着是的,你可以合法的解除引用,但是读写地址的结果是不明确的。
感谢ilproxyil在这里纠正最后一点,回答你的问题的最后一部分:
-
array + 5
实际上不提取任何东西,它只是创build一个指向array
末尾的指针。 -
&array[4] + 1
dereferencesarray+4
(这是非常安全的),获取该左值的地址,并将该地址加1,这导致了一个过去的结束指针(但该指针永远不会被解除引用。 -
&array[5]
dereferences array + 5(就我所见,这是合法的,并且导致“数组的元素types不相关的对象”,如上所述),然后获取该元素的地址,这也是似乎足够合法。
所以他们不会做相同的事情,虽然在这种情况下,最终的结果是一样的。
是的,这是合法的。 从C99草案标准 :
§6.5.2.1第2段:
后缀expression式后面跟着方括号
[]
的expression式是数组对象元素的下标。 下标运算符[]
的定义是E1[E2]
与(*((E1)+(E2)))
。 由于适用于二元运算符的转换规则,如果E1
是一个数组对象(等价地,指向一个数组对象的初始元素的指针),E2
是一个整数,E1[E2]
指定E2
第E2
个元素E1
(从零开始计数)。
第6.5.3.2节第3段(重点是我的):
一元
&
运算符产生其操作数的地址。 如果操作数的types是'' type '',那么结果的types是指向''的指针。 如果操作数是一元*
运算符的结果,那么该运算符和&
运算符都不会被计算,结果就好像两个都被省略一样,除了运算符上的约束仍然适用并且结果不是左值。 类似地, 如果操作数是[]
运算符的结果,则评估[]
所隐含的&运算符和一元运算符*
,结果就好像&
运算符被删除,[]
运算符被更改为一个+
运算符 。 否则,结果是指向由操作数指定的对象或函数的指针。
第6.5.6节第8段:
当具有整数types的expression式被添加到指针或从指针中减去时,结果具有指针操作数的types。 如果指针操作数指向数组对象的一个元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果数组元素和原始数组元素的下标之差等于整数expression式。 换句话说,如果expression式
P
指向数组对象的第i
个元素,则expression式(P)+N
(等价地,N+(P)
)和(P)-N
(其中N
具有值n
)分别为数组对象的第i+n
个元素和第i+n
i−n
个元素,只要它们存在。 此外,如果expression式P
指向数组对象的最后一个元素,则expression式(P)+1
指向数组对象的最后一个元素,如果expression式Q
指向一个数组对象的最后一个元素,expression式(Q)-1
指向数组对象的最后一个元素。 如果指针操作数和结果指向相同数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为是不确定的。 如果结果指向一个超过数组对象的最后一个元素,则不应将其用作所评估的一元运算符的操作数。
请注意,标准明确允许指针指向超过数组末尾的一个元素, 前提是它们不被解除引用 。 通过6.5.2.1和6.5.3.2,expression式&array[5]
等价于&*(array + 5)
,它等于(array+5)
,它指向数组的末尾。 这不会导致解除引用(6.5.3.2),所以它是合法的。
这是合法的。
根据C ++的gcc文档 , &array[5]
是合法的。 在C ++ 和C中,你都可以安全的访问数组的末尾的元素 – 你将得到一个有效的指针。 所以&array[5]
作为一个expression式是合法的。
但是,即使指针指向一个有效地址,试图取消引用未分配内存的指针仍然是未定义行为。 所以试图取消引用该expression式生成的指针仍然是未定义的行为(即非法),即使指针本身是有效的。
实际上,我想这通常不会导致崩溃。
编辑:顺便说一下,这通常是如何实现STL容器的end()迭代器(作为指向一个过去的结尾),所以这是一个非常好的certificate实践是合法的。
编辑:哦,现在我看到你不是真的问是否持有指向该地址的指针是合法的,但如果确切的方式获得指针是合法的。 我会顺从其他答复者。
我相信这是合法的,这取决于“左值与右值”的转换。 最后一行核心问题232有以下几点:
我们同意标准中的方法似乎没有问题:p = 0; * P; 本质上不是一个错误。 一个左值到右值的转换会给它一个未定义的行为
虽然这是一个稍微不同的例子,但是它显示的是'*'不会导致右值转换,所以,假设expression式是'&'的立即操作数,它需要一个左值然后定义行为。
我不认为这是违法的,但是我相信&array [5]的行为是不确定的。
-
5.2.1 [expr.sub] E1 [E2]与*((E1)+(E2))是相同的(根据定义)
-
5.3.1 [expr.unary.op]一元运算符…结果是一个左值,指向expression式指向的对象或函数。
在这一点上,你有没有定义的行为,因为expression式((E1)+(E2))实际上并没有指向一个对象,标准没有说明结果应该是什么,除非它。
- 1.3.12 [defns.undefined]当本标准忽略任何明确的行为定义的描述时,也可能会出现未定义的行为。
如其他地方所指出的, array + 5
和&array[0] + 5
是有效的,并且是在数组末尾获得指针的有效方式。
除了上面的答案之外,我还会指出operator&可以被覆盖的类。 所以即使它对于POD是有效的,对于一个你认为无效的对象来说,这也不是一个好主意(就像首先重写operator&()一样)。
这是合法的:
int array[5]; int *array_begin = &array[0]; int *array_end = &array[5];
5.2.1节下标expression式E1 [E2]与*((E1)+(E2))是相同的(通过定义)
所以通过这个我们可以说array_end也是等价的:
int *array_end = &(*((array) + 5)); // or &(*(array + 5))
5.3.1.1节一元运算符'*':一元运算符执行间接处理:应用它的expression式应该是一个指向对象types的指针,或者一个指向函数types的指针, 结果是指向对象的左值或expression指向的function。 如果expression式的types是“指向T的指针”,那么结果的types是“T”。[注意:指向不完整types(cv void除外)的指针可以被取消引用。 由此得到的左值可以以有限的方式使用(例如初始化参考); 这个左值不能转换为右值,参见4.1。 – 结束注意]
以上的重要部分:
“结果是一个左值指的是对象或函数”。
一元运算符“*”正在返回一个指向int的左值(没有取消重复)。 一元运算符“&”然后得到左值的地址。
只要没有超出界限指针的引用,那么操作就完全被标准所覆盖,并且所有的行为都被定义了。 所以通过我的阅读上面是完全合法的。
事实上,很多STLalgorithm都依赖于行为的定义,这是标准委员会已经提出的一个暗示,我相信有一个明确的说法。
下面的评论部分提出了两个参数:
(请阅读:但很长,我们都结束了triclish)
参数1
由于第5.7节第5段,这是非法的
当具有整数types的expression式被添加到指针或从指针中减去时,结果具有指针操作数的types。 如果指针操作数指向数组对象的一个元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果数组元素与原始数组元素的下标之差等于整数expression式。 换言之,如果expression式P指向数组对象的第i个元素,则expression式(P)+ N(相当于N +(P))和(P)-N(其中N具有值n)到数组对象的第i + n个元素和第i-n个元素,只要它们存在。 此外,如果expression式P指向数组对象的最后一个元素,则expression式(P)+1将指向数组对象的最后一个元素,如果expression式Q指向一个数组对象的最后一个元素,expression式(Q)-1指向数组对象的最后一个元素。 如果指针操作数和结果指向相同数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为是不确定的。
虽然该部分是相关的, 它不显示未定义的行为。 我们所讨论的数组中的所有元素都在数组中,或者在数组的末尾(上面的段落定义好)中。
参数2:
下面介绍的第二个参数是: *
是去参考运算符。
尽pipe这是用来描述'*'运算符的通用术语, 这个术语在标准中是故意避免的,因为术语“去引用”在语言方面没有很好的定义,对底层硬件意味着什么。
尽pipe访问超出数组末尾的内存肯定是未定义的行为。 我不相信unary * operator
在这个上下文中访问内存(读/写内存)(不是以标准定义的方式)。 在这个上下文中(由标准定义(见5.3.1.1)), unary * operator
返回一个lvalue referring to the object
的lvalue referring to the object
。 根据我对语言的理解,这不是访问底层的内存。 然后这个expression式的结果立即被unary & operator
符unary & operator
符使用,该unary & operator
返回由lvalue referring to the object
的lvalue referring to the object
。
介绍了许多其他维基百科和非规范来源的参考资料。 所有我觉得无关的。 C ++由标准定义 。
结论:
我不禁要承认,我可能没有考虑过这个标准的很多部分,可能certificate我上面的观点是错误的。 下面提供了NON 。 如果你给我看一个显示这是UB的标准参考。 我会
- 留下答案。
- 把所有的大写,这是愚蠢的,我错了所有人阅读。
这不是一个说法:
并非全世界的一切都是由C ++标准定义的。 打开你的想法
即使是合法的,为什么离开公约呢? array + 5无论如何都是短的,在我看来,更具可读性。
编辑:如果你想通过对称你可以写
int* array_begin = array; int* array_end = array + 5;
工作草案( n2798 ):
“一元&运算符的结果是一个指向它的操作数的指针,操作数应该是一个左值或一个限定的id,在第一种情况下,如果expression式的types是”T“,结果的types是”指向T.“”(第103页)
数组[5]并不是一个合格的id,我可以告诉(列表在第87页); 最接近似乎是标识符,但是当数组是一个标识符数组[5]不是。 这不是一个左值,因为“左值是指一个物体或function”(第76页)。 数组[5]显然不是一个函数,并不保证引用一个有效的对象(因为数组+5是在最后分配的数组元素之后)。
显然,它可能在某些情况下工作,但它不是有效的C ++或安全的。
注意:添加一个数组(第113页)是合法的:
“如果expression式P [指针]指向数组对象的最后一个元素,则expression式(P)+1指向数组对象的最后一个元素之后的一个,如果expression式Q指向一个超过数组对象,则expression式(Q)-1指向数组对象的最后一个元素,如果指针操作数和结果都指向相同数组对象的元素,或者指向数组对象的最后一个元素之后,则评估不得产生溢stream“
但是这样做使用&是不合法的。
应该是未定义的行为,原因如下:
-
尝试访问超出界限的元素会导致未定义的行为。 因此,在这种情况下,标准并不禁止一个实现抛出一个exception(即一个元素访问之前的实现检查边界)。 如果将
& (array[size])
定义为begin (array) + size
,则在出界访问的情况下抛出exception的实现将不再符合标准。 -
如果array不是一个数组,而是一个任意的集合types,那么将这个yield
end (array)
是不可能的。
对于C ++来说,答案似乎在这里:
http://eel.is/c++draft/conv#lval-2
当一个左值到右值的转换被应用到一个expression式e中,并且(2.1)e没有被潜在的评估,或者(2.2)e的评估结果评估了e ,ex指定一个variablesx,这个variables不是被ex使用的,所以被引用的对象中包含的值不被访问。
C ++标准5.19,第4段:
地址常量expression式是一个指向左值的指针….指针应该使用一元&运算符…或使用数组(4.2)…types的expression式显式创build。 下标操作符[] …可用于创build地址常量expression式,但不得使用这些操作符来访问对象的值。 如果使用下标运算符,则其操作数中的一个应为整型常量expression式。
看起来像&array [5]是合法的C ++,是一个地址常量expression式。
如果您的示例不是一般情况,而是特定情况,则允许。 你可以合法的 ,AFAIK,移动一个过去分配的内存块。 It does not work for a generic case though ie where you are trying to access elements farther by 1 from the end of an array.
Just searched C-Faq : link text
It is perfectly legal.
The vector<> template class from the stl does exactly this when you call myVec.end(): it gets you a pointer (here as an iterator) which points one element past the end of the array.