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

Zan*_*ynx 75 c c++ standards language-lawyer

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

int array[5];
int *array_begin = &array[0];
int *array_end = &array[5];
Run Code Online (Sandbox Code Playgroud)

&array[5]在这种情况下是合法的C++代码吗?

如果可能的话,我想要一个参考标准的答案.

知道它是否符合C标准也很有趣.如果它不是标准的C++,为什么决定做出从不同的治疗呢array + 5还是&array[4] + 1

Ada*_*eld 40

是的,这是合法的.从C99标准草案:

§6.5.2.1,第2段:

后缀表达式后跟方括号中的表达式[]是数组对象元素的下标名称.下标操作符的定义[]E1[E2]相同(*((E1)+(E2))).由于适用于二元+运算符的转换规则,if E1是一个数组对象(等效地,指向数组对象的初始元素的指针)并且E2是一个整数,因此E1[E2]指定E2-th元素E1(从零开始计数).

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

一元运算&符产生其操作数的地址.如果操作数的类型是"" 类型 "",结果类型为""指针键入 "".如果操作数是一元运算*符的结果,则不会对该运算符和&运算符进行求值,结果就好像两者都被省略,除了对运算符的约束仍然适用且结果不是左值.类似地,如果操作数是[]运算符的结果,那么&运算符和由此*隐含的一元运算符都不会[]被计算,结果就好像&运算符被移除并且[]运算符被更改为+运算符.否则,结果是指向由其操作数指定的对象或函数的指针.

§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标准是C++标准中的规范性参考.这意味着C++标准中引用的C标准中的规定是C++标准的一部分.这并不意味着C标准中的所有内容都适用.特别是附件C是提供信息的,而不是规范性的,因此仅仅因为本节未突出显示差异并不意味着C'版本'适用于C++. (11认同)
  • 他明确询问了有关 C++ 的问题。这是两者之间移植时无法依赖的微妙差异。 (4认同)
  • 他询问两者:"知道它是否符合C标准也会很有趣." (3认同)
  • @Matthew Flaschen:C++ 标准通过引用合并了 C 标准。附录 C.2 包含变更列表(ISO C 和 ISO C++ 之间的不兼容性),并且没有任何变更与这些条款相关。因此,&array[5] 在 C 和 C++ 中是合法的。 (2认同)
  • @PoweredByRice:这在 C99 中是合法的,请阅读上面标准的引用段落,其中明确指出“&”和“*”运算符都不会被评估。C++ 则不同。据我所知,C++11(在最初撰写此答案时仍在起草中)没有类似的条款。 (2认同)

jal*_*alf 38

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

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

通常,您甚至不允许创建越界指针.指针必须指向数组中的元素,或者指向结尾的元素.无处.

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

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

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] + 1dereferences array+4(这是非常安全的),获取该左值的地址,并向该地址添加一个,这会产生一个一个接一个的指针(但该指针永远不会被解除引用.
  • &array[5] 取消引用数组+5(据我所知,这是合法的,并导致"数组元素类型的无关对象",如上所述),然后获取该元素的地址,这似乎也足够合法.

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

  • 最后一句不正确."array + 5"和"&array [4] + 1"在结束时不会取消引用,而array [5] DOES.(我也假设你的意思是数组[5],但评论仍然有效).前两个只是指出一个结束. (5认同)
  • &array [5]指向一个过去.但是,这不是获得该地址的合法方式. (4认同)
  • @jalf注意 - 我认为它只是想说"a + sizeof a"对于"&b"同样有效,如果b在数组"a"之后直接分配,并且结果地址同样"指向"相同宾语.不多.请记住,所有注释都是提供信息的(非规范性的):如果它会说明这样一些基本的重要事实,比如数组对象之后的对象位于过去的末端,那么这样的规则就需要做成规范 (2认同)
  • 就ANSI C(C89/C90)而言,这是正确的答案.如果你遵循标准的字母,&array [5]在技术上是无效的,而array + 5是有效的,即使几乎每个编译器都会为两个表达式生成相同的代码.C99更新标准以明确允许&array [5].有关详细信息,请参阅我的回答 (2认同)
  • @jalf,也是注释的整个文本以"如果T类型的对象位于地址A ......"开头.< - 表示"以下文本假设地址A处有一个对象".所以你的引言不会(在这种情况下不能)说地址A总有一个对象. (2认同)

Tyl*_*nry 17

合法的.

根据C++的gcc文档,&array[5]是合法的.在C++ 和C中,您可以安全地在数组末尾处理一个元素 - 您将获得一个有效的指针.所以&array[5]表达是合法的.

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

实际上,我想它通常不会导致崩溃.

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

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

  • 你引用的页面(正确地)表示在结束时*指向*是合法的.&array [5],技术上首先解引用(数组+5),然后再次引用它.所以它在技术上是这样的:(&*(array + 5)).幸运的是,编译器足够聪明,知道&*可以被解决.但是,他们没有*做到这一点,因此,我会说它是UB. (8认同)
  • 我会说你是对的,当且仅当C++规范没有说&*必须被视为无操作时.我想它可能不会这么说. (4认同)
  • @Evan:还有更多.查看核心问题232的最后一行:http://std.dkuug.dk/JTC1/SC22/WG21/docs/cwg_active.html#232.最后一个例子看起来错了 - 但他们清楚地解释了区别在于"左值到右值"转换,在这种情况下不会发生. (4认同)
  • 它与人们一直在讨论的"引用到空"的事情是同一种未定义的行为,似乎所有人都投了答案说"它是未定义的行为" (2认同)

Ric*_*den 9

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

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

虽然这是稍微不同的示例,但它显示的是'*'不会导致左值到左值转换,因此,假设表达式是'&'的直接操作数,它需要左值,则定义行为.


CB *_*ley 8

我不相信它是非法的,但我确实认为&array [5]的行为是不确定的.

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

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

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

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

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


Tod*_*ner 7

除了上面的答案,我还会指出运算符&可以为类重写.因此,即使它对POD有效,对于你知道无效的对象也许不是一个好主意(很像首先重写operator&()).

  • 关于运算符和讨论的+1,即使专家建议永远不要覆盖它,因为一些STL容器依赖于它返回指向元素的指针.在他们知道更好之前,这是进入标准的事情之一. (4认同)