In the following program, the line 5 does give overflow warning as expected, but surprisingly the line 4 doesn't give any warning in GCC: http://www.ideone.com/U0BXn
int main()
{
int i = 256;
char c1 = i; //line 4
char c2 = 256; //line 5
return 0;
}
I was thinking both lines should give overflow warning. Or is there something I'm missing?
The topic which led me to do this experiment is this: typedef type checking?
There I said the following(which I deleted from my answer, because when I run it, it didn't show up as I had expected):
//However, you'll get warning for this case:
typedef int T1;
typedef char T2;
T1 x = 256;
T2 y = x; //possible overflow warning! (but it doesn't give warning :()
-Wall doesn't include many options. -Wconversion is one of them and warns about the behavior you're interested in.
See http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
In the general case of assigning an int value to a char object, the compiler doesn't know whether the int is out of range of the char.
Look at the actual warning more closely:
warning: overflow in implicit constant conversion
It is in this specific case, where a constant is being converted to char that the compiler is able to warn you. Likewise, if you changed the declaration of i to be const:
const int i = 256;
you will also get the warning, because the value being assigned to c2 is a constant expression.
Note also that the warning is somewhat misleading as the conversion does not technically "overflow." Arithmetic overflow yields undefined behavior in C++. A narrowing conversion (like int to char, if int has a larger range than char) yields some implementation-defined conversion.
Well, line 5 is an obvious error that any compiler can see directly, and always an error. Line 4 would require at least some data flow analysis to discover the error. Perhaps this isn't done with the settings used at the site, or perhaps the compiler writers didn't consider this important enough to figure it out.
Post GCC 4.3, the semantics of -Wconversion have been updated to detect implicit conversions that might change a value, but you have to enable -Wsign-conversion as well, because otherwise you won't get a warning for code that might change the sign of a number due to coercion between signed and unsigned types.
Contrary to what Crazy Eddie is saying, prior to GCC 4.3 (which hadn't been released yet at the time) -Wconversion didn't generically check for problems introduced by implicit type conversion and the like. Rather, it checked whether your program would behave differently than it would have behaved if it had used old-style K&R function prototypes.
This not only meant that it didn't give a warning on all implicit type conversion / coercion problems, but it also meant that some good code gave an unnecessary warning. And of course, you'd get no error with g++ because such prototypes aren't valid C++ anyway.
Related
I have the following sample code:
inline float successor(float f, bool const check)
{
const unsigned long int mask = 0x7f800000U;
unsigned long int i = *(unsigned long int*)&f;
if (check)
{
if ((i & mask) == mask)
return f;
}
i++;
return *(float*)&i;
}
float next1(float a)
{
return successor(a, true);
}
float next2(float a)
{
return successor(a, false);
}
Under x86-64 clang 13.0.1, the code compiles as expected.
Under x86-64 clang 14.0.0 or 15, the output is merely a ret op for next1(float) and next2(float).
Compiler options: -march=x86-64-v3 -O3
The code and output are here: Godbolt.
The successor(float,bool) function is not a no-op.
As a note, the output is as expected under GCC, ICC, and MSVCC. Am I missing something here?
*(unsigned long int*)&f is an immediate aliasing violation. f is a float. You are not allowed to access it through a pointer to unsigned long int. (And the same applies to *(float*)&i.)
So the code has undefined behavior and Clang likes to assume that code with undefined behavior is unreachable.
Compile with -fno-strict-aliasing to force Clang to not consider aliasing violations as undefined behavior that cannot happen (although that is probably not sufficient here, see below) or better do not rely on undefined behavior. Instead use either std::bit_cast (since C++20) or std::memcpy to create a copy of f with the new type but same object representation. That way your program will be valid standard C++ and not rely on the -fno-strict-aliasing compiler extension.
(And if you use std::memcpy add a static_assert to verify that unsigned long int and float have the same size. That is not true on all platforms and also not on all common platforms. std::bit_cast has the test built-in.)
As noticed by #CarstenS in the other answer, given that you are (at least on compiler explorer) compiling for the SysV ABI, unsigned long int (64bit) is indeed a different size than float (32bit). Consequently there is much more direct UB in that you are accessing memory out-of-bounds in the initialization of i. And as he also noticed Clang does seem to compile the code as intended when an integer type of matching size is used, even without -fno-strict-aliasing. This does not invalidate what I wrote above in general though.
Standards and UB aside, on your target platform float is 32 bits and long is 64 bits, so I am surprised by the clang 13 code (indeed I think you will get actual UB with -O0). If you use uint32_t instead of long, the problem goes away.
Some compiler writers interpret the Standard as deprecating "non-portable or erroneous" program constructs, including constructs which implementations for commonplace hardware had to date had unanimously processed in a manner consistent with implementation-defined behavioral traits such as numeric representations.
Compilers that are designed for paying customers will look at a construct like:
unsigned long int i = *(unsigned long int*)&f; ; f is of type float
and recognize that while converting the address of a float to an unsigned long* is non-portable construct, it was almost certainly written for the purpose of examining the bits of a float type. This is a very different situation from the one offered in the published Rationale as being the reason for the rule, which was more like:
int x;
int test(double *p)
{
x = 1;
*p = 2.0;
return x;
}
In the latter situation, it would be theoretically possible that *p points to or overlaps x, and that the programmer knows what precedes and/or follows x in memory, and the authors of the Standard recognized that having the function unconditionally returned 1 would be incorrect behavior if that were the case, but decided that there was no need to mandate support for such dubious possibilities.
Returning to the original, that represents a completely different situation since any compiler that isn't willfully blind to such things would know that the address being accessed via type unsigned long* was formed from a pointer of type float*. While the Standard wouldn't forbid compilers from being willfully blind to the possibility that a float* might actually hold the address of storage that will be accessed using type float, that's because the Standard saw no need to mandate that compiler writers do things which anyone wanting to sell compilers would do, with or without a mandate.
Probably not coincidentally, the compilers I'm aware of that would require a -fno-strict-aliasing option to usefully process constructs such as yours also require that flag in order to correctly process some constructs whose behavior is unambiguously specified by the Standard. Rather than jumping through hoops to accommodate a deficient compiler configurations, a better course of action would be to simply use the "don't make buggy aliasing optimizations" option.
I've been running the following code through different compilers:
int main()
{
float **a;
void **b;
b = a;
}
From what I've been able to gather, void ** is not a generic pointer which means that any conversion from another pointer should not compile or at least throw a warning. However, here are my results (all done on Windows):
gcc - Throws a warning, as expected.
g++ - Throws an error, as expected (this is due to the less permissive typing of C++, right?)
MSVC (cl.exe) - Throws no warnings whatsoever, even with /Wall specified.
My question is: Am I missing something about the whole thing and is there any specific reason why MSVC does not produce a warning? MSVC does produce a warning when converting from void ** to float **.
Another thing of note: If I replace a = b with the explicit conversion a = (void **)b, none of the compilers throw a warning. I thought this should be an invalid cast, so why wouldn't there be any warnings?
The reason I am asking this question is because I was starting to learn CUDA and in the official Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory) the following code can be found:
// Allocate vectors in device memory
float* d_A;
cudaMalloc(&d_A, size);
which should perform an implicit conversion to void ** for &d_A, as the first argument of cudaMalloc is of type void **. Similar code can be found all over the documentation. Is this just sloppy work on NVIDIA's end or am I, again, missing something? Since nvcc uses MSVC, the code compiles without warnings.
Am I missing something about the whole thing and is there any specific reason why MSVC does not produce a warning? MSVC does produce a warning when converting from void ** to float **
This assignment without a cast is a constraint violation, so a standard compliant compiler will print a warning or error. However, MSVC is not fully compliant C implementation.
Another thing of note: If I replace a = b with the explicit conversion a = (void **)b, none of the compilers throw a warning. I thought this should be an invalid cast, so why wouldn't there be any warnings?
Pointer conversions via a cast are allowed in some situations. The C standard says the following in section 6.3.2.3p7:
A pointer to an object type may be converted to a pointer to a different object type. If the
resulting pointer is not correctly aligned for the referenced type, the behavior is
undefined. Otherwise, when converted back again, the result shall compare equal to the
original pointer. When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.
So you can convert between pointer types provided there are no alignment issues and you only convert back (unless the target is a char *).
float* d_A;
cudaMalloc(&d_A, size);
...
Is this just sloppy work on NVIDIA's end or am I, again, missing something?
Presumably, this function is dereferencing the given pointer and writing the address of some allocated memory. That would mean it is trying to write to a float * as if it were a void *. This is not the same as the typical conversion to/from a void *. Strictly speaking this looks like undefined behavior, although it "works" because modern x86 processors (when not in real mode) use the same representation for all pointer types.
I have a large codebase which I need to sanitize and came across some code similar to the one below that GCC warns me about and that doesn't make any sense to me.
void SetValue(int32_t value); // implementation not important
int main()
{
//...
float val = -10.0f; // real value comes from a function, but can be negative
//...
SetValue(uint32_t(val)); // huh?
//...
}
Any reason to force cast to an unsigned int if the function takes a signed int?
This happens a few times throughout the codebase so it's not a single case.
Is it just a simple mistake or is there some trick that I'm missing?
EDIT: To be clear, the GCC sanitizer throws runtime error: value -10 is outside the range of representable values of type 'unsigned int'.
In the shown code, the cast appears to make no sense. Converting a floating point to an integer type in which the value is out of representable bounds has undefined behaviour. You don't want UB.
My guess: The cast is there to show explicitly that the float is converted to an integer, but the type is wrong by mistake.
Using an explicit cast prevents many compilers from spitting out a warning about an implicit conversion and possible loss of data. it's not uncommon to put in casts to reduce the amount of compiler warnings: you're letting the compiler know that you're ok with the coversion.
In this case the value is being cast to an unsigned int then cast back to signed int. There is probably no reason at all to do that. My guess is that the function once took an unsigned int, and the cast was related to that (explicit cast to reduce the warnings), but later the function profile changed but the other part wasn't updated.
Following code is in simplest form:
struct X {
operator char () const { return 'a'; }
};
int main ()
{
X obj, *p = &obj;
char a = *p; // ok
char c = (true)? *p : 'z';
}
This code gives compiler error as,
error: operands to ?: have different types ‘X’ and ‘char’
Why *p is not resolved to char when there is no ambiguity in class X for typecasting operator ?
Is such spurious error message correct or it's a g++ bug ?
[Update Note: Interestingly this scenario doesn't generate such error]
It seems to be a compiler-bug. I checked it out in the spec, the Standard clearly says (§5.16/3 - C++03),
Otherwise, if the second and third operand have different types, and either has (possibly cv-qualified) class type, an attempt is made to convert each of those operands to the type of the other.
and the rest of the section explains how the conversion is done. There is nothing that stops *p from implicitly converting into char type, using the user-defined conversion operator.
Also, I compiled it with (GCC) 4.5.0. It gives no error, with -pedantic option as well. Tried it -std=c++98 and -std=c++0x. Still no error.
Most definitely, it is a compiler-bug.
There are issues with implicit conversion between classes for the conditional operator ?: in certain versions of G++. Also depending on the version you may either see an error, or you may /not/ see such an errors, however all three of your samples are correctly flagged by G++.
This is less of an answer to the original question but a re-certification of the original poster's, and others', qualm that there might be problems with spurious errors with G++ compiler and the conversion behavior for class types as specified in ISOC++:2003 5.16/3 [expr.cond]/3 (also outlined above by #Nawaz)
One example that shows the converse of the original poster's sample where implicit conversion in both directions can happen but isn't flagged can be seen here.
I compiled this on G++ v3.4.6, v4.1.2, v4.2.1 and v4.5.0(windows) and saw no problems because as again #Nawaz said this is the correct behavior within the ISOC++:2003 spec.
However, when I followed the link on your IDEONE page the structure definition was different and that is the root of your error.
struct X
{
char ch;
X(const char c) : ch(c) {}
operator char () const { return ch; }
};
int main ()
{
X obj('a'), *p = &obj;
char a = *p; // ok
char c = (true)? *p : 'b';
}
Then as above the struct is different and I do get the error your see. This is a correct error as 'z' can be converted into X or char, and because of the constructor 'z' can also be converted into X - bi-directionality of X/'z' so the compiler should stop. However with a little change it can be made to pass and that is exactly the same bug as the GNU G++ bug posted above.
If you convert your sample above to a pointer sample it will fail. Character pointer, int pointer or whatever.
Although unrelated I thought it might be a good point to highlight something that often caught me out whilst cross-porting between Windows/Linux. MSVC will happily pass such conversions often if it deems the conversion to be "safe", or often it may proffer a warning that conversion is being done at the risk of loss of precision - I am not sure what the trigger for allowing this is.
The error is correct. operands to ?: have different types: ‘X’ the first, and ‘char’ the second. The compiler cannot know that you want the expression to be a char in the end - that will happen later, after the evaluation of the whole expression (true)? *p : 'z'; - an evaluation which cannot be done in the first place due to type discrepancies.
C++0x is going to make the following code and similar code ill-formed, because it requires a so-called narrowing conversion of a double to a int.
int a[] = { 1.0 };
I'm wondering whether this kind of initialization is used much in real world code. How many code will be broken by this change? Is it much effort to fix this in your code, if your code is affected at all?
For reference, see 8.5.4/6 of n3225
A narrowing conversion is an implicit conversion
from a floating-point type to an integer type, or
from long double to double or float, or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly), or
from an integer type or unscoped enumeration type to a floating-point type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type, or
from an integer type or unscoped enumeration type to an integer type that cannot represent all the values of the original type, except where the source is a constant expression and the actual value after conversion will fit into the target type and will produce the original value when converted back to the original type.
I ran into this breaking change when I used GCC. The compiler printed an error for code like this:
void foo(const unsigned long long &i)
{
unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}
In function void foo(const long long unsigned int&):
error: narrowing conversion of (((long long unsigned int)i) & 4294967295ull) from long long unsigned int to unsigned int inside { }
error: narrowing conversion of (((long long unsigned int)i) >> 32) from long long unsigned int to unsigned int inside { }
Fortunately, the error messages were straightforward and the fix was simple:
void foo(const unsigned long long &i)
{
unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
static_cast<unsigned int>(i >> 32)};
}
The code was in an external library, with only two occurrences in one file. I don't think the breaking change will affect much code. Novices might get confused, though.
I would be surprised and disappointed in myself to learn that any of the C++ code I wrote in the last 12 years had this sort of problem. But most compilers would have spewed warnings about any compile-time "narrowings" all along, unless I'm missing something.
Are these also narrowing conversions?
unsigned short b[] = { -1, INT_MAX };
If so, I think they might come up a bit more often than your floating-type to integral-type example.
A practical instance that I have encountered:
float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};
The numeric literal is implicitly double which causes promotion.
Try adding -Wno-narrowing to your CFLAGS, for example :
CFLAGS += -std=c++0x -Wno-narrowing
I wouldn't be all that surprised if somebody gets caught out by something like:
float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};
(on my implementation, the last two don't produce the same result when converted back to int/long, hence are narrowing)
I don't remember ever writing this, though. It's only useful if an approximation to the limits is useful for something.
This seems at least vaguely plausible too:
void some_function(int val1, int val2) {
float asfloat[] = {val1, val2}; // not in C++0x
double asdouble[] = {val1, val2}; // not in C++0x
int asint[] = {val1, val2}; // OK
// now do something with the arrays
}
but it isn't entirely convincing, because if I know I have exactly two values, why put them in arrays rather than just float floatval1 = val1, floatval1 = val2;? What's the motivation, though, why that should compile (and work, provided the loss of precision is within acceptable accuracy for the program), while float asfloat[] = {val1, val2}; shouldn't? Either way I'm initializing two floats from two ints, it's just that in one case the two floats happen to be members of an aggregate.
That seems particularly harsh in cases where a non-constant expression results in a narrowing conversion even though (on a particular implementation), all values of the source type are representable in the destination type and convertible back to their original values:
char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?
Assuming there's no bug, presumably the fix is always to make the conversion explicit. Unless you're doing something odd with macros, I think an array initializer only appears close to the type of the array, or at least to something representing the type, which could be dependent on a template parameter. So a cast should be easy, if verbose.
Narrowing conversion errors interact badly with implicit integer promotion rules.
I had an error with code which looked like
struct char_t {
char a;
}
void function(char c, char d) {
char_t a = { c+d };
}
Which produces an narrowing conversion error (which is correct according to the standard). The reason is that c and d implicitly get promoted to int and the resulting int isn't allowed to be narrowed back to char in an initializer list.
OTOH
void function(char c, char d) {
char a = c+d;
}
is of course still fine (otherwise all hell would break loose). But surprisingly, even
template<char c, char d>
void function() {
char_t a = { c+d };
}
is ok and compiles without a warning if the sum of c and d is less than CHAR_MAX. I still think this is a defect in C++11, but the people there think otherwise - possibly because it isn't easy to fix without get rid of either implicit integer conversion (which is a relict from the past, when people wrote code like char a=b*c/d and expected it to work even if (b*c) > CHAR_MAX) or narrowing conversion errors (which are possibly a good thing).
It was indeed a breaking change as real life experience with this feature has shown gcc had turned narrowing into a warning from an error for many cases due to real life pains with porting C++03 code bases to C++11. See this comment in a gcc bug report:
The standard only requires that "a conforming implementation shall issue at least one diagnostic message" so compiling the program with a warning is allowed. As Andrew said, -Werror=narrowing allows you to make it an error if you want.
G++ 4.6 gave an error but it was changed to a warning intentionally for 4.7 because many people (myself included) found that narrowing conversions where one of the most commonly encountered problems when trying to compile large C++03 codebases as C++11. Previously well-formed code such as char c[] = { i, 0 }; (where i will only ever be within the range of char) caused errors and had to be changed to char c[] = { (char)i, 0 }
It looks like GCC-4.7 no longer gives errors for narrowing conversions, but warnings instead.