我已经看到它多次断言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
?
Answers:
您的示例是合法的,但这仅是因为您实际上并未使用越界指针。
让我们先处理越界指针(因为这是我最初解释您的问题的方式,在我注意到该示例使用单点结束指针之前):
通常,甚至不允许您创建边界指针。指针必须指向数组中的一个元素,或者指向end之后的一个元素。无处。
甚至不允许指针存在,这显然也不允许您取消引用它。
这是该标准在主题上必须说的:
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]
取消引用array + 5(据我所知,这是合法的,并导致“数组元素类型的不相关对象”,如上所述),然后获取该元素的地址,这似乎也足够合法。因此,他们做的事情并不完全相同,尽管在这种情况下,最终结果是相同的。
是的,这是合法的。根据C99标准草案:
§6.5.2.1第2段:
后缀表达式后跟方括号的表达式
[]
是数组对象元素的下标名称。下标操作符的定义[]
是E1[E2]
相同(*((E1)+(E2)))
。由于适用于二进制+
运算符的转换规则,如果E1
是数组对象(等效于指向数组对象初始元素的指针)并且E2
是整数,E1[E2]
则将其指定为E2
-th元素E1
(从零开始计数)。
§6.5.3.2,第3段(强调我的内容):
一元运算
&
符产生其操作数的地址。如果操作数的类型为“ type ”,则结果的类型为“ pointer to type ”。如果操作数是一元运算*
符的结果,则该运算符和该&
运算符都不会被求值,并且结果都好像都被省略了,除了运算符上的约束仍然适用且结果不是左值。同样,如果操作数是[]
运算符的结果,则&运算符和所*
隐含的一元数都不会[]
被评估,并且结果就像&
是删除了该[]
运算符并将该运算符更改为一个+
运算符。否则,结果是指向由其操作数指定的对象或函数的指针。
§6.5.6,第8段:
将具有整数类型的表达式添加到指针或从指针中减去时,结果将具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,以使结果数组元素和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式
P
指向i
数组对象的-th元素,则表达式(P)+N
(等效为N+(P)
)和(P)-N
(其中N
值为n
)分别指向数组对象的i+n
-th和i−n
-th元素,前提是它们存在。而且,如果表达P
指向数组对象的最后一个元素,表达式(P)+1
指向数组对象的最后一个元素,如果表达式Q
指向一个数组对象的最后一个元素,则表达式(Q)-1
指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素之后,则求值不应产生溢出。否则,行为是不确定的。如果结果指向数组对象的最后一个元素之后,则不应将其用作*
被评估的一元运算符的操作数。
请注意,该标准明确允许指针指向数组末尾的一个元素,前提是它们未取消引用。在6.5.2.1和6.5.3.2中,表达式&array[5]
等于&*(array + 5)
,等于(array+5)
,它指向数组末尾的一个。这不会导致取消引用(按6.5.3.2),因此是合法的。
它是合法的。
根据C ++的gcc文档,&array[5]
是合法的。在C ++和C语言中,您都可以安全地在数组末尾寻址该元素-您将获得一个有效的指针。因此&array[5]
,表达是合法的。
但是,即使指针指向有效地址,尝试取消对未分配内存的指针的引用仍然是未定义的行为。因此,即使指针本身有效,尝试取消引用由该表达式生成的指针仍然是未定义的行为(即非法)。
在实践中,我想通常不会导致崩溃。
编辑:顺便说一下,这通常是实现STL容器的end()迭代器的方式(作为指向过去的指针的指针),因此,这很好地证明了这种做法是合法的。
编辑:哦,现在我看到您不是真的在问持有指向该地址的指针是否合法,但是获取指针的确切方法是否合法。关于这一点,我会请其他回答者来回答。
我认为这是合法的,并且取决于发生的“从左值到右值”的转换。最后一行核心问题232具有以下内容:
我们认为标准中的方法似乎还可以:p = 0; * p; 本质上不是错误。从左值到右值的转换将使其具有未定义的行为
尽管这是一个稍有不同的示例,但它确实表明'*'不会导致左值到右值的转换,因此,如果表达式是'&'的直接操作数,且期望该值是左值,那么就定义了行为。
&*a[n]
问题:“类似地,只要不使用该值,就应允许取消引用指向数组末尾的指针”。不幸的是,自2003年以来,这个问题一直没有解决,标准中还没有解决方案。
我不认为这是非法的,但我确实认为&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)),unary * operator
返回a lvalue referring to the object
。在我对语言的理解中,这不是访问基础内存。然后,该表达式的结果将被unary & operator
运算符立即使用,该运算符返回所引用对象的地址lvalue referring to the object
。
提出了许多其他有关Wikipedia和非规范资源的参考。我发现所有这些都不相关。C ++由标准定义。
我想承认该标准的许多部分我可能没有考虑过,并且可能证明我的上述观点是错误的。 以下提供了NON。如果您给我看一个显示这是UB的标准参考书。我会
这不是一个参数:
并非全世界的所有事物都由C ++标准定义。开开心心
*
执行解除引用。它是取消引用运算符。这就是它的作用。可以争辩的是,随后获得一个指向结果值的新指针(使用&
)是无关紧要的。您不能仅仅给出一个评估序列,给出最终表达的语义,并假装没有发生中间步骤(或者该语言的规则不适用于每个规则)。
the result is an lvalue referring to the object or function to which the expression points.
很明显,如果不存在这样的对象,则不会为此操作符定义任何行为。您的后续声明is returning a lvalue referring to the int (no de-refeference)
对我而言毫无意义。您为什么认为这不是取消引用?
It returns a reference to what is being pointed at.
如果不取消引用,这是什么?这段话说*
执行间接操作,从指针到指针的间接操作称为解引用。您的论点实质上断言指针和引用是同一件事,或者至少是隐式链接,这根本不是事实。int x = 0; int* ptr = &x; int& y = *x;
在这里我取消引用x
。我不需要使用y
它来做到这一点。
*
返回表示表达式所指向对象的左值的一元运算符”?(请注意,该标准的后续句子确实将此过程称为C ++ 11规范中的取消引用)
工作草案(n2798):
“一元&运算符的结果是指向其操作数的指针。操作数应为左值或限定ID。在第一种情况下,如果表达式的类型为“ T”,则结果的类型为“指向T的指针。”“(第103页)
我所知,array [5]并不是一个限定ID(列表在第87页);最接近的似乎是标识符,但是当array是标识符时,array [5]不是。它不是左值,因为“左值是指对象或函数。”(第76页)。array [5]显然不是函数,并且不能保证引用有效的对象(因为array + 5在最后分配的array元素之后)。
显然,它在某些情况下可能会起作用,但是它不是有效的C ++或安全的。
注意:合法的加法运算是通过数组(第113页):
“如果表达式P [指针]指向数组对象的最后一个元素,则表达式(P)+1指向数组对象的最后一个元素之后,如果表达式Q指向数组对象的最后一个元素之后。数组对象,表达式(Q)-1指向数组对象的最后一个元素,如果指针操作数和结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素,则求值不会产生溢出”
但是使用&这样做是不合法的。
即使合法,为什么还要违背约定?无论如何,array + 5会更短,我认为可读性更高。
编辑:如果您希望通过对称可以编写
int* array_begin = array;
int* array_end = array + 5;
C ++标准5.19第4段:
地址常量表达式是指向左值的指针。...应使用一元&运算符...或使用数组(4.2)...类型的表达式显式创建指针。下标运算符[] ...可以用于创建地址常量表达式,但是不能通过使用这些运算符来访问对象的值。如果使用下标运算符,则其操作数之一应为整数常量表达式。
在我看来,&array [5]是合法的C ++,是地址常量表达式。
&array[5]
合法且引用了静态存储期限数组,则该地址常数为常数。(&array[99]
例如,与之相比,本段中没有文字可区分这两种情况)。
如果您的示例不是一般情况,而是特定情况,则可以使用。您可以合法AFAIK将内存移动到分配的内存块之外。但是,它不适用于一般情况,即您尝试访问距离数组末尾1个元素远的元素。
刚搜索的C-Faq:链接文本
这是完全合法的。
当您调用myVec.end()时,来自stl的vector <>模板类正是这样做的:它为您提供了一个指针(此处为迭代器),该指针指向数组末尾的一个元素。