Why no type conversion warning?

Viewed 278

I have the following snippet of C code, with -Wconversion enabled in Clang:

uint8_t num_ch = get_num_channels();
uint64_t test_mask = 1 << num_ch;

I was hoping this would throw a warning of some sort, since 1 is an implicit "signed int" and then getting assigned to a uint64_t, but the compiler did not complain :(

What am I missing?

1 Answers

Why no type conversion warning?

Short answer: Because of a defect (some might even call it a bug) in the version of clang/LLVM that you are using.

Discussion/Explanation/Reasoning

The issue stems from the evaluation of a bit-shift expression, s << u, where s is a signed integer type and u is an unsigned type. Now, according to this C11 Draft Standard (bolding mine):

6.5.7 Bitwise shift operators
...
3 The integer promotions are performed on each of the operands. The type of the result is that of the promoted left operand.

Which means that the result of the expression is a signed integer. Henceforth, I should point out, I am using (and assuming) a platform that uses a 32-bit 'basic' int type. Note also that an integer constant without the u suffix is a signed type.

Note: Although I don't have access to the OP's embedded system, nor to a local GCC compiler, I can nevertheless reproduce the issue using the clang-cl compiler provided in the latest version of Visual Studio 2019 (16.7.2 - I'll add the clang version when I've found out what it is).

The following code does generate the expected warning (in fact, two warnings, as shown):

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

int main()
{
    uint64_t test_mask = 1 << (uint8_t)(31); // Warnings here, as expected.
    printf("%016llX\n", test_mask);
    return 0;
}

Warnings:

warning : signed shift result (0x80000000) sets the sign bit of the shift expression's type ('int') and becomes negative [-Wshift-sign-overflow]
warning : implicit conversion changes signedness: 'int' to 'uint64_t' (aka 'unsigned long long') [-Wsign-conversion]

Furthermore, the output of the printf call is FFFFFFFF80000000, which shows why the warning is important: the bit-shift (on a 32-bit int) has produced a negative value, which is then sign-extended when promoted to 64-bits - thus 'corrupting' the intended/expected result (which will be 0000000080000000 if the literal 1 is simply changed to 1u, thus avoiding the sign-extension).

However! If a very minor (some might even say insignificant) change is made to the code, changing the literal (constant) right-hand operand of the bit-shift to an exactly equivalent variable, as shown in the code below, then the warnings from clang-cl vanish (but the output remains 'corrupted' - so the operations performed are the same).

int main()
{
    uint8_t num_ch = 31;
    uint64_t test_mask = 1 << num_ch; // No warning here!
    printf("%016llX\n", test_mask);
    return 0;
}

Possibly not the most helpful answer - but what more can be done, other than reporting the issue to the folks at LLVM? I would definitely file such a report, because the code as posted by the OP could cause some serious issues that should be detected and indicated by a compiler with full warnings enabled (MSVC warns even for the second code snippet).

Related