Historically, C compilers are built on the principle of "independent
translation". The compiler proper sees and compiler each translation
unit independently, without any knowledge of any other translation
units.
I was just yesterday reading "History of Programming Languages" from a
conference some years ago. It seesm that the idea was to have someone
(probably someones) write about each language, then get together for
a conference and discuss them. Much of the discussion is also published,
as it was thought that many important ideas would only come out that
way.
As to what should go into the papers: "what they thought when they
did it." Seems to be the guiding thought.
As well as I know it, much of what we think about linkers today
came from early Fortran systems. Fortran I didn't have user
subroutines at all. SUBROUTINE and FUNCTION were added with
Fortran II along with COMMON, but not yet named COMMON.
Computers were still relatively small, drums instead of disks,
and features were limited by what could be done in available
memory (and drum) space. It was up to the programmer to match
argument lists and the types and sizes of data in COMMON.
For this reason, the compiler proper cannot detect any errors that are
caused by any inconsistencies between different translation units. The
language specification explicitly gives the compilers the freedom to
ignore such errors, i.e. it allows them generate invalid code without
issuing any diagnostic messages.
Fortran didn't require the linkers to check. I presume COBOL didn't
either. Early C was on small machines, though they might have already
had Fortran compilers and associated linkers.
However, C had lint. I don't know the history very well, but by the
time I learned C, lint was there and expected to be used to catch
many errors, including those between different files. By removing
the other requirements of compilers, lint could use all its memory
keeping track of things like argument types and external data.
But lint seems not to be used much now.
The only part of the compiler that actually sees the program in its
entirety (in a typical implementation) is called linker. So, linker is
actually in position to detect such errors. But in a typical
implementation by the time the program gets to the linking stage, the
additional information needed for error detection is already lost
irreversibly.
As noted in a discussion in another newsgroup, the STDCALL calling
convention appends onto external names the length of the argument
list (in bytes). That will catch some errors that might otherwise
go unnoticed. As STDCALL has the callee pop the arguments off the
stack, things can go wrong very fast if the argument list length
is wrong. With C and varargs, that normally can't be done.
But also early linkers had smaller limits on the length of external
names, like 6 or 8. There wasn't room to add extra information to
the external names to pass along such information.
As far as I know, it was unix linkers for C that increased the
length of external symbols in the linker.
Hence the responsibility to observe the inter-translation-unit
relationships resides entirely on the user. If you fail to observe them,
you'll typically end up with a program, whose behavior is undefined.
Well, as I noted above, historically lint was supposed to help the
user in C. In Fortran it was entirely on the user. I believe that
DEC added INCLUDE (as an extension) to its Fortran compilers.
That helped some in getting COMMON consistent between different
program units.
It doesn't have to be that way. It is really a quality-of-implementation
issue. Some compiler might decide to go that extra mile and take extra
steps in order to detect such errors. However, most compilers indulge
the liberty provided by the language specification and leave such errors
undiagnosed.
Not that I really know "What they thought when they did it" but
it seems to me that we are stuck with what Fortran could do 50 years
ago, on much smaller computers. That, and that people don't write a
new linker for each new language.
-- glen