Even when linking, don't call "cc", but "$(CC)". You're relying on the implicit rule to compile your .c to .o which will use $(CC). $(CC) could be some ARM cross-compiler supplied by a distro. When you link using "cc", you end up using the build-machine's native compiler.
Write the build system like this is an awesome program that major distros will be eager to pick up, and make it easy for the package maintainer. :)
Speaking of CFLAGS, only set that conditionally; don't clobber it:
CFLAGS ?= -O2 # Only if CFLAGS is not specified externally, use this default
for things that your program needs in order to build right, put that in other variables of your own: DIALECT_CFLAGS := -std=c11
CFLAGS += $(DIALECT_CFLAGS) # integrate external CFLAGS with our own.
Same deal with LDFLAGS. Both CFLAGS and LDFLAGS can include important things that cause a bad build if you mess with them.I feel like one of the best ways to acquire this wisdom is to post a project with lots of mistakes and let people tear it apart.
Most of what I know about 'make' I learned from this book.
As far as knowing GNU Make, I recommend just reading the manual from beginning to end, perhaps twice.
int main(int argc, char **argv) {
...
Vector *tokens = tokenize(path, true);
Program *prog = parse(tokens);
sema(prog);
gen_ir(prog);
if (dump_ir1)
dump_ir(prog->funcs);
optimize(prog);
liveness(prog);
alloc_regs(prog);
if (dump_ir2)
dump_ir(prog->funcs);
gen_x86(prog);
return 0;
}
This is wonderful.https://www.reddit.com/r/ProgrammingLanguages/comments/89n3w...
In many compilers, including some linked on that thread, this clean structure gets lost.
For functions that have prototypes, it is okay, but when they do not, you're losing type checking (yes, even on the number of arguments).
The following program compiles with no diagnostics for me with -W -Wall -ansi -pedantic, with GCC 7.3 on Ubuntu 18:
int func()
{
return 0;
}
int main()
{
func(3);
return 0;
}
-std=c11 (as you're using) makes no difference.The same is not true of C++: func() in C++ is a prototype definition. C++ supports (void) for compatibility with C, but even in nonsensical contexts: class::class(void);
> An identifier list declares only the identifiers of the parameters of the function. An empty list in a function declarator that is part of a definition of that function specifies that the function has no parameters. The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied.
So, my interpretation is that the following two function definitions define the same function of the exact same type
void func() {}
void func(void) {}
although the following two function declarations declare two functions of different types void func();
void func(void);Interesting find there. The wording is also in the C99 draft; it is not new.
It is in fact saying that the empty list in a definition is a special case and does declare that the function takes no parameters. To "specify" here can be understood to mean as inserting information about type into the declaration scope; that which a declaration does.
So that's a bit of a bug in GCC there; it should be treating this the same as (void) and therefore diagnosing that way. If not by default, then at least when -pedantic is applied. But nope:
$ gcc -Wall -W -std=c11 -pedantic proto.c
$"The empty list in a function declarator that is not part of a definition of that function specifies that no information about the number or types of the parameters is supplied."
i.e. the most important is the end of that sentence, so GCC behavior should be right.
Formally, this is not introduced into the scope as type information according to the language spec, but that's no reason not to warn about it informally.
I will also make a suggestion to use precedence climbing instead of plain recursive descent for the parser; it makes the parser even simpler and table-driven, which is important with a language like C that has many precedence levels.
https://lobste.rs/s/nognrl/creating_language_using_only_asse...
The last time I investigated the linux kernel had so many gcc-isms that it was probably true that if you could compile the linux kernel, you could probably compile any program targeted to gcc.
https://pdos.csail.mit.edu/6.828/2012/xv6.html
For bootstrapping, the overall consensus is you make enough C to compile, without optimizations, early version of GCC which compiles itself from there until you reach current version. Several folks are trying to compile TCC instead to leverage it and dwheeler's work.
>I know that people find the policy odd, but this is actually a reasonable design choice for short-lived programs such as compilers.
I'm strongly disagree at this point. Memory management is important even for short-lived programs. It would bring burden to the OS if you invoke this kind of "short-lived, memory-management-free" programs multiple times.
>This policy greatly simplifies code and also eliminates use-after-free bugs entirely
Not facing it is definitely not a good way to solve a problem. It's a bad attitude as a programmer be honestly.
I disagree with your characterisation of this as bad practice and "not facing it", it is an informed decision rather than ignorance, and brings considerable benefits. With a C complier, some reasonable estimates can be made of the input data length/complexity and hence allocation sizes, and having poor performance if someone tries to feed in a million line file is perfectly acceptable, especially in a complier designed to be simple like the OP.
I think recognising the special set of circumstances that justify making an unusual tradeoff that wouldn't normally be acceptable is actually a mark of maturity as a programmer, not a bad attitude. The phrase "Don't let perfect be the enemy of good" comes to mind.
Finally, why would it bring burden to the OS if you invoke this multiple times sequentially? malloc/free is implemented in the C library, not in the kernel, it's a mechanism for sharing bigger chunks fetched/given-back from/to the kernel with sbrk(). The kernel would just reclaim the pages on program exit, as it would have to anyway, as it cannot rely on programs being well behaved enough to call free() (which rarely would give the memory back to the kernel immediately anyway). Since the kernel uses virtual memory, its not actually moving the contents about, just manipulating page tables, so it should be quite fast, and zeroing memory is not as time consuming as you think due to Zero Fill On Demand (ZFOD), and hence the kernel does about the same amount of work regardless of if the program called free() or not.
Sure it's using more peak memory than it could be, but I reckon compared to say, a web browser, it's still a very small fraction of the total.
All the memory is freed when the process exits. Why does it matter if you run the program multiple times?
[0] https://gitlab.com/sifoo/snigl/blob/master/src/snigl/pool.h
http://lists.gnu.org/archive/html/coreutils/2014-08/msg00012...
Source: https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#...