-3

Here is a code in C:

#include <stdio.h>

int main()
{
    printf("Int width = %lu\n", sizeof(unsigned int)); // Gives 32 bits on my computer

    unsigned int n = 32;
    unsigned int y = ((unsigned int)1 << n) - 1;  // This is line 8
    printf("%u\n", y);
    
    unsigned int x = ((unsigned int)1 << 32) - 1; // This is line 11
    printf("%u", x);
    
    return 0;
}

It outputs:

main.c:11:39: warning: left shift count >= width of type [-Wshift-count-overflow]
Int width = 4
0
4 294 967 295 (= 2^32-1)

The warning for the line 11 is expected as explained in these links: wiki.sei.cmu.edu and https://stackoverflow.com/a/11270515

left-shift operations [...] if the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

There is no warning for the line 8, but I was expected the same warning as for line 11. Futhermore, the results are entirely different ! What do I miss ?

This behaviour is similar for C++:

#include <iostream>
using namespace std;
int main()
{
    cout << "Int width = " << sizeof(uint64_t) << "\n"; // Gives 64 bits on my computer

    int n = 64;
    uint64_t y = ((uint64_t)1 << n) - 1; // This is line 8
    cout << "y = " << y;
    
    uint64_t x = ((uint64_t)1 << 64) - 1; // This is line 11
    cout << "\nx = " << x;

    return 0;
}

Which outputs:

main.cpp:11:34: warning: left shift count >= width of type [-Wshift-count-overflow]                                                            
Int width = 8                                                                                                                                  
y = 0                                                                                                                                          
x = 18 446 744 073 709 551 615 (= 2^64-1)

I used the onlineGBD for C compiler for the C code and onlineGBD for C++ compiler. Here are the link to the code: C code and C++ code.

dalex78
  • 228
  • 1
  • 8

4 Answers4

3

For line 8, the compiler has to prove that in ((unsigned int)1 << n), n is 32 or more. That can be difficult since n is not const so it's value could be changed. The compiler would have to do more static analysis to give you the warning.

On the other hand, with (unsigned int)1 << 32) the compiler knows that the value is 32 or more and can easily warn. This requies almost no time to detect, since the type and the value to shift by are both compile time "literals".

If you switch to using const int n = 64; in your C++ code, then you will get an error at OnlineGBD. You can see that here. I tried that with the C version but it still doesn't warn.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • In C `const` is no quite the same as in C++, if you use a *real* constant ***i.e*** `#define n 32` you get the warning. – anastaciu Mar 15 '21 at 21:42
  • 1
    @anastaciu I figured that was the case. I just though the compiler would be more willing to issue a warning since it's value shouldn't change if it's at least marked `const`. – NathanOliver Mar 15 '21 at 21:43
2

There is no warning for the line 8, but I was expected the same warning as for line 11.

The C standard does not require a compiler to diagnose an excessive shift amount. (Generally, it does not require C implementations to diagnose errors other than those explicitly listed in “Constraints” clauses.)

The compiler you using diagnoses the error with the integer constant expression (32), as this is easy. It does not diagnose the error with the variable n, as that involves more work and the compiler authors have not implemented it.

Futhermore, the results are entirely different !

With the integer constant expression, the compiler evaluates the shift during compilation, using whatever software is built into it. That apparently produces zero for (unsigned int) 1 << 32. With the variable, the compiler generates an instruction to perform the shift during program execution. That instruction likely uses only the low five bits of the right operand, so an operand of 32 (1000002) yields of shift of zero bits, so shifting (unsigned int) 1 produces one.

Both behaviors are allowed by the C standard.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
1

It's likely because n is a variable, the compiler doesn't seem to be verifying it, as it doesn't know its value it doesn't issue a warning, if you turn it into a constant i.e const int n = 64;, the warning is issued.

https://godbolt.org/z/4s5jz6

As for the results, undefined behavior is what it is, for the sake of curiosity you can analyze a particular case and try to figure out what the compiler did, but the results can't be reasoned with because there is no correct result.

Even the warnings are optional, gcc is nice enough to to warn you when a constant or constant literal is used but it didn't have to.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
-2

Undefined behaviour (UB) means undefined behaviour. Literally anything can happen. Compilers are not required to tell you of UB, but are permitted to.

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

So max unsigned -1, or zero, or format your SSD while installing viruses on your cloud stored files and emailing your browser history to your contact list are all permitted things for your compiler to convert a shift of 32 bits of a 32 bit integer.

As a Quality of Implementation issue, the last is rare.

The compiler is optimizing (1<<x)-1 as x 1 bits, not even doing a shift operation, in one case. Within the bounds of defined shift operations, this is equivalent, so this is a valid optimization. So when you pass 32, it writes 0xffffffff.

In the other case, it is maybe setting the nth bit, reading the low 5 bits of the shift operation to see which to set. Also valid within the range of defined behaviour, utterly different.

Welcome to UB.

I would expect further changes based on optimization level.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • "Undefined behaviour (UB) means undefined behaviour. " - That's infinitely misleading! – Hi - I love SO Mar 15 '21 at 22:45
  • 1
    “Undefined behavior” only means the C standard does not impose any requirements. It does not assert that anything can happen. It does not, and cannot, lift requirements imposed by law, physics, hardware specifications, materials constraints, mathematics, or logic. – Eric Postpischil Mar 16 '21 at 14:54
  • @EricPostpischil Are you claiming undefined behavior cannot time travel? Because I can give you an example. If a program's behavior is undefined by the C++ standard, the behavior of the program *before* it encounters the triggering code is *also* undefined by the C++ standard. So the effects of UB code can time-travel backwards and make code before it even runs change. And of course it can violate hardware specifications; one of the classic UB is running an ASM instruction that the hardware doesn't support, which can cause the CPU to do really random stuff. – Yakk - Adam Nevraumont Mar 16 '21 at 17:57
  • 1
    @Yakk-AdamNevraumont: No, that is not what I am saying. The statement “Anything can happen” is false. The fact that the C standard says behavior is “undefined” means **only** that the C standard does not impose any requirements on the behavior. It does not eliminate other constraints on the behavior. Other constraints on the behavior continue to exist and prevent some things from happening. That is why the statement “Anything can happen” is false… – Eric Postpischil Mar 16 '21 at 18:06
  • This has practical consequences, ranging from implementation-defined extensions continuing to be defined (it is not true that “Anything can happen” if the compiler documentation specifies that it supports an applicable extension to fill in behavior not defined by the C standard) to useful clues for diagnosing bugs (it is not true that “Anything can happen” when the compiler design has consequences that illuminate how a program misbehaved) to safeguarding human lives (it is not true that “Anything can happen” when laws require product safety). Telling people “Anything can happen” misleads them. – Eric Postpischil Mar 16 '21 at 18:08
  • It depends on the context we are talking about here and how far the standard can be extended here in terms of the behavior of the abstract machine at least in term of formalization. If the AM is fully seen as a part of the standard, including its behavior, then there is something like turing provable "weak UB", i.e. something is UB by the strict standard ruling but formally provable to be quite deterministic and even possibly quite specific. There had been and are several examples within the standard, where UB is even "beaten" by other specific rules, where priorities are clear then I think. – Secundi Mar 17 '21 at 11:56
  • A priori, UB can never always imply "You can do what you want here" for the compiler writers. The standard is quite non-specific for its own concistency and priority rules but it's very clear about the priority between specified aspects and the fallback of UB: Specified distinct aspects come always first! So yes, in general, it's totally ok to emphasize the warning with mentioned UB, that one should be aware of the fact, that "maybe" the behavior might be totally weird, but strict formally in terms of mathematical proof systems, that needn's to be the case always. – Secundi Mar 17 '21 at 12:06