Ser*_*gey 50 c++ pointers pointer-arithmetic language-lawyer
请考虑以下代码:
int* p1 = new int[100];
int* p2 = new int[100];
const ptrdiff_t ptrDiff = p1 - p2;
int* p1_42 = &(p1[42]);
int* p2_42 = p1_42 + ptrDiff;
Run Code Online (Sandbox Code Playgroud)
现在,标准保证p2_42指向p2[42]哪个?如果没有,在Windows,Linux或webassembly堆上总是如此吗?
Max*_*hof 54
要添加标准报价:
当两个指针表达式
P和Q相减,其结果的类型是一个实现定义符号整型; 此类型应std::ptrdiff_t与<cstddef>标题([support.types])中定义的类型相同.
(5.1)如果
P和Q两者都计算为空指针值,则结果为0.(5.2)否则,如果
P和Q分别指向元素x[i]和x[j]同一个数组对象x,则表达式P - Q具有值i?j.(5.3)否则,行为未定义.[ 注意:如果值
i?j不在类型的可表示值范围内std::ptrdiff_t,则行为未定义. - 结束说明]
(5.1)不适用,因为指针不是nullptrs.(5.2)不适用,因为指针不在同一个数组中.所以,我们留下了(5.3) - UB.
Mat*_*lia 28
const ptrdiff_t ptrDiff = p1 - p2;
Run Code Online (Sandbox Code Playgroud)
这是未定义的行为.两个指针之间的减法只有在它们指向同一数组中的元素时才能很好地定义.([expr.add]5.3).
当两个指针表达式
P和Q相减,其结果的类型是一个实现定义符号整型; 此类型应std::ptrdiff_t与<cstddef>标题([support.types])中定义的类型相同.
- 如果
P且Q两者都计算为空指针值,则结果为0.- 否则,如果P和Q分别指向元素
x[i]和x[j]同一个数组对象x,则表达式P - Q具有该值i?j.- 否则,行为未定义
即使有一些假设的方法以合法的方式获得这个值,即使这个求和也是非法的,因为即使指针+整数求和也被限制在数组的边界内([expr.add]4.2)
当
J具有整数类型的表达式添加到P指针类型的表达式或从指针类型的表达式中减去时,结果的类型为P.
- 如果
P求值为空指针值并J求值为0,则结果为空指针值.- 否则,如果
P指向元件x[i]阵列对象的x与n个元素,81个的表达P + J和J + P(其中,J具有值j)点到(可能-假设的)元件x[i+j],如果0?i+j?n和表达P - J指向(可能-假设的)元件x[i?j],如果0?i?j?n.- 否则,行为未定义.
第三行是未定义行为,因此标准允许之后的任何内容.
减去指向同一数组(或之后)的两个指针是合法的.
Windows或Linux并不真正相关; 编译器,特别是它们的优化器是打破你的程序的原因.例如,优化器可能会识别出这一点,p1并且p2两者都指向int[100]so 的开头,因此p1-p2必须为0.
该标准允许在平台上实现,其中存储器被划分为使用指针算法不能彼此到达的离散区域.举一个简单的例子,一些平台使用24位地址,由一个8位的bank号和一个16位的地址组成.添加一个标识银行的最后一个字节将产生一个指针的第一个字节的地址相同的银行,而不是第一个字节的下一个银行.这种方法允许使用16位数学而不是24位数学计算地址算术和偏移,但要求没有对象跨越存储体边界.这样的设计会增加一些额外的复杂性malloc,并且可能导致比其他情况更多的存储器碎片,但是用户代码通常不需要关心将存储器划分成存储体.
许多平台没有这样的架构限制,并且一些专为此类平台上的低级编程而设计的编译器将允许在任意指针之间执行地址算法.该标准指出,处理未定义行为的一种常见方法是"在文档中以环境特征的方式执行转换或程序执行",并且在支持它的环境中对广义指针算法的支持将很好地适合该类别.遗憾的是,该标准未能提供任何区分以这种有用方式表现的实现方式和不提供方式的方法.