假设有两个任意时间戳记:
uint32_t timestamp1;
uint32_t timestamp2;
除了转换为更大的带符号类型的明显变体和相当冗长的if-else之外,是否有一种标准的一致方式来获得两者的带符号区别。
事先不知道哪个更大,但是知道相差不超过最大20位,因此它将适合32位带符号。
int32_t difference = (int32_t)( (int64_t)timestamp1 - (int64_t)timestamp2 );
此变体的缺点是硬件可能不支持使用64位算术,当然只有存在较大的类型(时间戳已经是64位的情况下)时才有可能使用。
其他版本
int32_t difference;
if (timestamp1 > timestamp2) {
difference = (int32_t)(timestamp1 - timestamp2);
} else {
difference = - ((int32_t)(timestamp2 - timestamp1));
}
非常冗长,涉及条件跳转。
那就是
int32_t difference = (int32_t)(timestamp1 - timestamp2);
从标准角度来看,这是否可以保证正常工作?
答案 0 :(得分:7)
您可以根据以下情况使用union
类型的双关语
typedef union
{
int32_t _signed;
uint32_t _unsigned;
} u;
以unsigned
算术执行计算,将结果分配给_unsigned
成员,然后读取_signed
的{{1}}成员作为结果:
union
这可移植到实现我们所依赖的固定宽度类型的任何平台上(它们不需要)。对于有符号成员,保证2的补码,在“机器”级别,2的补码有符号算术与无符号算术没有区别。这里没有转换或u result {._unsigned = timestamp1 - timestamp2};
result._signed; // yields the result
类型的开销:一个好的编译器将编译出本质上是标准的语法糖。
(请注意,这是C ++中的未定义行为。)
答案 1 :(得分:1)
Bathsheba的回答是正确的,但是为了完整起见,这里还有另外两种方法(也可以在C ++中使用):
uint32_t u_diff = timestamp1 - timestamp2;
int32_t difference;
memcpy(&difference, &u_diff, sizeof difference);
和
uint32_t u_diff = timestamp1 - timestamp2;
int32_t difference = *(int32_t *)&u_diff;
后者不是严格的别名冲突,因为该规则明确允许在整数类型的有符号和无符号版本之间进行修剪。
建议:
int32_t difference = (int32_t)(timestamp1 - timestamp2);
可以在任何存在并提供int32_t
类型的实际机器上运行,但是从技术上讲,该标准不能保证(结果是实现定义的)。
答案 2 :(得分:0)
将无符号整数值转换为有符号整数是实现定义。 C standard的6.3.1.3节中对整数转换做了详细说明:
1 将整数类型的值转换为除 _Bool,如果该值可以用新类型表示,则它保持不变。
2 否则,如果新类型是无符号的,则通过重复加或减一个多于 新类型可以表示的最大值 直到该值在新类型的范围内。 60)
3 否则,将对新类型进行签名,并且无法在其中表示值;结果是实现定义的 或引发实现定义的信号。
在人们最有可能使用的实现中,转换将按照您期望的方式进行,即无符号值的表示形式将重新解释为有符号值。
具体地说,GCC执行以下操作:
- 当值不能在对象中表示时,将整数转换为有符号整数类型的结果或发出的信号 这种类型(C90 6.2.1.2,C99和C11 6.3.1.3)。
要转换为宽度为N的类型,该值将以2 ^ N为模降低 在类型范围内;没有信号。
MSVC:
如果将长整型强制转换为短整数,或者将短整型强制转换为char, 保留最低有效字节。
例如,这一行
short x = (short)0x12345678L;
将值0x5678分配给x,并在此行
char y = (char)0x1234;
将值0x34分配给y。
将有符号变量转换为无符号变量,反之亦然时, 位模式保持不变。例如,将-2(0xFE)强制转换为 无符号值产生254(也为0xFE)。
因此对于这些实现,您提出的建议将起作用。
答案 3 :(得分:0)
将伊恩·阿博特(Ian Abbott)的宏大包装重新诠释Bathseba的答案作为答案:
uint32_t
总结有关为什么它比简单的类型转换更可移植的讨论:C标准(至少回到C99)指定了{{1}}的表示(必须为二进制补码) ),但并非在所有情况下都应从{{1}}强制转换。
最后,请注意,Ian的宏,Bathseba的答案和MM的答案也都在the more general case中起作用,其中允许计数器回绕0,例如,使用TCP序列号。