C++数组越界访问如何计算指针的有效性?

3
以下代码是否保证可以正常工作?
int* arr = new int[2];
std::cout << &arr[0x100];

这被视为良好的做法吗?还是通过常规方式添加偏移会更清晰?编辑:通过“工作”,我指的是它应该打印出在0x100处的理论成员的指针。基本上,如果这等同于“std::cout << ((unsigned int)arr + 0x100*sizeof(int))”。

1
你如何定义“工作正常”? - juanchopanza
3
你所做的事情和arr + 0x100一样是未定义的。 - StoryTeller - Unslander Monica
1
嗯,想读取数组末尾的254个条目吗?很抱歉,读取超出数组末尾的内容永远不是一个干净或良好的做法。无论哪种方式都是未定义行为。 - Michael Dorgan
2
@StoryTeller 我认为它们并不等价。从逻辑上讲是相等的,但 &arr[0x100] 相当于 *(arr+0x100),这会导致地址解引用,因此是未定义的行为。而 arr + 0x100 仅是一个地址计算。 - Jens
1
@Jens - 无论我们怎么想,这个加法都是无效的。在§5.7.5中已经黑白分明地说明了这一点。 - StoryTeller - Unslander Monica
显示剩余5条评论
2个回答

1

使用我的编译器(Cygwin GCC),在这个值处获取地址与执行指针算术相同,尽管每个都是未定义行为(UB)。如Jens在下面的评论中所提到的,在http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html中,我发现了以下有用的信息。

还值得指出的是,Clang和GCC都确定了一些C标准未定义的行为。我将描述的内容在标准中都是未定义的,并且在这两个编译器的默认模式下都被视为未定义行为。

野指针解引用和越界数组访问:解引用随机指针(如NULL、指向已释放内存等)以及访问数组越界的特殊情况是C应用程序中常见的错误,希望不需要解释。为了消除这种未定义行为的来源,每个数组访问都必须进行范围检查,并且ABI必须更改,以确保范围信息跟随任何可能受到指针算术影响的指针。这将对许多数值和其他应用程序产生极高的成本,并破坏与所有现有C库的二进制兼容性。

指针算术也是未定义行为。因此,您有一个地址,但不能对其进行解引用。因此,实际上没有必要拥有这个地址。获取地址本身就是未定义的行为,不应在代码中使用。
请参见此答案以了解越界指针:为什么越界指针算术是未定义行为? 我的示例代码:
    int* arr = new int[2];
    std::cout << arr << std::endl;
    std::cout << &(arr[0])<< std::endl;
    std::cout << &(arr[1])<< std::endl;
    std::cout << &arr[0x100] << std::endl; // UB, cannot be dereferenced
    std::cout << &arr[256] << std::endl;   // cannot be dereferenced, so no use in having it
    std::cout << arr + 0x100; // UB here too, no use in having this address 

样本输出:

0x60003ae50
0x60003ae50
0x60003ae54
0x60003b250
0x60003b250
0x60003b250

我建议阅读http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html... - Jens
1
"C++允许获取这些地址" - 不,它不允许,标准规定这是未定义行为。 - UnholySheep
@UnholySheep 真的,已经移除。 - 9Breaker
1
现在这个答案仍然让人觉得代码没有调用 UB - 实际上它确实调用了,就像你的示例代码一样。指针算术也是 UB。 - UnholySheep
@UnholySheep,感谢您的帮助,我看到了我的错误并作出了相应的修改。现在你同意了吗? - 9Breaker
显示剩余2条评论

0
在第一行中,您分配了2个整数值。在第二行中,您访问了超出此范围的内存。这是绝对不允许的。
编辑:这里有一些有趣的评论。但我无法理解为什么需要引用标准来回答如此简单的问题,以及为什么在这里要讨论指针算术?
从逻辑上看,std::cout << &arr[0x100] 包括3个步骤: 1. 访问数组的不存在成员 2. 获取不存在成员的地址 3. 使用不存在成员的地址
如果第一步无效,那么所有后续步骤都是未定义的吗?

引用标准或更详细地说明为什么这是未定义行为。作为它现在的评论,这个答案只是一个注释。 - Michael Dorgan
1
@MichaelDorgan 我认为声明“访问您不拥有的内存是UB”不需要文档。但是,我认为他需要解释隐含的假设,即实际上“访问”了内存(因为OP似乎不理解下标运算符正在执行什么操作)。 - scohe001
“嗯,我实际上从来没有访问过数据”,这让人听起来好像他没有意识到它实际上是在取消引用。但我不认为在拥有的内存之外进行指针算术运算应该是UB...如果你能找到那篇文章证明我错了,我会很高兴的 :) - scohe001
@iBent:我没有看到你第一个代码片段和函数“removeFirst”之间的联系。如果m_size是数组的大小,这里是2,而T是int类型,那么该函数可以工作。您只需将数组内容向较低索引移动一个整数距离即可。但是,在C++中为什么需要访问数组外的int呢? - Ernie Mur
@Ernie Mur 是的,我承认我的例子有点糟糕。我实际上是想说 "if (m_size) m_size--;"。考虑到在调用函数时 m_size 为 0。(当然,在这里使用 "if (m_size == 0) return;" 更好)。 - iBent
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接