union UB 的非活动成员上的指针算术?
Is pointer arithmetic on inactive member of a union UB?
让我们考虑这个示例代码:
struct sso
{
union {
struct {
char* ptr;
char size_r[8];
} large_str;
char short_str[16];
};
const char* get_tag_ptr() const {
return short_str+15;
}
};
在 [basic.expr] 中指定只要结果指向数组的另一个元素(或超过对象或最后一个元素的末尾),就允许指针算术。然而,如果数组是一个联合的非活动成员,这个部分没有指定会发生什么。我相信这不是问题 short_str+15
永远不会是 UB。对吗?
清楚表明我的意图
写入 return short_str+15;
,您获取了生命周期可能尚未开始的对象的地址,但这不会导致未定义的行为,除非您取消引用它。
[basic.life]/1.2
if the object is a union member or subobject thereof,
its lifetime only begins if that union member is the initialized
member in the union, or as described in [class.union]
.
和
[class.union]/1
In a union, a non-static data member is active if its
name refers to an object whose lifetime has begun and has not ended
([basic.life]
). At most one of the non-static data members of an object of
union type can be active at any time, that is, the value of at most
one of the non-static data members can be stored in a union at any
time.
但是
[basic.life]/6
Before the lifetime of an object has started but after the storage which the
object will occupy has been allocated or, after the lifetime of an
object has ended and before the storage which the object occupied is
reused or released, any pointer that represents the address of the
storage location where the object will be or was located may be used
but only in limited ways. For an object under construction or
destruction, see [class.cdtor]
. Otherwise, such a pointer refers to allocated
storage ([basic.stc.dynamic.allocation]
), and using the pointer as if the pointer were of
type void* , is well-defined. Indirection through such a pointer is
permitted but the resulting lvalue may only be used in limited ways,
as described below.
- [list unrelated to unions]
联合成员的指针运算是否会导致别名取决于指针最终将如何使用。在补充标准的实现中,保证 "type-access" 规则将仅在 存在实际别名的情况下应用 ,或(对于 C++)在涉及非类型的情况下琐碎的语义,指针操作的有效性与它们是在活动成员还是非活动成员上执行几乎没有关系。
考虑,例如:
#include <stdint.h>
uint32_t readU(uint32_t *p) { return *p; }
void writeD(double *p, double v) { *p = v; }
union udBlob { double dd[2]; uint32_t ww[4]; } udb;
uint32_t noAliasing(int i, int j)
{
if (readU(udb.ww+i))
writeD(udb.dd+j, 1.0);
return readU(udb.ww+i);
}
uint32_t aliasesUnlessDisjoint(int i, int j)
{
uint32_t *up = udb.ww+i;
double *dp = udb.dd+j;
if (readU(up))
writeD(dp, 1.0);
return readU(up);
}
在 readU 执行期间,通过 *p
访问的任何存储都不会通过任何其他方式访问,因此在该函数的执行期间没有别名。在执行 writeD
期间也是如此。在 noAliasing
的执行期间,所有会影响与 udb
关联的任何存储的操作都是使用从 udb
派生的指针执行的,并且显然具有明显不重叠的活动生命周期,因此那里没有别名。
在 aliasesUnlessDisjoint
的执行期间,所有访问都是使用从 udb
派生的指针执行的,但是在 [=20] 的创建和使用之间通过 up
访问存储=],并且在 up
的创建和使用之间通过 dp
访问存储。因此,*dp
和 *up
将在执行 aliasesUnlessDisjoint
期间别名,除非 udb.ww[i]
和 udb.dd[j]
占用不相交的存储空间。
请注意,即使在没有实际别名的情况下(如上面的无别名函数),gcc 和 clang 都应用类型访问规则。尽管标准明确指出 someArray[y]
形式的左值表达式等同于 *(someArray+(y))
,但如果 []
使用语法。例如:
uint32_t noAliasing2(int i, int j)
{
if (udb.ww[i])
udb.ww[j] = 1.0;
return udb.ww[i];
}
uint32_t noAliasing3(int i, int j)
{
if (*(udb.ww+i))
*(udb.dd+j) = 1.0;
return *(udb.ww+i);
}
虽然 noAliasing2
的 gcc 或 clang 生成的代码会在 udb.dd[j]
的操作后重新加载 udb.ww[i]
,但 noAliasing3
的代码不会。这在标准下在技术上是允许的(因为规则,如所写,不允许在 任何 情况下访问 udb.ww[i]
!),但这绝不意味着任何作者认为 gcc 和 clang 的行为在高质量实现中是合适的。纯粹看标准,我看不出任何特定的 noAliasing
形式应该比任何其他形式更有效或更不有效,但是考虑在 -fstrict-aliasing
模式下使用 gcc 或 clang 的程序员应该认识到gcc 和 clang 对待它们的方式不同。
让我们考虑这个示例代码:
struct sso
{
union {
struct {
char* ptr;
char size_r[8];
} large_str;
char short_str[16];
};
const char* get_tag_ptr() const {
return short_str+15;
}
};
在 [basic.expr] 中指定只要结果指向数组的另一个元素(或超过对象或最后一个元素的末尾),就允许指针算术。然而,如果数组是一个联合的非活动成员,这个部分没有指定会发生什么。我相信这不是问题 short_str+15
永远不会是 UB。对吗?
写入 return short_str+15;
,您获取了生命周期可能尚未开始的对象的地址,但这不会导致未定义的行为,除非您取消引用它。
[basic.life]/1.2
if the object is a union member or subobject thereof, its lifetime only begins if that union member is the initialized member in the union, or as described in
[class.union]
.
和
[class.union]/1
In a union, a non-static data member is active if its name refers to an object whose lifetime has begun and has not ended (
[basic.life]
). At most one of the non-static data members of an object of union type can be active at any time, that is, the value of at most one of the non-static data members can be stored in a union at any time.
但是
[basic.life]/6
Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. For an object under construction or destruction, see
[class.cdtor]
. Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.allocation]
), and using the pointer as if the pointer were of type void* , is well-defined. Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below.
- [list unrelated to unions]
联合成员的指针运算是否会导致别名取决于指针最终将如何使用。在补充标准的实现中,保证 "type-access" 规则将仅在 存在实际别名的情况下应用 ,或(对于 C++)在涉及非类型的情况下琐碎的语义,指针操作的有效性与它们是在活动成员还是非活动成员上执行几乎没有关系。
考虑,例如:
#include <stdint.h>
uint32_t readU(uint32_t *p) { return *p; }
void writeD(double *p, double v) { *p = v; }
union udBlob { double dd[2]; uint32_t ww[4]; } udb;
uint32_t noAliasing(int i, int j)
{
if (readU(udb.ww+i))
writeD(udb.dd+j, 1.0);
return readU(udb.ww+i);
}
uint32_t aliasesUnlessDisjoint(int i, int j)
{
uint32_t *up = udb.ww+i;
double *dp = udb.dd+j;
if (readU(up))
writeD(dp, 1.0);
return readU(up);
}
在 readU 执行期间,通过 *p
访问的任何存储都不会通过任何其他方式访问,因此在该函数的执行期间没有别名。在执行 writeD
期间也是如此。在 noAliasing
的执行期间,所有会影响与 udb
关联的任何存储的操作都是使用从 udb
派生的指针执行的,并且显然具有明显不重叠的活动生命周期,因此那里没有别名。
在 aliasesUnlessDisjoint
的执行期间,所有访问都是使用从 udb
派生的指针执行的,但是在 [=20] 的创建和使用之间通过 up
访问存储=],并且在 up
的创建和使用之间通过 dp
访问存储。因此,*dp
和 *up
将在执行 aliasesUnlessDisjoint
期间别名,除非 udb.ww[i]
和 udb.dd[j]
占用不相交的存储空间。
请注意,即使在没有实际别名的情况下(如上面的无别名函数),gcc 和 clang 都应用类型访问规则。尽管标准明确指出 someArray[y]
形式的左值表达式等同于 *(someArray+(y))
,但如果 []
使用语法。例如:
uint32_t noAliasing2(int i, int j)
{
if (udb.ww[i])
udb.ww[j] = 1.0;
return udb.ww[i];
}
uint32_t noAliasing3(int i, int j)
{
if (*(udb.ww+i))
*(udb.dd+j) = 1.0;
return *(udb.ww+i);
}
虽然 noAliasing2
的 gcc 或 clang 生成的代码会在 udb.dd[j]
的操作后重新加载 udb.ww[i]
,但 noAliasing3
的代码不会。这在标准下在技术上是允许的(因为规则,如所写,不允许在 任何 情况下访问 udb.ww[i]
!),但这绝不意味着任何作者认为 gcc 和 clang 的行为在高质量实现中是合适的。纯粹看标准,我看不出任何特定的 noAliasing
形式应该比任何其他形式更有效或更不有效,但是考虑在 -fstrict-aliasing
模式下使用 gcc 或 clang 的程序员应该认识到gcc 和 clang 对待它们的方式不同。