下面的模板函数是序列生成器的一部分。我提出了下面的基于联合的解决方案来代替手动移位,以使操作更加明确。它在所有测试的编译器上都工作得很好。Godbolt link。
然而,尽管在实践中工作,我担心有别名规则被违反,这意味着它可能在未来或在其他编译器以外的GCC和CLANG。
严格按照C++标准:下面的代码格式正确吗?2是否会导致未定义的行为?
template <int BITS>
uint64_t flog2(uint64_t num) {
constexpr uint64_t MAXNUM = (uint64_t(1) << BITS);
if (num < MAXNUM) return num;
union FP {
double dbl;
struct {
uint64_t man: 52;
uint32_t exp: 11;
uint32_t sign: 1;
};
struct {
uint64_t xman: 52-BITS;
uint32_t xexp: 11+BITS;
uint32_t xsgn: 1;
};
};
FP fp;
fp.dbl = num;
fp.exp -= 1023-1+BITS;
return fp.xexp;
}
谢谢!
2条答案
按热度按时间ccrfmcuu1#
首先,在ISO标准C中,该程序在语法上是病态的。匿名
struct
成员不是标准C(与C相反)。它们是一个扩展。在ISO标准C++中,struct
必须通过该名称命名和访问。我将在回答的其余部分忽略它,并假设您是通过这样的名称访问的。
从技术上讲,这不是别名冲突,而是在中阅读联合对象的非活动成员的未定义行为
类型并不重要(与别名相反)。联合体中最多只有一个活动成员,这是最后一个显式创建或用成员访问/赋值表达式编写的成员。在您的情况下,
fp.dbl = num;
意味着dbl
是活动成员,也是唯一可以从中读取的成员。标准中有一个例外是访问联合体的标准布局类类型成员的公共初始序列,在这种情况下,可以像访问活动成员一样访问非活动成员。但是,即使两个
struct {
成员也只有BITS == 0
具有非空公共初始序列。然而,在实践中,编译器通常显式支持这种类型的双关语,可能已经在允许的C兼容性中支持了。
当然,即使抛开所有这些不谈,位域的布局和所涉及类型的表示也完全是由实现定义的,您不能期望它具有通用的可移植性。
osh3o9ms2#
从不是最近写入的union成员读取是未定义的行为。
此外,bit-fields的布局是由实现定义的。
因此,从严格的C++标准的Angular 来看,这段代码既调用了未定义的行为(通过在写入
dbl
之后阅读exp
),又依赖于实现定义的行为,假设位字段布局对应于double
浮点表示(顺便说一下,这也是实现定义的)。