C 无符号整数的差异-获得有符号结果的标准支持方式?

C 无符号整数的差异-获得有符号结果的标准支持方式?,c,language-lawyer,C,Language Lawyer,假设有两个任意时间戳: uint32_t timestamp1; uint32_t timestamp2; 除了转换为更大的有符号类型和更详细的if-else之外,还有一种标准的conform方法来获得这两种类型的有符号差异 事先不知道哪一个更大,但已知差值不大于最大20位,因此它将适合32位带符号 int32_t difference = (int32_t)( (int64_t)timestamp1 - (int64_t)timestamp2 ); 这种变体的缺点是硬件可能不支持使

假设有两个任意时间戳:

uint32_t timestamp1;    
uint32_t timestamp2;
除了转换为更大的有符号类型和更详细的if-else之外,还有一种标准的conform方法来获得这两种类型的有符号差异

事先不知道哪一个更大,但已知差值不大于最大20位,因此它将适合32位带符号

int32_t difference = (int32_t)( (int64_t)timestamp1 - (int64_t)timestamp2 );
这种变体的缺点是硬件可能不支持使用64位算法,当然,只有在存在更大类型的情况下才有可能使用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);

从标准的角度来看,这保证有效吗?

您可以使用基于

typedef union
{
    int32_t _signed;
    uint32_t _unsigned;
} u;
unsigned
算术中执行计算,将结果分配给
\u unsigned
成员,然后读取
联合
\u signed
成员作为结果:

u result {._unsigned = timestamp1 - timestamp2};
result._signed; // yields the result
这对于实现我们所依赖的固定宽度类型的任何平台都是可移植的(它们不需要)。2的补码保证用于有符号成员,并且在“机器”级别,2的补码有符号算术与无符号算术无法区分。这里没有转换或
memcpy
-类型开销:一个好的编译器将编译出本质上标准的语法


(注意这是C++中的未定义行为)

< p>将无符号整数值转换为有符号整数为<强>实现定义>。有关整数转换,请参见本手册第6.3.1.3节:

1当整数类型的值转换为除 _Bool,如果该值可以用新类型表示,则该值不变

2否则,如果新类型是无符号的,则通过重复地加上或减去一个以上的值来转换该值 可以在新类型中表示的最大值 直到值在新类型的范围内。(60)

3否则,新类型为有符号类型,且其中无法表示值;要么结果是实现定义的 或发出实施定义的信号。

在人们最可能使用的实现上,转换将按照您期望的方式进行,即,无符号值的表示将被重新解释为有符号值

具体做了以下几点:

  • 当值不能在对象中表示时,将整数转换为有符号整数类型的结果或所产生的信号 该类型(C90 6.2.1.2、C99和C11 6.3.1.3)
对于转换为宽度N的类型,该值被减少为模2^N 在该类型的范围内;没有发出任何信号

:

当长整数转换为短整数或短整数转换为字符时, 保留最低有效字节

例如,这一行

short x = (short)0x12345678L;
char y = (char)0x1234;
将值0x5678指定给x,并将此行

short x = (short)0x12345678L;
char y = (char)0x1234;
将值0x34指定给y

当有符号变量转换为无符号变量,反之亦然时 位模式保持不变。例如,将-2(0xFE)强制转换为 无符号值产生254(也是0xFE)


对于这些实现,你所提出的将是有效的。

< P> BaseSBA的答案是正确的,但是完整性这里还有两种方式(这也正好在C++中工作):

后者不是严格的别名冲突,因为该规则明确允许在整数类型的有符号和无符号版本之间进行双关


建议:

int32_t difference = (int32_t)(timestamp1 - timestamp2);

将在现有的任何实际机器上工作,并提供
int32\t
类型,但技术上不受标准的保证(结果由实现定义)

将Ian Abbott对Bathseba答案的宏观包装重新命名为答案:

#define UTOS32(a) ((union { uint32_t u; int32_t i; }){ .u = (a) }.i)

int32_t difference = UTOS32(timestamp1 - timestamp2);
总结关于为什么这比简单的类型转换更具可移植性的讨论:C标准(至少回到C99)指定了
int32\u t
的表示形式(它必须是两个的补充),但并非在所有情况下都应该如何从
uint32\u t
转换


最后,请注意,Ian的宏、Bathseba的答案和M.M的答案都在允许计数器环绕0的情况下工作,例如TCP序列号。

给定已知限制,
(int32_t)(timestamp1+1048576-timestamp2)-1048576
保证在不溢出的情况下计算差值,Apple LLVM 10.0.1和Clang 1001.0.46.4将其编译为x86_64的单个
subl
指令。相关:更一般的问题,允许时间戳环绕0:[@Bathsheba:我不认为严格遵守C有一个优雅的解决方案。通过union或
memcpy
重新解释可能是最好的办法。@BenVoigt:重要的不是指针的互易性,而是C 2018 6.5 7中的别名规则明确允许对应的有符号和无符号的别名类型。(就其本身而言,将指针转换为
unsigned int y;…int*x=(int*)&y;
是合法的,并不意味着
*x
是合法的。)
timestamp1
timestamp2
变量可以在代码中保留为
uint32\u t
。只有
结果
变量需要是一个联合。下面是一个与此相同的宏定义:
定义UTOS32(a)=(联合{uint32\u t u t u;int32\t i;}{.u=(a)}.i)
UTOS32(timestamp1-timestamp2)生成结果。因为固定宽度类型(如果存在)必须以这种方式运行。我不知道如何在回答中更清楚地说明这一点?此问题被标记为语言律师,并明确要求