Reputation: 1120
I have a code that is being changed to compile in a 64-bit mode where previously it was being compiled in Win32 for various reasons. This has caused some cleanup work to address some warnings, so I'm picking through code and found something that looks like this:
class foo
{
public:
int foo() { return data_.size()-1; }
private:
std::vector<int> data_;
};
The size() method on STL containers returns unsigned values. The return value is being cast into a signed integer value so there's a conversion that will occur at some point.
I'm not sure about the precedence here though. Will the value that size() is returning get cast to an int and then have 1 subtracted, which would result in the return value being -1 if size was zero? Or will we subtract 1 from an unsigned int, possibly doing bad things if the container is empty when this gets called?
Thanks!
Upvotes: 1
Views: 158
Reputation: 7625
Your second guess is correct. In general, evaluating any expression of the form
exp1 op exp2
works in the steps
exp1
exp2
op
on the values of exp1
and exp2
Note: steps 1 and 2 can occur in any sequence. The point is each operand is evaluated before applying the operator.
So in this case exp1
will evaluated as an unsigned int
value first, and may have undesirable effect.
Upvotes: 0
Reputation:
It will be unsigned = unsigned - 1; return signed(unsigned) where unsigned(0) - 1 == unsigned(max)
From 4.7 Integral conversions
A prvalue of an integer type can be converted to a prvalue of another integer type. A prvalue of an unscoped enumeration type can be converted to a prvalue of an integer type. If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source integer (modulo 2n where n is the number of bits used to represent the unsigned type). [ Note: In a two’s complement representation, this conversion is conceptual and there is no change in the bit pattern (if there is no truncation). — end note ] If the destination type is signed, the value is unchanged if it can be represented in the destination type (and bit-field width); otherwise, the value is implementation-defined.
Hence any unsigned value greater than the maximum signed value leads to implementation defined behavior.
Upvotes: 3
Reputation: 5198
The next program returns i=-1
. The bit pattern of std::numeric_limits<unsigned>::max()
is that one of the int
-1
. This is the reason why your code works in w32.
#include <iostream>
#include <limits>
int main() {
std::cout << "i=" << static_cast<int>(std::numeric_limits<unsigned>::max()) << '\n';
}
/*
Local Variables:
compile-command: "g++ test.cc -o a.exe && ./a.exe"
End:
*/
But, you rely in your code on the translation of signed integral types and unsigned ones. The types must fit! Therefore, better use std::ptrdiff_t
and std::size_t
.
#include <iostream>
#include <limits>
int main() {
std::cout << "i=" << static_cast<std::ptrdiff_t>(std::numeric_limits<std::size_t>::max()) << '\n';
}
/*
Local Variables:
compile-command: "g++ test.cc -o a.exe && ./a.exe"
End:
*/
Upvotes: 0
Reputation: 58
If the size is 0 the result is not -1 but a very large integer "18446744073709551615" (unsigned(max)).
#include <vector>
#include <iostream>
int main()
{
std::vector<int> nums {};
std::cout << "nums contains " << nums.size()-1 << " elements.\n";
// nums contains 18446744073709551615 elements.
}
http://coliru.stacked-crooked.com/a/a69d4af99ba77f47
Upvotes: 1
Reputation: 206697
When data_.size()-1
is evaluated as an unsigned integer. When data_.size()
is 0
, the function could return a very large positive number instead of -1
.
Your best option:
int foo() { int s = data_.size(); return s-1; }
Upvotes: 0