Compared to everything else a compiler needs to do this is trivial.
Doing it for types is harder though as far as single-pass compilers are concerned. Both are much easier if the compiler builds a syntax tree (or at least a list of all declarations even if the function bodies are handled at a later pass) ahead of time though.
f() { g(1); }
g(x) double x; { printf("%lf\n", x); }
You have to know the type of g's parameter before you can generate code for f.In this context, it isn’t. The comment you replied to was a response to the statement:
> I've implemented this for calls in a single-pass compiler and it is really simple. You just treat the yet-undeclared call as if it was used correctly and add it to a list of calls to check later.
Single-pass compilation is very pertinent. One tenet of c's design is that it should be compilable in a single pass (which is why it is compilable in a single pass, unlike many other programming languages). This is relevant to us because it allows us to make predictions about the language; in particular, if the addition of some feature would cause c to not be compilable in a single pass, we can predict that the feature will not be added to the language. Notice that I have not made any value judgments. You may disagree about the importance of supporting single-pass compilers, but I am not the person you need to convince of that.
You need to be able to (ideally) treat compilation as a dynamic schedule rather than a static list of passes.
Everything else that can fit AST in RAM can do it relatively easily. Modern C compilers have millions of lines and hundreds of person-years of work in them, so two passes and a lookup table is not a big deal.
It's just not done because the C standard doesn't say it should. Perhaps there is some preprocessor crime that this isn't compatible with, or they just don't want to break that PDP-11 compatibility.