TL;DR: Linker Links Without Judgeing
A linker doesn't know about programs and functions and even less about usage. A linker combines objects, including all segments and resolving open relation. If a data area is defined in an object it will be included in the resulting object. The linker has no way to determinate if it's used or not - nor is it his job to do so.
The issue seems to be originated in an in complete picture about what a compiler creates, a library stores and how a linker handles it. So lets take a detour to look about the different things we like to subsume under compiling and linking(*1):
To start with, a compiler does not create programs or functions, but object modules. Program and function are source level categorises programmers of a certain language will use. And while the compiler will understand their meaning within its language and handle it accordingly during source handling, his output will be nothing but an object modules or objects for short. Usually one per compile run. Objects are language independent items. An object does not hold 'functions' and 'data tags' as distinct elements, but a collection of segment, each a list of size and content as well a list of names associated with offsets within those segments and fixup information containing how to modify content when reordering those elements into a new object.
All this is stored as a sequence of records - a structure that dates back to the earliest days of computing. In case of Microsoft tools it's based on the Intel object format (*2) with some MS-specific modifications.
C-compiler are build with the assumption of creating a standard set of segment names (BSS,TEXT,DATA,STACK) according to the way C structures programs, but the object format couldn't care less. Here a segment name is without any meaning, beside being a unique identifier. What it does and how it's handles is defined by attribute records.
Important point to remember: C specific ideas and concepts stop here. Every handling once an object is created is independent of language and compiler.
A library is just a container of objects - much like a ZIP archive.
I most systems a compiler outputs the object created as standard file (*3) - the one C likes to call a.out (*4). That output may, depending on OS, be directly run. But more often than not it's just a part of a larger project or some generic utility, like C functions to handle streams. Back in ye olde days creating a file was a complex issue and having dozens if not hundreds of files almost impossible. To handle that libraries were invented, collection of various items that could have been files on their own. One special case were object libraries, especially suited to store and quickly retrieve object modules.
Using libraries simplified handling as well as software delivery. Including run time support like the C-Lib provides. What was true in the 1960s is still true today. Also, while file clutter when installing isn't uncommon on today's small systems, the use of a libraries is still of help to reduce such.
Microsoft libraries are rather simple structures consisting of a library header followed straight appended object modules.
While it's possible to write a single object for a single program, we all know and appreciate the ability to work on independent modules and clear interfaces - in our own projects as well as for using components others provide. Someone has to assemble them: The Linker.
Unlike often assumed a linkers primary job is ot to create a loadable program, but to join multiple objects into a new combined object, which then can be put to further use - including being executed.
To do so the linker works solemnly with the information given in the objects involved. As described they do not contain any information about the source level structure (functions etc.). Only description of segments, their attributes and noteworthy offsets within to be modified. The objects to be linked are given by command interface. In addition libraries can be given, allowing the linker to search those for objects that fit - simplifying the process of delivering a group of modules quite a lot :))
It will try to resolve whatever can be resolved (combined). The result will always be another - hopefully one with less unresolved references :)
It's important to keep in mind that the linker has no idea what a TEXT, DATA or BSS segment is. They will be processed like any other random segment name. Only their attributes do count.
The Other Kind of Linking
An object module may already be an executable program. That's the case if the OS-loader simply used the same object format as compiler/linker produce (*5). With others may need a converter. MS-DOS is of the later kind. While it's EXE ('MZ') format is not a memory image, but an object format that still needs adjustment before execution, only it's format differs from the format MS defined for objects its compilers create (*6)
In fact, even this part does not really know about C style segment names. It will blindly output all groups as attributed and update the header accordingly.
Those Basics Applied
Now let's look at the issue using above view of object modules and how they may create the observed result (*7):
So imagine the developers writing the C-Lib for their C-compiler structuring the various function for streaming I/O (stdio) into multiple objects but using common header files defining global data structures used by some of them.
Let's say they create two objects 'FOPEN' and 'FREAD' each just consisting of the C-Lib function they are named after (fopen(); fread()). Both are compiled separate, but both include a common global data definition (say via a .H file). One coudl think of a single read/write buffer plus maybe some global flag(s) (*8).
Now lets assume of them only READ will also use the buffer, FOPEN not. Still, if either object gets linked into a new object, the buffer and any other global data defined by that common include will be part of that new object. The linker doesn't care if the data item is used or not. It's defined as part of both and will thus be included.
All as expected.
Of course one can now ask why they didn't split the global definitions into multiple files and include only those really needed for each object - like defining he buffer only in FREAD, not in FOPEN. Sure, would work, but what's the use case? Open without any Read (or Write) in a program is at best a strange fringe case (*9).
On the other hand, we all know, spreading information over evermore include files is a first class source for problems. So, it's rather understandable of MS using a single data definition for all related functions of a package - even if compiled separate.
*A linker does not judge usefulness of the items within an object to be linked (10). It simply processes it as defined.
*1 - I will try to stay with only a rough description necessary to see what happened above, not documenting every detail.
*2 - The well known Intel-Hex format is a subset of the full object format well known for handling ROM data.
*3 - Technically a Microsoft object file can contain more than one object (something Assembler programmers enjoyed to use). Objects are structured as records, ending with a MODEND record, any following records will be interpreted as a (hopefully) new object module.
4 - Other, like all TSOS offspring provide a special unnamed container ('') to free scripts from reserving and assigning storage space - something that can be quite cumbersome in vastly different environments - by using virtual memory to hold compiler output.
*5 - Some OS even offer Link-Loaders which are not only able to load a single object module into memory and execute it, but also perform linking with (standard) libraries at load time. Quite handy to reduce code size on disk as well as making sure OS interface libraries are always up to date. This is related to dynamic linking, but different from dynamic loading at runtime under program control.
*6 - The object format is a carry over from earlier usage. CP/M as well as stand alone products, while the EXE format is a new creation for MS-DOS to support the increased capabilities of the 8086 while not going all the way of directly handling objects. After all, it was to be integrated into the DOS kernel, so compactness was a plus :)
*7 - Note this is just an example how it most likely worked. I have not dissected the library used (not linked anyway), so the exact structure may diverge. More so, there are other configuration that may lead to the same result, due the same mechanics, but using different compartization.
For example, only FOPEN declaring those sections, even though not using, After all, if one tries to split the streaming functions into smaller chunks so only needed ones are included, FOPEN would be always needed ... well or not in case of standard handles. Or in other words, there are countless ways to organize that. For a definite answer one would need to dissect the library used.
*8 - Think error code or active or whatsoever.
*9 - Yes, i know, one could build a 'size' utility that only does
fopen(); fseek(,,SEEK_END);fclose() to get a file size without using any other stream functions, but lets stay serious...
*10 -Having said that, there are optimizing linkers that try to be smarter than the programmer they serve. They only work sufficient reliable within very strict regulated environments. Though, problems they create are, in my experience very hard to detect and correct. There is a reason reliable linkers are very conservative creations, working almost unchanged since the1960s.