That Windows is generally better at handling compatibility is one thing, but I'm curious about the following part:
> There's a good reason for this, which IMO Unix-like compilers and system libraries should start adopting, too.
Why? I'm understanding from your comment that you can't mix debug and release objects on Windows because the ABI is different. That's not a feature, that's a limitation. If it works on Linux to mix debug-enabled objects with "release", what use would it have to make it not work anymore?
IIUC debug symbols can be totally separated from the object code, such that you can debug the release if you download the debug symbols. A well configured GDB on distros that offer this feature is able to do it automatically for you. It seems very useful and elegant. Why can't Windows do something like this and how is it an advantage?
(Genuine question, I have a remote idea on how ELF works (wrote a toy linker), not much how DWARF works, and not the slightest idea on how all this stuff works on Windows)
> If it works on Linux to mix debug-enabled objects with "release", what use would it have to make it not work anymore?
There is no difference between Linux and Windows here. The debug/release issue is ultimately up to the API developer.
C++ has has the standard template library (STL). libstdc++, libc++, and MSVC STL are three different implementations. STL defines various iterators. A common choice is for a release-mode iterator to be a raw pointer, just 8 bytes on 64-bit. But the debug-mode iterator is a struct with some extra information for runtime validation, so it's 24 bytes!
The end result is that if you pass an iterator to a function that iterator is effectively two completely different types with different memory layouts on debug and release. This is a common issue with C++. Less so with C. But it's not a platform choice per se.
> IUC debug symbols can be totally separated from the object code, such that you can debug the release if you download the debug symbols. A well configured GDB on distros that offer this feature is able to do it automatically for you. It seems very useful and elegant. Why can't Windows do something like this and how is it an advantage?
MSVC always generates separate .pdb files for debug symbols. Windows tooling has spectacular tooling support for symbol servers (download symbols) and source indexing (download source code). It's great.
> The difference is that on Linux, "compile my program in debug mode"
"Linux" does not have a "compile my program in debug mode" magic toggle (or Release or whatever for what it's worth). Different IDEs and toolchains may have different defaults and expectations. "g++ -g" is not debug mode, it's debug symbols.
The expectation that debug libraries are are compiled with ABI changing flags is, while not fundamental, an significant platform difference.
Historically this might be because MSVC didn't even preserve the standard library ABI across versions (although it has done so for the last 10 years at least), so there were little ABI stability expectations.
> If it works on Linux to mix debug-enabled objects with "release"
it definitely does not. MSVC's debug mode is akin to for instance using libstdc++ with -D_GLIBCXX_DEBUG which does change the ABI. Just passing "-g" which enable debug symbols is very different from what Microsoft calls Debug mode, which adds very extensive checks at all levels of the standard library (for instance, iterators become fat objects which track provenance, algorithms check preconditions such as "the input data is sorted", etc.)
Note tough that libstdc++ has an ABI compatible debug mode that still adds a significant amount debug checks (and it is meant for production deployment).
Yes, I wonder that too. The comment says that debug and release builds have ABIs because they have different type layouts. But why do they have different type layouts? Bounds checking and assertions shouldn’t change the type layout. It seems to me that debug flags should generally only modify code generation & asserts. This is usually the case on Linux, and it’s extremely convenient.
If windows is going to insist on different libraries in debug and release mode, I wish the development version of the library bundled debug and release builds together so I could just say “link with library X” and the compiler, linker and runtime would just figure it out. (Like framework bundles on the Mac). Windows could start by having a standard for library file naming - foo.obj/dll for release and foo-debug.obj/dll for debug builds or something. Then make the compiler smart enough to pick the right file automatically.
Seriously. It’s 2024. We know how to make good compiler tooling (look at go, Swift, rust, etc). There’s no sane reason that C++ has to be so unbelievably complex and horrible to work with.
Windows debug builds add extra sanity checks for which it needs extra members in types. For instance, a vector<T>::iterator is just a T* in a regular build, but in a debug build it also keeps a pointer to the vector so it can check bounds on every access.
But yes, C++ punts a lot of things to the build system, partly because the standard has to work on embedded systems where shared libraries don’t exist. A better build system could fix most of these things, but every build system that tries ends up massively complicated and slow, like Bazel.
But fixing it in Windows doesn’t mean they also need to fix it in embedded systems. Microsoft is in control of their whole ecosystem from the kernel to userland to visual studio. Microsoft could make C++ on windows sane without anyone else’s permission. Their failure to do that is on them and them alone.
I think browser vendors have the right idea when it comes to evolving standards. Vendors experiment using their own products and then come together and try and standardise their work at committee. I think that would be a much better idea than either doing nothing or, as you say, trying to boil the ocean.
> Bounds checking and assertions shouldn’t change the type layout.
Any bounds checks and assertions that rely on storing additional data such as valid iterator ranges or mutation counters would need to change the type layout, wouldn't they?
Even if the STL were purely a header-only library (and influenced only by code generation changes for debug builds), there's still the problem of ABI compatibility across different translation units--or different libraries--which might be built with different options.
EDIT: One of your sibling comments goes into greater detail!
There's a bit of a conflation here; partially my fault. Allow me to clarify...
To generate debug symbols for a given binary (whether executable or library) on Windows and MSVC's cl.exe (and Clang on Windows), compile with `/DEBUG`[1] and one of `/Z7`, `/Zi`, or `/ZI`[2]. This is equivalent to `-g` on Linux gcc/clang. In particular, `/Z7` generates separate `.pdb` files, which contain debug symbols for the binary in question.
The options that the parent commenter and I were discussing, i.e. `/MD`, `/MDd`, /MT`, and `/MTd`[3] have to do with the C and C++ runtime link configuration. These correspond to multithreaded dynamic, multithreaded dynamic debug, multithreaded static, and multithreaded static debug respectively. Therefore, the small `d` refers to debug versions of the C and C++ runtimes. The differences between the debug and release versions of the C and C++ runtimes are listed in the following links[4][5][6][7][8]. The last link in particular demonstrates the debug CRT's functionality.
Conventionally on Windows, debug binaries are linked to the debug versions of the C and C++ runtimes; ergo the requirement that 'Release and Debug binaries on Windows cannot be combined'. This convention is respected by all maintainers who release binary libraries on Windows.
There is no equivalent on Unix-likes: it'd be like having 'debug' versions of libc.so.6/libstdc++.so/libc++.so/libpthread.so with different ABIs. If you wanted to change between release/debug here, you would have to at least re-link (if not re-compile) everything. Imagine having `-cstdlib=libc-debug` and `stdlib=libc++-debug` options.
Both sets of options (debug symbol options and C runtime link options) are orthogonal, and may be freely combined. Hence, it is perfectly possible to link the debug versions of the C and C++ runtimes to a 'release' executable, although it would be pretty weird. For instance, `/O2 /LTCG /arch:AVX2 /MTd`. Equivalent imaginary GNU-style command: `-O3 -flto=thin -march=x86-64-v3 -cstdlib=libc-debug stdlib=libc++-debug -static`. You can see what I mean, I hope.
> There's a good reason for this, which IMO Unix-like compilers and system libraries should start adopting, too.
Why? I'm understanding from your comment that you can't mix debug and release objects on Windows because the ABI is different. That's not a feature, that's a limitation. If it works on Linux to mix debug-enabled objects with "release", what use would it have to make it not work anymore?
IIUC debug symbols can be totally separated from the object code, such that you can debug the release if you download the debug symbols. A well configured GDB on distros that offer this feature is able to do it automatically for you. It seems very useful and elegant. Why can't Windows do something like this and how is it an advantage?
(Genuine question, I have a remote idea on how ELF works (wrote a toy linker), not much how DWARF works, and not the slightest idea on how all this stuff works on Windows)