Safe low 32 bits masking of uint64_t

3

Assume the following code:

uint64_t g_global_var;

....
....

void foo(void)
{
    uint64_t local_32bit_low = g_global_var & 0xFFFFFFFF;
    ....
}

With the current toolchain, this code works as expected, local_32bit_low indeed contains the low 32 bits of g_global_var.

I wonder if it is guaranteed by the standard C that this code will always work as expected? My concern is that the compiler may treat 0xFFFFFFFF as integer value of -1 and when promoting to uint64_t it would become 0xFFFFFFFFFFFFFFFF.

P.S.

I know that to be on the safe side it is better to use 0xFFFFFFFFULL in this case. The point is that I saw it in a legacy code and I wonder if it worth to be fixed or not.

c
bit-manipulation
64-bit
implicit-conversion
bitmask
asked on Stack Overflow Aug 21, 2019 by Alex Lop. • edited Aug 21, 2019 by Vlad from Moscow

4 Answers

8

There is no problem. The integer constant 0xFFFFFFFF has the type that is able to store the value as is.

According to the C Standard (6.4.4.1 Integer constants)

5 The type of an integer constant is the first of the corresponding list in which its value can be represented

So this value is stored as a positive value.

If the type unsigned int is a 32-bit integer type then the constant will have the type unsigned int.

Otherwise it will have one of the types that can store the value.

long int
unsigned long int
long long int
unsigned long long int 

Due to the usual arithmetic conversions in the expression

g_global_var & 0xFFFFFFFF;

it is promoted like

0x00000000FFFFFFFF

Pay attention to that in C there is no negative integer constants. For example an expression like

-10

consists of two sub-expressions: the primary expression 10 and the sub-expression with the unary operator - -19 that coincides with the full expression.

answered on Stack Overflow Aug 21, 2019 by Vlad from Moscow • edited Aug 21, 2019 by Vlad from Moscow
3

0xffffffff is not -1, ever. It may convert to -1 if you cast or coerce (e.g. by assignment) it to a signed 32-bit type, but integer literals in C always have their mathematical value unless they overflow.

For decimal literals, the type is the narrowest signed type that can represent the value. For hex literals, unsigned types are used before going up to the next wider signed type. So, in the common case where int is 32-bit, 0xffffffff would have type unsigned int. If you wrote it as decimal, it would have type long (if long is 64-bit) or long long (if long is only 32-bit).

1

The type of an unsuffixed hexadecimal or octal constant is the first of the following list in which its value can be represented:

int
unsigned int
long int
unsigned long int
long long int
unsigned long long int

(For unsuffixed decimal constants, remove the unsigned types from the above list.)

The hexadecimal constant 0xFFFFFFFF can definitely be represented by unsigned long int, so its type will be the first of int, unsigned int, long int or unsigned long int that can represent its value.

Note that although 0xFFFFFFFF > 0 always evaluates to 1 (true), it is possible for 0xFFFFFFFF > -1 to evaluate to either 0 (false) or 1 (true) on different implementations. So you need to be careful when comparing integer constants with each other or with other objects of integer type.

answered on Stack Overflow Aug 21, 2019 by Ian Abbott • edited Aug 21, 2019 by Ian Abbott
0

Others have answered the question, just a recomendation, next time (if you are under C11) you can check the type of the expression by yourself using _Generic

#include <stdio.h>
#include <stdint.h>

#define print_type(x) _Generic((x), \
    int64_t:  puts("int64_t"),      \
    uint64_t: puts("uint64_t"),     \
    default:  puts("unknown")       \
)

uint64_t g_global_var;

int main(void)
{
    print_type(g_global_var & 0xFFFFFFFF);
    return 0;
}

The ouput is

uint64_t
answered on Stack Overflow Aug 21, 2019 by David Ranieri

User contributions licensed under CC BY-SA 3.0