when does static_cast compile to no CPU instructions - c++

This question is motivated by the following (simplified) example.
Consider the following code:
void f(unsigned int x) { }
int main()
{
int z = 3;
if (z > 0)
for (unsigned int i = 0; i < z; ++i)
f(i);
return 0;
}
Compiling with all warnings enabled, the compiler issues a
warning: comparison of integer expressions of different signedness: ‘unsigned int’ and ‘int’
This is clearly not a real problem, since the code explicitly checks that the upper limit in the for loop is positive.
One way to eliminate the warning is to explicitly static_cast z:
int main()
{
int z = 3;
if (z > 0)
for (unsigned int i = 0; i < static_cast<unsigned int>(z); ++i)
f(i);
return 0;
}
At least in this simplified example, both codes compile to the same assembler, see it on godbolt.
Indeed, I would not expect that a cast from an int to unsigned int to result in any instructions, since both types are stored with the same number of bits.
Which leads to the question: when does static_cast result in no compiled instructions? Is this always the case for a static_cast between integers having the same size in bits?

When you don't have the cast there, the compiler adds a conversion for you anyway, so it makes sense that they both compile to identical machine code. But when it is implicit, it's likely that you weren't expecting or even aware of that (just search on StackOverflow for the number of cases people try to compare vector.size() with -1). So your compiler generates a warning to ask you to confirm that's really what you want.

Related

How do usual arithmetic conversions work?

I was running this code in VS2019:
#include<iostream>
#include<string>
#include<typeinfo>
using namespace std;
int main() {
string mystring = "hello world";
for (int j = 0; j < 10; j++) {
if (mystring[j + 1] == 'w') {
cout<<"string contains w letter\n";
}
else {
continue;
}
return 0;
}
}
And I realized that when I run it on Debug mode on an x86 platform, everything is ok, but if I change the platform to x64, the following warning appears:
C26451 Arithmetic overflow: Using operator '+' on a 4-byte value and then casting the result to an 8-byte value. Cast the value to the wider type before calling operator '+' to avoid overflow (io.2).
It seems to be related to Usual arithmetic conversions, such that, if the operands are of different types, a conversion is applied to one of them before calculation. But if they are equal, that still happens?
If I print typeid(j).name() and typeid(1).name(), it prints int for both, so what is the reason for this warning? The warning is fixed if I change the if condition to (mystring[j + static_cast<__int64>(1)] == 'w'). The explanation, I think, should be that the number '1' is not considered of type int on x64, or it is but occupies different bits of memory than the int type on x64.
I would really like to clarify the issue, thanks.
The "C26451" warning is not a standard compiler warning. It's part of the C++ Code Guidelines Checker which is giving you 'recommendations'. For more on this feature, see Microsoft Docs.
In C++ Core Guidelines the specific recommendation the checker is using here is: ES.103: Don't overflow.
The reason this only happens in x64 is because size_t is 64-bits while int is 32-bits. In x86, both int and size_t are 32-bits.
The std::string operator[] takes a size_t. The cleanest simplest fix here is:
for (size_t j= 0; j <10; j++)
You could also address this by explicitly promoting the int to size_t before the addition takes place:
if (mystring[size_t(j) + 1] == 'w') {
You could also ignore the warning by adding:
#pragma warning(disable : 26451)
Or you could disable the C++ Core Guidelines Checker.
This is how the std::string [] operator defined. It takes std::size_t.
char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;
That's where the casting is taking place: int -> size_t.
https://www.learncpp.com/cpp-tutorial/fixed-width-integers-and-size-t/
edit: See Chuck Walbourn answer
It seems to be related to Usual arithmetic conversions, such that, if the operands are of different types, a conversion is applied to one of them before calculation. But if they are equal, that still happens?
Your conclusion is incorrect.
VS thinks that j + 1 has the potential to overflow. It recommends that you perform the arithmetic operation, +, on a wider integral type to reduce the chances of overflow. Note that static_cast<std::size_t>(j) + 1 could, in theory, still overflow but VS does not care about that.
You don't get the warning in x86 mode since the size of std::size_t and int are same on that platform.

Using `size_t` for lengths impacts on compiler optimizations?

While reading this question, I've seen the first comment saying that:
size_t for length is not a great idea, the proper types are signed ones for optimization/UB reasons.
followed by another comment supporting the reasoning. Is it true?
The question is important, because if I were to write e.g. a matrix library, the image dimensions could be size_t, just to avoid checking if they are negative. But then all loops would naturally use size_t. Could this impact on optimization?
size_t being unsigned is mostly an historical accident - if your world is 16 bit, going from 32767 to 65535 maximum object size is a big win; in current-day mainstream computing (where 64 and 32 bit are the norm) the fact that size_t is unsigned is mostly a nuisance.
Although unsigned types have less undefined behavior (as wraparound is guaranteed), the fact that they have mostly "bitfield" semantics is often cause of bugs and other bad surprises; in particular:
difference between unsigned values is unsigned as well, with the usual wraparound semantics, so if you may expect a negative value you have to cast beforehand;
unsigned a = 10, b = 20;
// prints UINT_MAX-10, i.e. 4294967286 if unsigned is 32 bit
std::cout << a-b << "\n";
more in general, in signed/unsigned comparisons and mathematical operations unsigned wins (so the signed value is casted to unsigned implicitly) which, again, leads to surprises;
unsigned a = 10;
int b = -2;
if(a < b) std::cout<<"a < b\n"; // prints "a < b"
in common situations (e.g. iterating backwards) the unsigned semantics are often problematic, as you'd like the index to go negative for the boundary condition
// This works fine if T is signed, loops forever if T is unsigned
for(T idx = c.size() - 1; idx >= 0; idx--) {
// ...
}
Also, the fact that an unsigned value cannot assume a negative value is mostly a strawman; you may avoid checking for negative values, but due to implicit signed-unsigned conversions it won't stop any error - you are just shifting the blame. If the user passes a negative value to your library function taking a size_t, it will just become a very big number, which will be just as wrong if not worse.
int sum_arr(int *arr, unsigned len) {
int ret = 0;
for(unsigned i = 0; i < len; ++i) {
ret += arr[i];
}
return ret;
}
// compiles successfully and overflows the array; it len was signed,
// it would just return 0
sum_arr(some_array, -10);
For the optimization part: the advantages of signed types in this regard are overrated; yes, the compiler can assume that overflow will never happen, so it can be extra smart in some situations, but generally this won't be game-changing (as in general wraparound semantics comes "for free" on current day architectures); most importantly, as usual if your profiler finds that a particular zone is a bottleneck you can modify just it to make it go faster (including switching types locally to make the compiler generate better code, if you find it advantageous).
Long story short: I'd go for signed, not for performance reasons, but because the semantics is generally way less surprising/hostile in most common scenarios.
That comment is simply wrong. When working with native pointer-sized operands on any reasonable architectute, there is no difference at the machine level between signed and unsigned offsets, and thus no room for them to have different performance properties.
As you've noted, use of size_t has some nice properties like not having to account for the possibility that a value might be negative (although accounting for it might be as simple as forbidding that in your interface contract). It also ensures that you can handle any size that a caller is requesting using the standard type for sizes/counts, without truncation or bounds checks. On the other hand, it precludes using the same type for index-offsets when the offset might need to be negative, and in some ways makes it difficult to perform certain types of comparisons (you have to write them arranged algebraically so that neither side is negative), but the same issue comes up when using signed types, in that you have to do algebraic rearrangements to ensure that no subexpression can overflow.
Ultimately you should initially always use the type that makes sense semantically to you, rather than trying to choose a type for performance properties. Only if there's a serious measured performance problem that looks like it might be improved by tradeoffs involving choice of types should you consider changing them.
I stand by my comment.
There is a simple way to check this: checking what the compiler generates.
void test1(double* data, size_t size)
{
for(size_t i = 0; i < size; i += 4)
{
data[i] = 0;
data[i+1] = 1;
data[i+2] = 2;
data[i+3] = 3;
}
}
void test2(double* data, int size)
{
for(int i = 0; i < size; i += 4)
{
data[i] = 0;
data[i+1] = 1;
data[i+2] = 2;
data[i+3] = 3;
}
}
So what does the compiler generate? I would expect loop unrolling, SIMD... for something that simple:
Let's check godbolt.
Well, the signed version has unrolling, SIMD, not the unsigned one.
I'm not going to show any benchmark, because in this example, the bottleneck is going to be on memory access, not on CPU computation. But you get the idea.
Second example, just keep the first assignment:
void test1(double* data, size_t size)
{
for(size_t i = 0; i < size; i += 4)
{
data[i] = 0;
}
}
void test2(double* data, int size)
{
for(int i = 0; i < size; i += 4)
{
data[i] = 0;
}
}
As you want gcc
OK, not as impressive as for clang, but it still generates different code.

Does C4800 have any real world value?

The C4800 warning in the microsoft c++ compiler as described here:
https://msdn.microsoft.com/en-us/library/b6801kcy.aspx
makes this code:
// C4800.cpp
// compile with: /W3
int main() {
int i = 0;
// try..
// bool i = 0;
bool j = i; // C4800
j++;
}
throw the C4800 warning: "'type' : forcing value to bool 'true' or 'false' (performance warning)"
Microsoft seems to think it's reasonably important, and has it as a Level3 warning, however Clang does apparently think it's not, as has zero complaints about it at -Weverything, its maximum warning level.
Is there any real world bug someone can come up with that C4800 would point out that would make it worth having it enabled?
Basically, this is just warning you that you've converting some other integer type to a bool, and this conversion isn't entirely free.
It's mostly present (at least as I see things) to warn that you're mixing bools with other integer types, which not only leads to a minor reduction in performance, but may also indicate some confusion in the code. Looking at the code in the question:
int i = 0;
// try..
// bool i = 0;
bool j = i; // C4800
j++;
...what we have looks like an incomplete conversion of some code that previously defined j to be of type int. The definition of j has been edited so it's now of type bool, but we're still assigning it a value from an int, and (worse) using a post-increment on it, both of which would make sense if j had type int, but really don't with j having type bool.
So, the question is whether what we really wanted to assign j with the result of a comparison: bool j = (i != 0); or maybe a more complete conversion that would turn i into a bool as well:
bool i = false;
// ...
bool j = i; // no warning
j = true; // cleaner way of producing the same result as the post-increment.

C++: warning: Signed and unsigned expression ( Gmake, freebsd )

I'm facing a little problem and I hope you can help me. Thank you.
Here is the error code:
FILE.cpp: In member function 'bool DragonSoulTable::ReadAdditionalApplys()':
FILE.cpp:223: warning: comparison between signed and unsigned integer expressions
And here is the code I put on pastebin because it is too big to put that code on forum
FILE.CPP
Go to line 223, you have:
for (int i = 0; i < m_vecDragonSoulNames.size(); i++)
As you can see, i is of type int but does m_vecDragonSoulNames.size() return an int??
In fact, you have a lot of comparisons like this in your code.
Compilers give warnings (not errors) when you compare signed and unsigned types, this is because the ranges of this types are different. And there are good reasons for this, if you dont be careful the results can be surprising...
If you know its safe to make such a comparison, you can explicitly cast one of the values to be of the same type of the other, and the warning will dissapear.
Something like:
unsigned int a = someUnisgnedValue;
int b = someSignedValue;
if ((unsigned) b < a)
//... do something
Or you can just use both of the same type. For example in the line 223 of your code you can do:
for (unsigned int i = 0; i < m_vecDragonSoulNames.size(); i++)
Please, check this other question: When to use unsigned values over signed ones?
Exactly what the warning says. You are comparing signed and unsigned integers. For example:
uint NUM = 5;
for (int i=0; i < NUM; i++) // Here you compare int and uint
The solution is to
make all of your variable the same type,
disregard the warning
suppress it
compile with lower error reporting level and thus not generate it at all.
In that for loop i is integer where as the m_vecDragonSoulNames.size() is of type unsigned integer. make i unsigned int

Does an observable difference exist using `unsigned long` and `unsigned int` in C (or C++) when both are 32 bits wide?

I'm using an MPC56XX (embedded systems) with a compiler for which an int and a long are both 32 bits wide.
In a required software package we had the following definitions for 32-bit wide types:
typedef signed int sint32;
typedef unsigned int uint32;
In a new release this was changed without much documentation to:
typedef signed long sint32;
typedef unsigned long uint32;
I can see why this would be a good thing: Integers have a conversion rank between short and long, so theoretically extra conversions can apply when using the first set of definitions.
My question: Given the above change forced upon us by the package authors, is there a situation imaginable where such a change would change the compiled code, correctly leading to a different result?
I'm familiar with the "usual unary conversions" and the "usual binary conversions", but I have a hard time coming up with a concrete situation where this could really ruin my existing code. But is it really irrelevant?
I'm currently working in a pure C environment, using C89/C94, but I'd be interested in both C and C++ issues.
EDIT: I know that mixing int with sint32 may produce different results when it's redefined. But we're not allowed to use the original C types directly, only the typedef'ed ones.
I'm looking for a sample (expression or snippet) using constants, unary/binary operators, casts, etc. with a different but correct compilation result based on the changed type definition.
In C++ you may run into issues with function overloading. Say you had the following:
signed int func(signed int x) {
return x + 1;
}
signed long func(signed long x) {
return x - 1;
}
int main(void) {
sint32 x = 5;
std::cout << func(x) << std::endl;
}
Prior to the typedef definition change, the value 6 would be printed. After the change the value 4 would be printed. While it's unlikely that an overload would have behavior that's this different, it is a possibility.
You could also run into issues with overload resolution. Assume you had two functions with the following definitions:
void func(int x);
void func(unsigned int x);
and were calling the functions with:
sint32 x;
func(x);
Prior to the change, the function call was unambiguous, func(int) would be an exact match. After the typedef change, there is no longer an exact match (neither function takes a long), and the compiler fails since it will not be able to determine which overload to invoke.
It might lead to subtle issues because literal numbers are int by default.
Consider the following program:
#include <iostream>
typedef signed short old16;
typedef signed int old32;
void old(old16) { std::cout << "16\n"; }
void old(old32) { std::cout << "32\n"; }
typedef signed short new16;
typedef signed long new32;
void newp(new16) { std::cout << "16\n"; }
void newp(new32) { std::cout << "32\n"; }
int main() {
old(3);
newp(3); // expected-error{{call of overload ‘newp(int)’ is ambiguous}}
}
This leads to an error because the call to newp is now ambiguous:
prog.cpp: In function ‘int main()’:
prog.cpp:17: error: call of overloaded ‘newp(int)’ is ambiguous
prog.cpp:12: note: candidates are: void newp(new16)
prog.cpp:13: note: void newp(new32)
whereas it worked fine before.
So there might be some overloads surprises where literals were used. If you always use named (and thus typed) constants, you should be fine.
If a pointer to sint32/uint32 is used where a pointer to int/long is expected (or vice versa) and they don't match int with int or long with long, you may get a warning or error at compile time (may in C, guaranteed in C++).
#include <limits.h>
#if UINT_MAX != ULONG_MAX
#error this is a test for systems with sizeof(int)=sizeof(long)
#endif
typedef unsigned uint32i;
typedef unsigned long uint32l;
uint32i i1;
uint32l l1;
unsigned* p1i = &i1;
unsigned long* p1l = &l1;
unsigned* p2il = &l1; // warning or error at compile time here
unsigned long* p2li = &i1; // warning or error at compile time here
int main(void)
{
return 0;
}
Nothing in the Standard would allow code to safely regard a 32-bit int and long as interchangeable. Given the code:
#include <stdio.h>
typedef int i32;
typedef long si32;
int main(void)
{
void *m = calloc(4,4); // Four 32-bit integers
char ch = getchar();
int i1 = ch & 3;
int i2 = (ch >> 2) & 3;
si32 *p1=(si32*)m + i1;
i32 *p2=(i32*)m + i2;
*p1 = 1234;
*p2 = 5678;
printf("%d", *p1);
return 0;
}
A compiler would be entitled to assume that because p1 and p2 are declared as different types (one as int and the other long), they cannot possibly point to the same object (without invoking Undefined Behavior). For any input character were the above program would be required to do anything (i.e. those which would avoid Undefined Behavior by causing i1 and i2 to be unequal), the program would be required to output 1234. Because of the Strict Aliasing Rule, a compiler would be entitled to do anything it likes for characters like 'P', 'E', 'J', or 'O' which would cause i and j to receive matching values; it could thus output 1234 for those as well.
While it's possible (and in fact likely) that many compilers where both int and long are 32 bits will in fact regard them as equivalent types for purposes of the Strict Aliasing Rule, nothing in the Standard mandates such behavior.