7 ms·
Issues with porting C++ code to a 64-bit platform
- ChuckMcM 14y agoGood things to keep in mind. Especially for a generation of programmers who didn't go through the 16 bit -> 32 bit transition. (Trust me, you do not want to write C code on an 80286)
- mpyne 14y agoI had issue #2 (varags) bite me once on a C function where a NULL pointer was used as a sentinel. 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.
- jws 14y agoHeh. Sometime around '94 I helped Southwestern Bell Telephone with that during the 16 bit to 32 bit transitions of a Mac client. 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.
- DerekL 14y agoI'm not sure what you mean by the 16-to-32 transition on the Mac. Are you referring to the 68k-to-PowerPC transition?
- derleth 14y agoInitially, I thought it was the transition to 32-bit-clean code in the 68k line, but that was more of a 24-to-32 transition, and it only affected pointers: http://lowendmac.com/trouble/32bit.shtml http://lowendmac.com/trouble/32bit.shtml
- yew 14y agoHuh. I'd always thought C++ shared C's definition of NULL, but looking I see that it doesn't. What a weird thing to change. At least they've added some safety with nullptr_t.
- CamperBob2 14y agoI'm not convinced that the explosion of specialized types benefits anyone but language lawyers. Some of us actually have work to do, and NULL should mean NULL. 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.
- to3m 14y agoBut before nullptr, NULL didn't portably mean "a null pointer". It meant "0", just as if you'd typed in "0" rather than "NULL". The language doesn't really give the library writer much choice in the matter! (Defining NULL as "((void *)0)" would at least give it an inherently pointer type, but it would be terribly inconvenient to use.) This is the whole reason for nullptr in the first place.
- georgemcbay 14y agoThis is great information for anyone making the 32-bit to 64-bit transition, and at this point you really should be viewing 64-bit as your primary platform if you're writing code for x86(amd64) platforms. Just falling back to 32-bit mode with WoW or similar for your target platform is just inexcusable at this point unless you have very specific legacy support issues. 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.
- cube13 14y ago> This is great information for anyone making the 32-bit to 64-bit transition, and at this point you really should be viewing 64-bit as your primary platform if you're writing code for x86(amd64) platforms. Just falling back to 32-bit mode with WoW or similar for your target platform is just inexcusable at this point unless you have very specific legacy support issues. 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.
- derleth 14y ago> 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. 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 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/ 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.
- shared4you 14y agoOne thing he doesn't mention is "-fPIC", I've been bitten by this bug many times: "please re-compile with PIC enabled and try again"!
- ansgri 14y agoIs there any reason that compilers don't somehow fix this thing by default?
- izacus 14y agoThey can't - you get this error when trying to link other objects, which are already compiled without position-independent code. So the linker can't really fix this. 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.
- 16s 14y agoBut while trying to store a 64-bit integer in double the exact value can be lost (see picture 1). 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.
- Arelius 14y ago> 64 bits is 64 bits he said, right... well yes 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.
- MichaelSalib 14y agoActually a 63 bit number and a 1 bit sign flag 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.
- dschatz 14y agoIn 2s complement, the most significant bit is the sign bit. In fact all valid C integer representations have a sign bit.
- peripetylabs 14y agoEvery C++ developer should read this. I've done research on integer arithmetic, which includes some of the pointer arithmetic issues discussed here. This is one of the best article I've seen on the subject so far. Thanks for sharing. 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/inttypes.h.html http://pubs.opengroup.org/onlinepubs/009604599/basedefs/intt... http://en.cppreference.com/w/cpp/header/cinttypes http://en.cppreference.com/w/cpp/header/cinttypes
- cjensen 14y agoItem #7 is incorrect. You can't make a union of a pointer and an integer type or a union between a number of chars and a larger int type and expect valid results ever -- the optimizer will rip your head off.
- asynchronous13 14y agoI happen to be working on some 32/64 bit code right now, and this article has some very useful tips. Pretty sure they mixed up arguments on their memset() call in section 3 though: 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.
- AndreyKarpov 14y agoThank you. I will correct it.