【问题标题】:Obtaining a past-the-end pointer using the address of an array使用数组的地址获取结束指针
【发布时间】:2013-12-14 01:07:25
【问题描述】:

在 C 和 C++ 中,使用结束指针来编写可以对任意大数组进行操作的函数通常很有用。 C++ 提供了一个std::end 重载以使这更容易。另一方面,在 C 中,我发现像这样定义和使用宏的情况并不少见:

#define ARRAYLEN(array) (sizeof(array)/sizeof(array[0]))

// ...

int a [42];
do_something (a, a + ARRAYLEN (a));

我还看到了一个指针算术技巧,用于让此类函数对单个对象进行操作:

int b;
do_something (&b, &b + 1);

我突然想到可以用数组来做类似的事情,因为它们被 C(而且,我相信,C++)认为是“完整的对象”。给定一个数组,我们可以在它之后立即派生一个指向数组的指针,取消对该指针的引用,并对结果引用的数组使用数组到指针的转换来获得原始数组的结束指针:

#define END(array) (*(&array + 1))

// ...

int a [42];
do_something (a, END (a));

我的问题是:在取消引用指向不存在的数组对象的指针时,这段代码是否表现出未定义的行为?我对 C 和 C++ 的最新版本有什么感兴趣说一下这段代码(不是因为我打算使用它,因为有更好的方法可以达到相同的结果,而是因为这是一个有趣的问题)。

【问题讨论】:

  • 我很惊讶这还没有答案,但是经过大量阅读后,我认为共识是指向数组末尾之后的指针是一个无法取消引用的有效指针.引用的 C 标准的常见段落是 6.5.6/8。在 C++ 中它是5.7/5。如果你有兴趣,这里是diff checker link
  • @remyabel 这似乎表明该代码不合法​​。出于指针算术的目的,C(不确定 C++)认为完整的对象等同于范围为 1 的数组的唯一元素(在这种情况下,int[42] 类型的数组是数组的唯一元素类型为int[1][42])。 6.5.8 明确禁止取消引用过去的指针(在评估的上下文中),例如由 &array + 1 形成的指针,正在被取消引用。
  • @StuartOlsen:但是数组到指针的转换(通常称为衰减)是评估的上下文吗?它不使用对象的值,只使用它的地址。
  • @BenVoigt 我相信评估的上下文规则是指正在评估的间接(即*ptr 出现在sizeof/alignof/_Alignof 表达式之外)。 “如果结果指向数组对象的最后一个元素,则它不应用作评估的一元 * 运算符的操作数” - 6.5.6.8, N1570
  • @StuartOlsen:毫无疑问,这些都是未经评估的上下文。但其他人可能是。例如,在 C++ 中,绑定引用不会评估它绑定到的对象。 (注意第 5.3.1 节第 1 段)

标签: c++ c c++11 undefined-behavior c11


【解决方案1】:

我在自己的代码中使用了它,如(&arr)[1]

我很确定它是安全的。数组到指针的衰减不是“左值到右值的转换”,尽管它以左值开始并以右值结束。

【讨论】:

  • 如果生成的左值(引用)不进行左值到右值的转换,这两个标准是否允许您通过不指向对象的指针(例如指针&arr + 1)间接进行?两种语言都引用了指针所指的“对象”,这似乎意味着在指针所描述的位置必须有一个适当类型的对象才能取消引用它。我能找到的最好的方法是,当地址运算符(例如,&*NULL)立即取消时,C 允许这种间接发生,这并不是这里发生的事情。
  • @Stuart:C++ 标准包含 (4p8) 对此效果的注释,并将 & 地址运算符的操作数作为示例...但不限于 @987654325 @ 运算符,它适用于不出现左值到右值转换的任何地方。此外,通过无效指针值访问未定义行为的规则是 4.1p2,并且仅适用于左值到右值的转换。
【解决方案2】:

这是未定义的行为。

a 的类型为 array of 42 int

&a 的类型为 pointer to array of 42 int。 (注意这不是数组到指针的转换)

&a + 1 也是pointer to array of 42 int 类型

5.7p5 状态:

当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,否则 [...] 则行为未定义

指针不指向数组对象的元素。它指向一个数组对象。所以“否则,行为未定义”是真的。行为未定义。

【讨论】:

  • 至少在 C++ 中,这是不正确的。有一条规则允许将每个对象视为大小为 1 的数组。
  • @BenVoigt:标准参考?
  • 5.7p4 "对于这些运算符,指向非数组对象的指针的行为与指向长度为 1 的数组的第一个元素的指针相同,该数组的元素类型为对象的类型。”
  • @BenVoigt:它是一个指向数组对象的指针,所以 5.7p4 不适用。虽然,我倾向于认为这是一个缺陷。我认为 5.7p4 应该读“......,一个不是指向数组元素的指针......”而不是“......,一个指向非数组对象的指针......”
  • 是的,显然意图是我在解释时在评论中给出的行为。或者也许指向数组对象的指针也被认为是指向其第一个元素的指针,这使得指针算术有效。
【解决方案3】:

这是 C 中未定义的行为,除非它本身是包含更多元素的更大对象的一部分,否则总是取消引用指向现有对象之外的指针。

但使用&array + 1 的基本思想是正确的,只要array 是左值。 (在某些情况下,数组不是左值。)在这种情况下,这是一个有效的指针操作。现在要获得指向第一个元素的指针,您只需将其转换回基本类型。在你的情况下,那将是

(int*)(&array + 1)

指向数组的指针的值保证与指向其第一个元素的指针的值相同,只是类型不同。

不幸的是,我看不到一种方法可以使这种表达式类型不可知,这样您就可以将其放入通用宏中,除非您强制转换为 void*。 (您可以使用 gcc typeof 扩展名,例如) 所以你最好坚持使用便携式(array)+ARRAYLEN(array),它应该适用于所有情况。

在一个奇怪的极端情况下,作为struct 一部分并从函数作为右值返回的数组不是左值。我认为标准在这里也允许指针算术,但我从来没有完全理解这种结构,所以我不确定它是否能在这种情况下工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-03
    • 1970-01-01
    • 2016-02-19
    • 2014-10-02
    • 2012-10-18
    相关资源
    最近更新 更多