9 ms·
Cproc C Compiler
- unwind 5y agoVery cool! The code looked very readable and clear too, from the little I looked at (mainly expr.c). One minor stylistic observation is that even though it's a C11 compiler, itself written in C11, it holds on to the very Old-Sk00l convention of declaring a function's variables at the top, instead of at point of use. It's at least very consistent, even the statement after the declaration is an assignment to a variable, that variable is still separately declared above. Example (again, from expr.c): static struct expr * mkexpr(enum exprkind k, struct type *t) { struct expr *e; e = xmalloc(sizeof(*e)); Really glad [1] it doesn't cast the return value of the alloc function, at least. :) I would write the body code as { struct expr * const e = xmalloc(sizeof *e); The 'const' there makes it impossible to re-assign e, thus leaking the memory, and is IMO a good thing. NOTE: I realize the separate declarations is a stylistic choice and I'm not being critical, I just think it's fun how enduring some theoretically outdated stylistic choices are in C. [1] https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc https://stackoverflow.com/questions/605845/do-i-cast-the-res...
- andrewchambers 5y agoI honestly don't mind going without C const (compared to C++ where it is more useful).
- bregma 5y ago"const" in C is like underwear. Some people are more comfortable without it but it serves a real and practical purpose and there will be times when you really appreciate its appropriate use.
- veltas 5y agoI used to use const everywhere and then one day I realised it clutters code and it's never caught a bug for me. I don't personally find it worth the extra time. (Of course, I use const with pointers on interfaces for read-only referencing).
- coliveira 5y agoAgreed. I never use const if not required (unfortunately C++ forces you to use it). It is ugly and you're doing work that should be done by the compiler. Moreover, like everything in C++, it doesn't need to be enforced, so it is mostly a moot point.
- adrian_b 5y agoThe convention of declaring all the variables at the top originates in Fortran, so it is indeed very old school. When writing a program, I find it more convenient to declare the variables at the first point of use. Nevertheless, when reading a program written by someone else, I strongly prefer to see all the variable declarations together. It might be that this preference is caused by the fact that I mainly use C when writing programs for resource-constrained embedded environments and in such applications it is valuable to be able to estimate at the first glance to a function which are its memory requirements.
- jes 5y agoI don’t write in C very often anymore. But when I last did, I found myself establishing local scopes inside functions so that variables I introduced went out of scope when I was done with them. I’m also an oddball, so there’s that.
- Someone 5y agoWith non-trivial functions and modern compilers, that gives you an upper bound that may differ significantly from the actual stack usage. There is no requirement for the compiler to keep the memory of all locals distinct. On more powerful CPUs, chances also are a large fraction of the locals never makes it onto the stack, but stays in registers. Having said that, compilers for embedded CPUs used to be fairly bad at optimizations (wouldn’t know of that has changed in the past decades)
- Twisol 5y agoHey, you write `const` on the right too! There must be, like, dozens of us! Is there a style guide recommending this, or have I just managed to dodge every example that puts it on the right?
- Vogtinator 5y agoint * const foo = baz; and int const * bar = baz; have different meaning. foo is a constant pointer (you can't change the address) while bar is a pointer to a constant int (you can't change the value it's pointing at). However, int const * bar; is the same as const int * bar;
- tialaramex 5y agoIn C++ this is called East Const vs West Const with, as someone explains below, East Const (const on the right) being the only one that actually does something consistent and explicable.
- Twisol 5y agoThanks for the nomenclature! That definitely helps find more discussion around the two preferences, like [0]. (FWIW, I'm aware of the semantic differences; I've just never come across that much material referencing it, much less acknowledging the schism.) [0] https://hackingcpp.com/cpp/design/east_vs_west_const.html https://hackingcpp.com/cpp/design/east_vs_west_const.html
- enqk 5y agolook up eastside const https://mariusbancila.ro/blog/2018/11/23/join-the-east-const-revolution/ https://mariusbancila.ro/blog/2018/11/23/join-the-east-const... I’ve been using this style at home in a longtime now, I appreciate how it’s more at home with C and more consistent
- mananaysiempre 5y agoIt may look more consistent, but it misleads the reader about the (inconsistent) way the C declaration syntax works: in int *const p, *q; p is constant but q is not, while in either of const int i, j; int const i, j; both i and j are constant, and int i, const j; is a syntax error. (This is still true but less relevant in C++, which made such an unintuitive mess of the declaration syntax that now the usual advice is to avoid declaring more than one variable per declaration at all.)
- mhh__ 5y agoDon't cast the result of malloc chanting always seems like C programmers getting envious of C++ legalese to me, who cares
- unwind 5y agoI (quite clearly) care quite a lot. :) Of course that doesn't matter, but I do think avoiding pointless casts makes the code better. Fewer tokens to process and read, fewer more or less hidden assumptions on what is going on makes the code more clear and concise. I can't understand why people are in favor of writing more code for no reason, especially casts which are sometimes mentioned as one of those things that make C annoying/dangerous/hard/bad. If it's like "I feel it makes it more clear that the pointer is being converted from scary void pointer to cozy foo pointer", then I would argue that's exactly what a line of code like foo * const my_foo = malloc(sizeof *my_foo); is saying, adding a cast on the right hand side of the assignment doesn't make that more clear. To me, it makes the code much more anxious-feeling, as if the programmer doesn't know what will happen without it, and just adds one "for good measure", which I consider a code smell.
- yakubin 5y agoOne of the things that make C casts annoying/dangerous/hard/bad is that they are often implicit. If I didn't know malloc, then I wouldn't know that in your line of code there is a cast. And casts between pointers of different types can sometimes cause UB... There is a cast, whether you write it or not; it's just implicit, and that's bug-prone.
- unwind 5y agoOkay, I see what you mean. One obvious point here is of course that if you don't know malloc() then you don't know C. Considering C is a pretty small language, and that malloc() isn't exactly at the furthest, most dusty and least-trodden part of its standard library, I think this is basic knowledge for anyone writing C. That said, I'm not sure I agree since implicit pointer conversions are not in general allowed, except to/from void * and then there will always be a properly typed thing close by (like the left-hand side in this example). Can you provide an example of where an implicit pointer conversion creates danger?
- veltas 5y agoIt's not just an Old-Sk00l convention, but the main convention for C programming, alongside avoiding C++ comments. It's cultural and I think C programmers are now trained to worry when you don't write code like this because they associate it with the C code that newer C++ programmers write when they're forced to write some C code or try to convert, inevitably with a lot of unnecessary paranoid cruft and C++-inspired misconceptions. So in the purely practical sense of trying to write code that doesn't 'smell' or send off the wrong signals it's worth sticking to the conventions. And there's no problem with C++ having its own conventions as well. They are two different languages with diverged syntax and semantics. Unlike the culture of C++ that has gotten a lot of benefits from the progression of the standard, in C the standard is not that interesting, does not change as much, and unfortunately compiler support was extremely slow (especially with C99, the first revision following the initial standard). In fact, many C99 features became optional in C11, and the attitude of most C programmers has been "use what you need" with new features, perhaps to make it easier to port to the exotic and outdated compilers that are more frequently found in the major application of most modern C code: embedded software.
- flohofwoe 5y agoHmm, I don't agree here. Declaring variables close to where they are used is objectively a good practice. "Pure" C89 isn't really relevant since more than two decades, teaching new C programmers such old habits would be actively harmful (sticking to old and outdated C89 features is actually a problem that many C tutorials still have, along with introducing malloc too early and for all the wrong reasons).
- veltas 5y agoI don't see how it is "actively harmful" to use this style. I'll defend the style and say I think either is fine from an objective standpoint, the advantage of declaring near the top and leaving uninitialised is that it means all assignments/values are separate and slightly clearer in code, type annotations are clutter. But likewise it's not that much clutter and you can get used to it (and it's conventional in many other languages). Only in very large functions does it significantly 'hide' the declarations and the type information from where they're used, and so arguably that encourages you to not write very large functions with lots of different variables! But don't pick on this point and say "the limitation becomes a 'feature'", I'm just saying with most good code this doesn't matter, not that it's 'good' that it's harder to write long functions, yes that is a limitation, I just don't think it's a significant one for good code. So I don't see how either is objectively superior. I recommend this style because since there is not a huge difference between the two, I prefer what is conventional.
- outsomnia 5y agoProbably makes sense to integrate a preprocessor before doing anything else, even somebody else's to get started. Because that is pretty offputting for casually trying it on existing codebase to see what happens.
- andrewchambers 5y agoThere is one, see pp.c.
- turminal 5y agoNote that cproc uses another small but very interesting project as the backend, QBE[1]. It currently has support for x86-64 and arm64, and a risc-v port is in the making. [1]: http://c9x.me/compile/ http://c9x.me/compile/
- xvilka 5y agoWould be nice to support it in Meson. Currently, there is work-in-progress TinyCC support [1]. But the work is hindered by the lack of recent TCC releases - 0.9.27 is too old to be supported. [1] https://github.com/mesonbuild/meson/pull/8248 https://github.com/mesonbuild/meson/pull/8248
- arthur2e5 5y agoCproc+QBE doesn’t seem to be trying to do the linker job itself, so presumably library order (apparently a main point in that PR) does not need to be changed?
- potus_kushner 5y agothe approach of meson to hardcode the properties (including the name of the main executable!) of each and every compiler in the universe seems pretty short-sighted if not outright dumb and isn't quite how the designers of UNIX, the C language and compiler toolchains envisioned a build system to work. if meson was properly designed, a user could just run `CC=whatevercc meson ...` and it would work with basically each and every single compiler available on *NIX and every POSIX compatible environment, including gcc, clang, tcc, cproc and even within cygwin. the only compiler i know that works differently is MSVC and it would be easier to just provide a wrapper that transforms POSIX-compatible compile command lines into ones that MSVC understands. (this reminds me of the head-desk approach CMake takes to adding library dependencies: each library has a hardcoded configuration file, which can go as far as scanning your /usr/lib for libfoo.so rather than just assuming "-lfoo works, and if it doesn't, use $PKG_CONFIG --libs foo"...) the author of cproc actually filed an issue report in 2019 pointing out this very fallacy, which is still open: https://github.com/mesonbuild/meson/issues/5406 https://github.com/mesonbuild/meson/issues/5406
- celest1 5y agoSorry for the stupid question, but what is the benefit of building a C compiler nowadays, and what is interesting about this particular one?
- andrewchambers 5y agoThe benefit is mainly that it is interesting and easy to learn/experiment because it has a very small and tidy implementation.. Just an example, building cproc from source takes a few seconds - building gcc from source takes like 15 minutes at least.
- jjice 5y ago> very small and tidy implementation Especially impressive for implementing the C11 spec and some GNU extensions.
- veltas 5y agoYour C compiler is probably a very large C++ program written over many years and with millions of dollars worth of time poured into it. The C compiler I use takes up hundreds of megabytes of space in a minimal build. So in my opinion it's interesting if someone can write something original with 80% of the useful functionality on their own, or achieve it in a different way. And their code might be more approachable/educational to people than the behemoth.