> The C standard is talking about the definition of symbols but it never mentions a linker or how it should work, AFAIR.
This is not true. For example, in C89, clause 126.96.36.199 Translation phases, number 8:
All external object and function references are resolved. Library components are linked to satisfy external references to functions and objects not defined in the current translation.
This is echoed verbatim in C11. The requirements for external references and definitions are covered elsewhere in the standard (I cited them earlier).
> The linker is just implementing the requirements in the C standard in a reasonable and convenient way:
The linker is either conformant or not. However, the particular requirement of "one definition only" is one where the standard lets the implementation (i.e., the linker) behave in an undefined way (because the standard implicitly recognises that an implementation may not have sufficient means to enforce conformant behaviour). This merely means that the onus is on the programmer to ensure that the whole program is conformant, which, in this case, means to ensure that no function is defined more than once in the program, which includes all its libraries.
> A library is not meant to be totally incorporated into the program; only modules that provide definitions for the program are incorporated.
This is, in fact, not a requirement. An implementation is free to incorporate all of the library. And when a shared library used, this is exactly what happens.
If the previous "static" linking phase has resolved some of the dependencies, these dependencies won't be resolved during the "dynamic" linking phase to the functions incorporated in the shared library. This is permitted under the broad umbrella of undefined behaviour. However, this:
> Again, this is reasonable and convenient, and works extremely well in practice. It has been like that for many decades.
would be a dangerous idea to entertain.
First, other libraries that the executable depends on will not have been linked statically to the same "replacement" functions, so they will have to resort to the "original" dependencies. Your program, as whole, may end up using different versions of the same set of functions simultaneously. Even without those other libraries the sole library that you intend to patch "without patching" can happily continue using its own functions, with the same result.
Second, if those other libraries resort to the same trick, the situation is not just proportionally worse, it is much worse because this may end up with duplicate export symbols and a whole new problem to solve.
Finally, none of this is portable and on some platforms one would have to go through pretty serious gyrations to convince the linker it is OK to have duplicate symbols and that these particular ones are to be used statically. Which is actually good.