The more I use C and C++, the more I am convinced that shared libraries are the biggest technical debt for the whole ecosystem. It is these shared libraries that are the driving impetus behind “ABI stability”. It is because of shared libraries that we can’t have nice performance or safety enhancing features.
Right now the C and C++ ecosystem is groaning under the weight of shared library technical debt.
I remember a friend in the dark ages worked on a mixed Pascal and C codebase and they had some tool that generated an adapter layer between the two. All it usually did was flip stuff around on the stack before calling the routine. And then clean things up before returning.
Surely one can use OS IPC instead, like we used to do during the days static linking ruled the compiler world, but then don't complain about higher resource usage when every single plugin is its own process.
Shared libraries not only require this absolutely crippling adherence to the ABI they also are a security mess since you need updates from both your software provider and your operating system vendor to ensure that anything is safe.
That is an entirely reasonable use. And one that can have specific types and ABI guarantees for the commercial software and the plug-in. In addition, this would only be required if you were the developer of the commercial software or a plug-in. One example of this is COM which had specific types and memory allocation/deallocation.
However, the case where the C standard library is being dynamically linked, forces everyone to pay the price for ABI stability.
However it is probably overused relative to the situations where it is genuinely useful.
If you have an ABI, you need to put an explicit size and signedness on every parameter and return value. Period. No excuses.
No "int". No "unsigned int". If I'm being really pedantic, don't even use "char".
It should be "int32_t", "uint32_t", and "uint8_t".
Every time I see objections, it's always someone who wants to use some weird 16-bit architecture. The problem is that those libraries probably won't work anyhow since nobody tests their libraries on anything other than x86 and maybe Arm. If your "int" is 16 bits, you're likely to have a broken library anyway.
> If I'm being really pedantic, don't even use "char".
Careful about that one. I believe `uint8_t` isn't required to be a character type, which has implications for type based aliasing.
The fact that every single compiler typedefs "uint8_t" and "unsigned char" and yet that isn't guaranteed by the standard is the kind of thing that just makes you want to cry.
I'm actually genuinely curious about this: people talk about the possibility of existence, but I haven't seen anybody point to an actual compiler that implements uint8_t as an extended numeric type rather than being an equivalent typedef to "unsigned char". Is there a compiler that really does this?
Different architectures had all manner of different "native" sizes. 8-bit micros were still relatively expensive even up through mid-1980s. The 80286, for example, was 16-bit and went away only in the early 1990s. IBM AS/400s were still 48-bits through the mid-1990s. Apple was still dealing with non-32 bit clean applications in the mid 90s. Linux running on Alpha was still cleaning up x86-isms in mid 1990s.
C99 finally introduced "stdint.h". By the mid-2000s, everything had converged to 32-bit (or 64-bit).
C11 should have deprecated "int" so that compilers could throw warnings on it. But, then, we still haven't removed K&R signatures from the C standards, so here we are.
As an user I complain that I can't run linux games from 20 years ago because GCC broke the libstdc++ ABI 15 years ago and how WIN32 is the only stable unix ABI.
Luckily I'm not a compiler developer.
There're couple special types with machine-dependent size, like IntPtr, but these only used for opaque handles and C interop.
There has to be an ABI for it because we have to pin down what it means to pass an intmax_t as an argument to a function, how it is aligned on the stack if passed that way, and how it's placed into a structure and so on.
However, there could be a provision that the ABI treatment of intmax_t is not guaranteed; it is subject to change due to the redefinition of intmax_t.
And, for that reason, it should be kept out of API's.
That leaves API's that deal specifically with intmax_t itself rather than using it to represent something. Those can use aliasing and versioning.
Say we had a function like this:
struct intmax_quot_rem intmax_div(intmax_t, intmax_t);
today intmax_t might be 64 bits, so the application should be compiled in such a way that the call to intmax_div goes to some __intmax_div_64, which looks like this: struct intmax_quot_rem __intmax_div_64(int64_t, int64_t);
Even when intmax_t changes to 128, that compiled program continues to reference __intmax_div_64 which uses int64_t parameters and structure members. A newly compiled program calls __intmax_div_128.A particular problem would be functions in the printf family. Say we have a conversion specifier which prints intmax_t which is 64 bits today. Here, the solution is even simpler. The "PRI" macros introduced in C99 provide it. Given an intmax_t value x, we print it like this:
printf("x = %" PRIdMAX "\n", x);
so today that might expand to some conversion specifier that is identical to the one for PRIx64. And so that compiled program will have that baked into its conversion string, so everything will continue to be the same even if the platform moves to a 128 bit intmax_t.A newly compiled program on the 128 bit intmax_t platform will get a different PRIdMAX string from the header file, which expands to a conversion specifier matching int128_t.
Basically all the issues are solvable except the issue of some application code carelessly using intmax_t in its APIs without any plan for versioning.