Is detecting unsigned wraparound via cast to signe

2020-07-03 07:34发布

问题:

I'm using a uint16_t as a sequence counter in a network protocol. This counter commonly wraps around as expected. When a receiver gets a packet it checks this counter against the most recently received to see whether it's a new packet or an out of order packet.

Wraparound needs to be taken into account when comparing sequence numbers. So if for example the last sequence number was 0x4000, then a sequence number from 0x4001 to 0xBFFF is newer, and a sequence number from 0xC000 to 0xFFFF and from 0x0000 to 0x3FFF is smaller.

The way I'm currently doing this is as follows:

uint16_t last;
uint16_t current;
...
// read in values for last and current
...
if ((int16_t)(current - last) > 0) {
    printf("current is newer\n");
} else {
    printf("current is older (or same)\n");
}

By subtracting the two and treating the result as a int16_t, I can easily see which is greater and by how much. So for example if the current sequence number is at least 5 less that the last, i.e ((int16_t)(current - last) < -5), I can assume this is not due to normal packet reordering and drop the packet.

I realize that signed wraparound is undefined, however in this case I'm treating an unsigned value as signed for the sake of doing comparisons. Does this invoke undefined behavior, and if so what would be a better way to do this type of comparison?

回答1:

The behaviour of out-of-range conversion is implementation-defined.

Why don't you just avoid this issue entirely and write:

if ( current != last && current - last < 0x8000 )
    printf("current is newer\n");
else
    printf("current is older (or same)\n");

Note: This answer only applies to the specific question involving uint16_t. For other types, different code would be required.



回答2:

You may cast uint16_ts to int32_ts and do the subtracting.

if (((int32_t) current - (int32_t) last) > 0) {
    printf("current is newer\n");
} else {
    printf("current is older (or same)\n");
}

Note: when casting to int16_t, uint16_ts larger than 32767 will show up as negative (in signed integer most significant bit set to 1 means negative one, in unsigned types it's just a regular bit).



回答3:

The subtraction will take place using a promoted type unless int is 16 bit in which case the behaviour is implementation defined.

To be on the safe side, you could compute the absolute difference using a ternary:

current > last ? current - last : last - current