In fact the C standar NEVER forbids different compiled units to redefine the same external symbols.
In fact it even **encourages** such use, which is required for example so that all C programs can have their own implementation of the "main()" function which is externalized to the same symbol once compiled with the same "C" linkage by default.
Then it's not the C language or the compiler that instruct themselves how these compiled units will be linked together. This is only specified at the linker level (which is not part of the C standard itself). But all linkers require you specify unambiguously the order of units.
If **any** linker allows you to use "libraries" (containing multiple units in random order), it can accept to process them ONLY if there's no pair of distinct units in the same library that define the same symbol (otherwise the result would be completely unpredictable).
Shared libraries (or libraries and directories containing an ordering manifest file) are assured to respect this constraint because they are already the result of a prevcious pass of the linker for which the order or resolution was already specified and checked.
Shared libraries are not a requirement for any POSIX system compatible with C: these systems can work perfectly without supporting them (it's enough on these system to have a linker tool that supports a manifest in their supported archive format, or that will look for the existence of a manifest file if libraries are represented as a filesystem directory containing all compiled units).
In practice, archive formats recognized by linkers **already look for a manifest file** containing not just the order of units, but a full mapping index of symbols, associating them with the name of the unit in which they are defined and exported, because it allows much faster linking, without having to process completely each unit file inside the library: such unique mapping index cannot be built and inserted inside the library if there are two units in the library definining the same symbol.
But:
- old archiving tools (like old versions of "ar" on old versions of Unix) did not check that and did not build this index/manifest, it had to be built and added separately to the archive, and updated each time you added/updated/removed a unit from the library. The old "ar" tool is now completely deprecated. For backup/archiving purpose, "tar" is much better and universally supported on all Unix/Linux variants, and now most archives are compressed ("taz", "tgz, "zip", "gz", "xz", you have the choice...)
- archive formats containing a manifest or index solve the problem for linkers (this solution is used by linkers inside virtual machines like Java, .Net, Perl, Python... (Even Lua uses such solution even if it's hidden behind the concept of "loaders", which are actually linkers that programs themselves can control and tune for their needs)
- shared libaries are in fact much cleaner, more compact, and much faster to process to create native programs: the ELF format (or similar variants) is now almost universal between all Unix/Linux/Windows and many other systems (and they still allow compiled programs to invoke or use the system linker system themselves using "loaders")
This solution based on shared libraries (or archives with manifests) and the concept of generic "loaders" is not just for linking standalone programs, but it also exists in scripting languages and programs runing on virtual machines: these programs can control themselves the resolution order, control themselves the environment path in which external libraries or units will be found, they can check contraints like security requirements, access rights, digital signatures; they can use network services to download the units; they can use conditions like the user's locale and other preferences, they can try to best match the architecture such as i386 vs x64 vs. i686 when they have the choice, they can perform comparative benchmark tests before deciding which implementation to use...
**Absolutely nothing in the C standard** forbids units needed or used in the same program to be limited to sets of unique symbols !
All what the C standard says, is that a separate unit will never be created by the compiler such that it contains multiple instances of the same extern symbol with the "C" linkage (Some compilers for specific systems may be exceptions to this rule : they may still create **simultaneously** multiple implementations of the same source, compiled for different architectures, or for different goals such as debugging purpose, or different levels or methods of optimization which may not be safe in all situations such as relocatable vs. reentrant versions for multithreading; in which case the object format will actuall be like an archive with several distinct entry points for the same symbol but distinguished by some encoded goals, or by some "decoration" in the encoded exported symbols; but this is equivalent to exporting distinct symbols; this works only if the linker recognizes the multiple encoded symbols and knows the rules to locate them and to decide predicatably which implementation are the most suitable)