4 ms·
> Suddenly I didn't need prototypes anymore Under some very old C standards, maybe. But C99 requires that you at least declare a function before calling it.
by duskwuff 3y ago
> Suddenly I didn't need prototypes anymore
Under some very old C standards, maybe. But C99 requires that you at least declare a function before calling it.
- fuzztester 3y agoPascal had a forward declaration from way back, even before Object Pascal: https://en.m.wikipedia.org/wiki/Forward_declaration https://en.m.wikipedia.org/wiki/Forward_declaration https://en.m.wikipedia.org/wiki/Pascal_(programming_language) https://en.m.wikipedia.org/wiki/Pascal_(programming_language... https://en.m.wikipedia.org/wiki/Object_Pascal https://en.m.wikipedia.org/wiki/Object_Pascal https://www.thedelphigeek.com/2017/03/forward-record-declaration.html?m=1 https://www.thedelphigeek.com/2017/03/forward-record-declara...
- billfruit 3y agoBut why does it have to be that way though, can't the compiler scan the rest of the code/files and see if a defenition/declaration is present somewhere? There is no such requirement of a declaration before first call in Java for example?
- fuzztester 3y agoGood question.
- rezonant 3y agoIt's because the C compiler was designed to be single pass, so as to be speedy and require less memory on the systems of it's time.
- billfruit 3y agoIs that a requirement that is relevant presently? I would say not so much. Do modern C compilers also make only a single pass?
- hulitu 3y agoYes. Gcc throws errors like hell with c files which have the definitions after functions.
- rezonant 3y agoModern compilers still have to follow the rules of the language.
- billfruit 3y agoYes, but is the constraint that compiler shall make only a single pass, a rule of the language?
- trealira 3y agoPrior to C99 (i.e., in C90), there was a rule that any undeclared identifier id that was followed by a function call parenthesis would be treated as though there were an implicit declaration in the scope in which it was used like this: extern int id(); The empty parentheses meant it could take any number of arguments, since it was declared in the traditional style, where the argument types weren't specified first. This implicit declaration meant that if it was later defined in the same file as returning a type other than int, the compiler wasn't permitted to go back up in the file and treat the function call as though it returned a type other than int. This requirement was removed in C99, but in practice, compilers kept doing it for backwards compatibility, even when not invoked in C90 mode.
- fuzztester 3y agoLike early Pascal compilers, or maybe all of them. Don't know about the latter case (all).
- dheera 3y agoI suppose you could just create a ".d" file standard that doesn't have that requirement but processes into a ".c" file that has the necessary prototypes. You could probably also auto-insert all the #if'n'def nonsense automatically and skip it in the ".d" files. Kind of like how the JavaScript dudes all use this ".ts" stuff now for convenience that processes into ".js"
- foodevl 3y agoThe Arduino build system does this (preprocesses your source code to pull out prototypes and put them at the top). To make things easier for beginners.
- phatskat 3y agoJust to be a little picky, but if you want “convenient” then just stick with pure JS - it’s overly forgiving and simple. TypeScript is lovely and I much prefer it over JS, but having to type _everything_ is far from convenient imo
- vinceguidry 3y agoIsn't Typescript gradually typed?
- phatskat 3y agoIt is! I’ve been working in an environment that essentially requires us to type as we code (rules against using the “any” and “unknown” types) that in just used to them being enforced now lol. So I suppose my point is moot, as the tediousness isn’t forced by the language necessarily.
- rezonant 3y agoIt's also considered by many to be an antipattern to add a type that is otherwise inferred. For a trivial example, let x: number = 6; You should instead allow the compiler to infer where it can and do let x = 6; Inference doesn't work across function calls like it does (did?) in Flow, which is a good thing, so those always need to be typed. If you wanted the type of x to be 6 instead of number, you would use let x = 6 as const;
- Gibbon1 3y agoWhat I understand is the preprocessor causes issues where you could do that maybe 99.9% of the time, especially with well written code. But it'd fail that 0.1% of the time. You do have analyses like ctags. In theory the compiler could use ctags to find definitions in source code. My experience is ctags can get confused. It's possible in a set of files to have multiple implementations of a function and ctags can't tell which one is used. And I see cases where it can't find definitions. Personally I'd be happy if the compiler tried to find a missing definition using ctags and issued a warning. I have wondered if adding funct, public, private keywords to the language might allow the compiler to reliably find function and stuct definitions in source files.
- reactordev 3y agoThe fact that I'm calling a function, it must exist, otherwise the compiler will throw an error ("undefined reference to function"). So forward declarations is just needless typing.
- saagarjha 3y agoThe compiler needs to see it to know how to call it.
- reactordev 3y agoIn which case, it needs to keep parsing the include tree until it finds it. I know why it exists. I’m just not happy about duplicating code. Other compilers are smarter than this.
- duskwuff 3y agoIf you're using a modern IDE, clangd can be configured to automatically insert #include statements (--header-insertion=iwyu).
- saagarjha 3y agoThe thing I hate about this is that it really has no idea what a “public” header is. If I have it on I’ll use something like uintptr_t and get a random include for <__bits/std/uintptr.h> or something that I don’t want. I assume there’s like some manual auditing one can do to go “don’t peek through stdint.h” but either nobody does this reliably or it doesn’t work.
- duskwuff 3y ago> The fact that I'm calling a function, it must exist That's not necessarily true. It's possible that the symbol is a function pointer; calling a function pointer requires slightly different code generation. Compare the generated code between: void fn(void); void call_fn(void) { fn(); } and void (*fn_ptr)(void); void call_fn_ptr(void) { fn_ptr(); } In practice, it's probably a function, and that's what the compiler assumes if there's no declaration to go off of. But we all know what happens when you make assumptions.
- jart 3y ago> C99 requires that you at least declare a function before calling it Where is that written? Please quote the standard. My reading of ISO/IEC 9899:TC3 is that Foreword says (paraphrasing) "hey, we removed implicit int and implicit prototypes from the standard". So they simply stopped specifying it, but as far as I can tell, the standard says nothing about forbidding them. From my point of view, that means compilers are free to still implement implicit types as a compiler extension to the language, and everyone does, because that extension is needed in order to support the older c89 standard. It would only impact people who want to be able to claim their codebase is c99 standard compliant, because you can't do that if you depend on compiler extensions.
- kevin_thibedeau 3y agoPrototypes are required in C23 as part of the expungement of K&R syntax. This also brings harmonization with C++ where an empty parameter list is implicitly void.
- duskwuff 3y ago> Where is that written? ISO/IEC 9899:TC3 [1] §6.5.1 ¶2: "An identifier is a primary expression, provided it has been declared as designating an object (in which case it is an lvalue) or a function (in which case it is a function designator)." There's even a footnote to underscore this point: "79) Thus, an undeclared identifier is a violation of the syntax." [1]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf