Issues with porting C++ code to a 64-bit platform
viva64.com
viva64.com
As someone who has been stepping away from C/C++ code towards Go, I'll add this for anyone in a similar boat:
When interfacing C (via CGO) or C++ (via SWIG) code with Go code the #1 thing to keep in mind with 64-bitness is that int in Go may be either 32-bit or 64-bit depending upon the GOARCH the compiler is targeting. int on the C/C++ side is virtually always 32-bit, even on a 64-bit compiler. Use int32 on the Go side to match int on the C/C++ side if you need your structs to align correctly when passing data back and forth.
Eh, unless you absolutely need the addressable memory, there's no real reason to move a legacy codebase to native 64-bit unless the software absolutely needs the RAM.
The general takeaway is that you shouldn't be relying on primitives that aren't underlying OS/implementation safe. Using things like longs(especially if you're targeting both Linux and Windows), and assumed pointer lengths can bite you in the ass if you're not careful(and there is a special circle of hell reserved for people who think bitshifting on a pointer type is fine). A few minutes doing some preventative #defines can save you a world of hurt, especially when porting to the next largest system.
Unless it can take advantage of the extra opcodes that are only available on x86-64 hardware. I think AVX is the best example here.
http://en.wikipedia.org/wiki/Advanced_Vector_Extensions
> Using things like longs(especially if you're targeting both Linux and Windows), and assumed pointer lengths can bite you in the ass if you're not careful(and there is a special circle of hell reserved for people who think bitshifting on a pointer type is fine). A few minutes doing some preventative #defines can save you a world of hurt, especially when porting to the next largest system.
Or investigate the types in stddef.h, such as ptrdiff_t:
> It is a type able to represent the result of any valid pointer subtraction operation.
> A pointer subtraction is only guaranteed to have a valid defined value for pointers to elements of the same array (or for the element just past the last in the array).
http://www.cplusplus.com/reference/cstddef/ptrdiff_t/
Trying to mess with #define stuff is only a good idea if you're targeting older or somewhat nonconformant compilers. The includes were written by the compiler authors; trust them to know more about their compiler than you do.
In C++ we normally use '0' as the name of the null pointer, as C's NULL macro ((void*)0) gives compilation errors normally (though it would have worked here). And of course, who knows what the NULL macro is defined to in C++ mode, it could be plain '0' or a compiler-specific type.
With varargs '0' simply translates to int 0. That is, 32-bit int 0, whereas the function was expecting a 64-bit null pointer. So on 32-bit mode it happened to work fine, but crashed when compiled for 64-bit platforms.
At least they've added some safety with nullptr_t.
Using '0' for null pointers as mpyne suggests is less desirable because it robs the reader of context ("Is this a pointer being compared to 0, or a numeric type?") That could be worked around with Hungarian notation, but some would argue that the cure is worse than the disease.
This is the whole reason for nullptr in the first place.
In this case, they should have turned NULL into a first-class keyword that would parse as an invalid pointer of any given type, and called it "done." In the original C spec NULL wasn't supposed to be a pointer at all -- it was just a more polite way to write "zero." As endless Internet debates have shown, it turns out that it would have been better to reserve it for use in pointer expressions.
But what library vendors can do now is to simply
#ifdef __cplusplus
#if /* version check for C++11 */
#undef NULL
#define NULL nullptr
#endif
#endif
And that way there's less stuff to memorize, but still requires that the code itself has to opt-in, instead of the language having to figure out what insanity NULL may have been defined to.This means that sometime around the mid 2030's some poor programmers will have to take a break from worrying about Y0x80000000 to get bit by the x86-128 varargs bugs.
I just flashed back, man
[1] Post Segmented Architecture Stress Syndrome
I've seen that first hand. In fact, I wrote code to fix it. The guy who wrote the code, still did not get it... 64 bits is 64 bits he said, right... well yes, but that's not the issue here. When you have an int that is say 56 bits in size and you put it in a double that is 64 bits... see what happens:
#include <iostream>
#include <boost/integer.hpp>
int main()
{
boost::uint64_t too_big = 72057594037927936;
double wont_fit = too_big;
std::cout << too_big << "\n";
std::cout << wont_fit << "\n";
return 0;
}./a.out
72057594037927936
7.20576e+16
The maddening part about this is that it's hit or miss. Smaller numbers fit just fine:
#include <iostream>
#include <boost/integer.hpp>
int main()
{
boost::uint64_t not_too_big = 281234;
double will_fit = not_too_big;
std::cout << not_too_big << "\n";
std::cout << will_fit << "\n";
return 0;
}./a.out
281234
281234
Finding and fixing bugs like this will cause ulcers.
That's the problem right there. 64 bits is not always 64 bits. While a 64 bit int gives you a 64 bit number (Actually a 63 bit number and a 1 bit sign flag). An IEEE double precision float only gives you a 52 bit number, and a separate 11 bit number as an exponent. So the problem is that declaring that 64 bits is 64 bits is actually precisely the issue here.
Not true. Modern architectures use 2s-complement rather than an explicit sign bit to represent integers. If you use a sign bit, there are two different zeros (positive and negative) but the range of positive numbers is exactly as large as the range of negative numbers. With 2s-complement, there's only one zero and the range of negative numbers is larger (by one) than the range for positive numbers.
Take an integer value x. Flip the sign bit. Do you now have the value -x? On a 2s complement architecture, you do not. In 2s complement, you have to flip all the bits and add one to get -x.
If you flip the sign bit, the sign changes. It does not mean that if you flip the sign, the value is negated.
But the top bit in 2s complement has a value - it's just a negative one (that's large enough to make any value with it set negative). That's not a sign bit! If you change it, the absolute value most definitely changes, and quite substantially.
I tell you what: if you think modern architectures use an explicit sign bit to represent integers, how do you explain the fact that INT_MAX+1==-INT_MIN? If there's a sign bit, how could you possibly represent more negative integers than positive integers?
memset(values, ARRAY_SIZE * sizeof(size_t), 0);
should be: memset(values, 0, ARRAY_SIZE * sizeof(size_t));
That would not be a fun bug to track down.This is a small detail, but in C99 you can use the "zu" format specifier for size_t typed arguments of the printf/scanf functions; is this also true for C++? Another tip is to use the PRI* and SCN* macros defined in the C header <inttypes.h> (<cinttypes> for C++):
http://pubs.opengroup.org/onlinepubs/009604599/basedefs/intt... http://en.cppreference.com/w/cpp/header/cinttypes
Also, if you're not doing dynamic linking, code without -fPIC is generally faster and for use-cases where C (and/or C++) is used that can make a difference.