Of course it is wonderful that you can make programs that are indeed portable between this huge range of computers; just that not every program needs to do so.
Of course it is wonderful that you can make programs that are indeed portable between this huge range of computers; just that not every program needs to do so.
Your code includes your build system and dependency management. If they are not portable, your code is not portable.
I'm a language descriptivist. A standard is an imperfect attempt at describing a language. When the compiler and the standard disagree, the compiler is right, because it decides what the code will actually do.
OpenMP is an essential part of C/C++. Compilers that don't support it can't compile code that claims to be C/C++.
There is no point in pretending that you support systems you don't regularly use. In my case, portability means supporting Linux and macOS, x86 and ARM, GCC and Clang, but not in all combinations.
Portability, simplicity, and performance form a triangle. You can choose two and lose the third, or you can make compromises between them.
Pointers are 64-bit. If they are not, you need a lot of workarounds for handling real data, because size_t is too small.
Computers are little-endian, because that enables all kinds of convenience features such as using data from memory-mapped files directly.
Compilers should warn you if you use integer types of platform-dependent width, unless the width is required to be the same as pointer width. And except as argc and related variables.
To pick up some of that, compiler maintainers obviously disagree about the compiler always being right (which one?), and I'm baffled by the requirement for a compiler to support OpenMP (which version) to be considered C, especially if you're dealing with, say, embedded systems for bioinformatics data acquisition, for instance.
I've successfully claimed to support systems I'd never used in high-profile projects (particularly when the architecture and operating system landscape was rather more interesting); I couldn't just tell the structural biology users there was no point supporting their hardware. I currently support a GPU-centric POWER9 system, but I hadn't actually used POWER in the years I'd been building packages for it (and ARM).
As I said, I'm a language descriptivist. The compiler I'm using right now is right, because it builds the binary. If another compiler has a different opinion, then I may have to deal with multiple dialects. And because the default compilers in many HPC clusters and Linux distributions don't get updated that often, the dialects may continue to be relevant for years.
The situation with OpenMP is similar. A compiler does not have to support OpenMP to be considered a C compiler but to be useful as a C compiler. The bioinformatics software I need to build often uses OpenMP. If a compiler can't build it, it's not doing a very good job of being a compiler. OpenMP versions are not particularly relevant, as the usage is usually very basic. The only version conflict I can remember is from the C++ side: some compilers didn't support parallel range-based for loops.
It's not a fetish if you have ever ported legacy code that was not written with potability in mind.
"ints are always 32-bit and can be used to store pointers with a simple cast" may have worked when the legacy program was written, but it sure makes porting it a pain.
A program with 32bit longs will probably work fine most of the time, and save memory while doing it (which can matter for cache hits/misses).
If the spec was to make it work under certain conditions and not to worry about others, then the additional work for portability just adds cost.
Clearly if it it would have cost 2N dollars to obviate N dollars of work, then cost would not have been worth it. This can occur if either a) portability complicates things a lot or b) it complicates things a little, but over a very long timespan during which maintenance work is being done.
http://archive.opengroup.org/public/tech/aspen/lp64_wp.htm
"By far, the largest body of existing code already modified for 64-bit environments runs on LP64 based platforms."
This.
You can code portably and take advantage of platform-specific features or performance advantages.
I used to work on a product line that supported dozens of different UNIX platforms (and, eventually, even DOS protected-mode). The entire build process was portable. We had a primary configuration file for code/libraries and one for server internals that provided for:
. shared memory configuration (if any) and other limits
. BSD sockets vs System V streams
. byte-swapped vs regular processors and little- vs big-endian
. 16- vs 32-bit processors (later 64 bit)
. RISC vs CISC
. vagaries of C compilers, normal flags to pass for optimization, gcc vs cc, static vs dynamic links, libraries etc
. INT / LONG bytecounts, floating point, etc.
. ... and many others.
The goal was to abstract everything possible and have platform-specific code limited to the configuration files. Then, porting becomes fine tuning the configuration files, adding platform specific code to the mainline (with appropriate DEFINES) only when absolutely necessary, test, finalize configuration, test again, post the final config files and any changes back to the mainline.
I fully agree, but the real problem is not the limitation to platform-specific logic but the conflation of fundamental requirements and incidental requirements. There's no good way to know when you meant "int" as just a counter of a user's files, or "int" as in a 32-bit bitmask, or "int" as the value of a pointer. For the former, it probably doesn't matter if someone later compiles it for a different architecture, for the latter, if you mean int32_t or uintptr_t, use that!
So the question is are you contesting the POSIX defined behaviour over using more logical interfaces from the OS or the wild west where people were hacking around broken, platform-specific features and often broken or awkward system libraries or just the more modern case where people use the standard interface instead of a more performant one. In the latter case I agree I wish there was a more 'nice' way of doing more dynamic and efficient feature detection without making simple C programs crazy complex.
And newer C standards code may even unbreak such code. Ints already are two’s complement in the latest version of C++ and will be in the next version of C, for example.
Be careful about undefined behaviour still. Even if your architecture have defined semantic, the compiler might still not assume them.
That is a pretty bold claim. I would be surprised if any nontrivial "modern" program would be bug-free on a platform with nonzero NULL, non two's compliment ints, big endian an non IEEE floats.
The assumptions we make about the platform just don't feel like assumptions anymore because every machine is the same nowadays.
Java sidesteps this, of course, by defining a JVM to run on and leaving the underlying details to the implementation.
Because if you make an Electron app it's only logical that because it is platform-independent it can only be run on macOS
Isn't most code that would behave differently on different architectures subject to undefined behaviour, however? The signed overflow case mentioned, for example.
Sure, some of it is implementation-defined, but in practice you need to write ultra-portable code anyway in order for your compiler not to pull the rug out underneath you.
Many "correct" programs rely on undefined behaviour for optimizations while the authors knowingly assert (or assume) that the undefined behaviour doesn't actually occur.
So the question is, if you are writing non-portable code for a specific environment, is a UB that actually triggers on your environment "non-portability" or is it a "bug"? I think most people would define that as being a bug.
Edit: Okay, bad example cos you could maybe use int_leastN_t or something in that example to document the assumptions... But you don't always have control over types, and there may be multiple constraints, etc..
The issue is when platform-specific code ties your business logic to a particular vendor API, and then this vendor milks your company for profit.