6 ms·
> It is no longer possible to call a function that has not been declared. Heh. Surprisingly, I've seen some people claiming that it's impossible for a language
by Joker_vD 3y ago
> It is no longer possible to call a function that has not been declared.
Heh. Surprisingly, I've seen some people claiming that it's impossible for a language without forward declarations to have a one-pass compiler that could handle calls of not-yet-defined functions, yet pre-ANSI C managed to do exactly that, in a pretty obvious (now that you know that it's in fact possible) way.
- uecker 3y agoWhy surprisingly? In pre-ANSI C is it only possible because there was no type safety. In other languages it could work by deriving the type from the call, but this does not work in C.
- jcranmer 3y agoBut prototype-less functions in C don't actually work all that well. In particular, the inferred return type is going to be an int, so if you're trying to return a 64-bit pointer via a prototype-less function, you're going to have a very bad time.
- Joker_vD 3y agoYes, they don't; it would be possible for a compiler to gather info about such call sites into a list and then, when it's finished compiling a compilation unit, to check this list against the now-complete symbol table and fail if some called functions have mismatched definitions or are still undefined... that's apparently was too much work back when C was designed so we have "hope for the best" design instead.
- mfichman 3y agoI think that would by definition be a two-pass compiler.
- Joker_vD 3y agoNo, you emit all the code in one go, then after you've done that you have some residual pieces of data left one the side: the symbol table, and the list of all the calls of (hopefully)-forward-declared functions. At this point you could run the check on that list against the symbol table, no additional codegen or re-reading the source text needed. Granted, you can call that a second pass although that's not that different from emitting a function's epilogue IMHO.
- sltkr 3y agoThere is no need for the second phase. You can record the implicit declaration when you encounter it, and if there is a subsequent declaration, you can check that it's consistent or error out immediately. This is what C compilers already do, in fact, to produce warnings when an implicit declaration doesn't match a later explicit declaration. But this is a best-effort warning only; it doesn't work if there is no declaration because the function is defined in a different translation unit, as I pointed out above.
- mfichman 3y agoUsually, the second pass in a compiler does not re-parse source files. Rather, it operates on another data structure, like an AST, intermediate representation, or the list mentioned in the original comment. At least, that’s my understanding of multi-pass compilation.
- Joker_vD 3y agoWell, this "list mentioned in the original comment" is not an AST or an immediate representation of the program in any reasonable sense just as a symbol table is not. Otherwise, setting the exact values of the text/data/bss size fields in the executable file header's at the end of the compilation would count as the second pass as well which IMO it should not.
- mfichman 3y agoThe difference is that you need to make a complete second iteration (or pass) over the entire list to correctly check all of the callsites after all function type information is collected. The same is not true for symbol table usage in a single-pass C compiler.
- kryptiskt 3y agoThere is no information about the function's prototype in the object file, so the linker can't determine that. The compiler can't either since it's just compiling the compilation unit and has no knowledge of the outside world except what we tell it by prototypes (injected via header files or otherwise), but we aren't doing that here.
- Someone 3y ago> There is no information about the function's prototype in the object file, so the linker can't determine that. That that information isn’t there is an implementation choice. For example, they could have hacked it in like C++ did by mangling names (https://en.wikipedia.org/wiki/Name_mangling https://en.wikipedia.org/wiki/Name_mangling). That probably would have required supporting longer identifiers (IIRC archives limited them to 14 characters), but that’s doable.
- sltkr 3y agoThis doesn't work in C because the function is not necessarily defined in the same translation unit. You'd need to do this at link time instead, which would require completely overhauling the format of object files, dynamic libraries, static libraries so they carry information about the types of functions instead of just the symbol names. It's not an easy fix.
- zozbot234 3y agoThat's what DWARF is for, and similar formats on other platforms. Also Pascal-lineage languages commonly use this kind of embedded type information to provide module interfaces, it works quite well.
- Joker_vD 3y ago> This doesn't work in C because the function is not necessarily defined in the same translation unit. It does work in C ― that's what the include files are for (among other things), after all. So it's possible to be able to forward-declare functions outside the translation unit only (extern-declare?), those inside the translation units don't need to even if the compiler works in a single pass. And those external declaration could still be introduced at the very end of the translation unit and still would count. I dunno, seems like a pretty reasonable idea.
- Karellen 3y agoWell, there is an argument to be had that, for C, a plain unadorned "int" should probably be the native word size for that architecture. On 64-bit systems, "int" would therefore also be 64 bits.
- _kst_ 3y agoI think there are two reasons int has not gone from 32 to 64 bits on 64-bit systems. Part of it is backward compatibility: code written to assume 32-bit int could break. (Arguably such code is badly written, but breaking it would still be inconvenient.) Another part is that C has a limited number of predefined integer types; char, short, int, long, long long (plus unsigned variants and signed char). If char is 8 bits and int is 64 bits, then you can't have both a 16-bit and a 32-bit integer type. Extended integer types (introduced in C99) could address this, but I don't know of any compilers that provide them.
- myrmidon 3y ago> Extended integer types (introduced in C99) could address this, but I don't know of any compilers that provide them. What environment are you working in? Because I don't know a single half-recent compiler that does not provide stdint (uint8_t, ..., int64_t), but I mostly work with GCC/LLVM toolchains.
- retrac 3y agoSome embedded compilers will provide stdint. And if the compiler doesn't, I've found that one of the first headers written for a project ends up being an equivalent to stdint. It's pretty common to develop part of an embedded C program under Linux or similar host environment. Better debuggers, better profiling tools, etc. And uint8_t and friends are particularly important when you're working cross-platform.
- dzaima 3y agoExtended integer types aren't necessarily related to stdint.h - in the vast majority if not every one of those "half-decent compilers" the stdint.h types are just typedefs over plain old char/short/int/long/long long, which are not extended integer types. Extended integer types is a mechanism to allow having types other than those.
- bewaretheirs 3y agoHistorically they worked well enough (on 16-bit and 32-bit machines) because sizeof(pointer) == sizeof(int) == sizeof(general register) on the architectures where C flourished in the pre-ANSI C era. But with the migration to 64-bit machines, typically int stayed put at 32 bits. I guess nobody wanted to introduce a new integral type between short and int; they had enough trouble dealing with code which assumed sizeof(long) == 4. I recall stumbling across a comment where the word "beint32_t" appeared where "belong" would have made sense in context..
- torstenvl 3y agoI love naïve search and replace errors. In the November 1996 version of the Defense Incident-Based Report System codes definitions in DoD Manual 7730.47, the code 092-C2 refers to "shallful" dereliction of duty.
- Joker_vD 3y agoWhy not just make short 32-bits? Yeah, you lose the type for the 16-bit wide integers but x64 doesn't natively support it all too well anyway, unlike the 32- and 64-bit wide integers. And that is what the C integer types are about, right, about being efficiently represented by the underlying hardware, not their exact bitwidth? Right?
- GrumpySloth 3y agoNo. It’s for tightly packing data in data structures. Bitwidth is exactly what’s important here.
- Joker_vD 3y agoWell, that's a shame because bitwidth of standard integer types is quite uncertain. CHAR_BIT can be (and is, on some platforms) 16 or 32, long was never guaranteed to be 64 bits (it's quite often was 32 bits on platforms with 16 bit ints) etc, not to mention that if that is what the standard integer types were for then they'd probably have names like int8/uint8/int16/int32/etc. It's almost as if they were not, in fact, intended for precise control of bitwidths in portable manner...
- NelsonMinar 3y agoOh this makes me feel old. When I first learned C declaring functions was very much an optional feature. A good idea, but optional.
- WalterBright 3y agoIn the C compiler I wrote a couple years ago (ImportC), since it was re-using the D semantic routines, forward referencing functions "just works". In fact, it works so well you don't even notice it working. If the C committee wants to improve C, they should make this an official feature. P.S. Because of the lack of forward referencing, C code tends to be organized as leaves first, and the entry point at the end. This is simply backwards, the entry point should be at the beginning.
- cpeterso 3y agoAre there any compilers our language that parse source files in reverse order? Then you could have a one-pass compiler without forward declarations (though you would need “backward” declarations). :) Someone shared with me their idea for a parallelized parser, where threads would parse different segments of the source file and then stitch their incomplete ASTs together.
- WalterBright 3y ago> Are there any compilers our language that parse source files in reverse order? I like the cut of your jib.
- dataflow 3y agoWell it depends on the calling convention. They're right in the sense that this can't be done for arbitrary calling conventions.