11

I have been programming in c/c++ for a while now, and I have never once used a makefile. I know that it is supposed to be useful when you are dealing with a large project, but I have always been able to compile, build and execute my program using only

gcc main.c -o run

and if I am working with an external library (gtk+, for example) I make a simple bash script that looks like:

#!/bin/bash
echo compiling....
gcc main.c `pkg-config --cflags --libs gtk+-3.0` -o run

So what I'm asking is do I need to learn how to make a makefile?

2
  • 2
    You should take the habit to compile with gcc -Wall -Wextra -g. Then use the gdb debugger. Once you have no bugs, ask the compiler to optimize with -O Feb 18, 2015 at 8:48
  • You can always use a general-purpose high-level language (Python, Haskell, etc) instead of using an archaic special-purpose language (Makefile). prog21.dadgum.com/49.html Feb 25, 2015 at 0:16

5 Answers 5

24

First, a Makefile for make is really useful when you build a program from several translation units (i.e. several *.c or *.cc files which are #include-ing some other header files) which are linked together (it is not very useful for a single source file tiny program). It organizes the various compilation steps of the translation units (and avoid running useless compilations again). See also this answer to a related question.

During the debugging phase, compile with all warnings and debug info (e.g. gcc -Wall -Wextra -g for C code compiled by GCC...) and learn how to use your debugger, e.g. GDB and memory leak detectors like valgrind.... Be scared of undefined behavior. With a recent GCC 5 compiler (at end of 2015), you may also want to use (occasionally) various -fsanitize= debugging options. Have some good testing procedures. For production and benchmarking, ask for compiler optimizations (e.g. add -march=native -O2 to your GCC compiler flags). For C++11 code, replace gcc with g++ -std=c++11; in your GNU Makefile, use CC & CFLAGS variables for C and CXX & CXXFLAGS for C++.

As soon as you are writing some not-tiny program in C or C++ (e.g. more than ten thousand lines of source code), you'll want to organize it in several translation units (at least to avoiding very long build times while working on it, and preferably to group together related code or features). Notice that with C++ the usual included header files are themselves quite big, so having tiny translation units slows the overall build process.

The size, organization, name, and purpose of a translation unit (i.e. of a foo.c or bar.cc file) is a matter of habits and conventional. Some people prefer having many tiny files (of a few dozen lines). I like having source files of several thousand lines. The recent version of GCC compiler, and the recent Linux kernel have dozens of human-written C or C++ source files bigger than ten thousand lines. Both are quite large software (many millions of source lines)

Then, you could build such a program using some other builder (like omake or scons, or ninja etc...), or even using a shell script (BTW, the GNU make distribution contains a shell script to build it on systems without any make yet!).

But yes, I believe you should learn GNU make (and you may even want to take advantage of recent GNU make features, e.g. Guile scriptability). Here is an example of Makefile, and another one.

Notice that for historical reasons the tab character is significant for make, so you need a specific mode in your editor.

In many cases, e.g. for configuration reasons, the Makefile is generated (e.g. with autoconf or cmake).

In several cases some *.c files or some header *.h included by them are generated by other programs (like SWIG, GNU bison, etc...)

You should look into the source code of several free software programs (e.g. GNU make itself! See also sourceforge, github, etc... to find some) and try building them. That would teach you a lot.

So you don't need make (or some other builder) yet, just because your programs are very tiny. As soon as they will grow, you'll need some building process. Notice that large programs (a web browser, an optimizing compiler, an OS kernel) have often many millions lines of source code organized in at least hundreds of translation units, and often have some generated C/C++ code (by some script in awk, python, guile, etc..., or a specialized program itself coded in C++, or an external generator like ANTLR or gperf) for application-specific metaprogramming or aspect-oriented programming purposes.

PS. Some other programming languages (Ocaml, Haskell, Go, SML, ...) know about modules and have very different builders.

10
  • 4
    I do have several translation units of about 10Klines. But it is a matter of opinion and habits. I strongly dislike Java because it almost forces to have a lot of small source files (only a few hundred lines each). Feb 18, 2015 at 6:40
  • 1
    Rather than being so vague as to say "translation units with x lines," you should consider saying "translation units should represent a logical unit of functionality - related functions and classes that encompass a feature." Translation units should be as big as they need to be and no larger. Putting a number on it is misleading.
    – greyfade
    Feb 18, 2015 at 7:04
  • 1
    I agree, but that is also subjective and matter of taste and of opinion. And many software have a misc.c or utilities.cc file grouping unrelated functions Feb 18, 2015 at 7:05
  • 3
    Does the GCC compiler counts as "real world of software programming" (for me, yes!)? It has several dozen of translation units of more than ten thousand lines. Feb 19, 2015 at 5:38
  • 2
    The Linux 3.19 kernel also has two dozens of *.c files of more than ten thousand lines. And it also looks like real world software programming. Feb 19, 2015 at 5:46
5

If you use a modern IDE(*) you will probably never have to look at a makefile in your life, even though the IDE generally will generate one for you on the fly.

Makefiles were a needed skill when I started out 30+ years ago. I rarely see them nowadays (and, if you do need one, there are makefile generators).

In short, don't learn a skill until you need it, or can anticipate needing it. Use the timer saved by not learning makefile to learn something else which will make you a better developer.


(*) and if you are not, you are missing out on a ton of helpful features, like code completion (often with pop up documentation of the completions), code navigation, integrated debugger, integrated unit test, GUI designers, refactoring, et al

4
  • 1
    I don't know much free software on Linux which requires an IDE to be compiled. All the major free software I have compiled at least once (Qt, GCC, Linux kernel, KDE, Gtk, Gnome, ...) use some builder; none of them require an IDE. And many of them (GCC; kernel) would be very painful to build with an IDE... Feb 18, 2015 at 18:30
  • 2
    That is a very good point, but free s/w has different requirements (and the world is bigger than free software on Linux). It needs to be buildable by everyone, so you can"t expect/force them to have, for instance, Eclipse installed. But for non-FOSS projects, once (please don't take this wrongly) would have ot be a foll not to use an IDE. I have been a professional freelance s/w developer for 35 years now, with 20+ companies, in 15+ countries, on three continents, and it has been 20+ years since I worked a project where we did not use some form of IDE.
    – Mawg
    Feb 19, 2015 at 6:49
  • YMMV, of course, but, as the OP sounds like he is at the learning stage, an IDE is highly recommended. Your point, however, is valid for FOSS (but I would argue against it being widely valid, for personal or business projects).
    – Mawg
    Feb 19, 2015 at 6:51
  • At the learning stage I would not recommend an IDE to automate build and deployment. Their are fine for editing, but new developers need to understand how their code is converted to something executable, and how it runs. For C/C++, that means understanding src => object => executable. Nov 21, 2015 at 21:21
4

Do you have a specific need for make?

Tools like make are all about automation - being able to repeat a process flawlessly, time after time, as efficiently as possible. Check out this thread on github regarding a JavaScript development framework and the potential choice of using make as the build tool:

https://github.com/ericelliott/cloverfield/issues/2#issuecomment-68798159

Whatever tool you choose, you should use it because you have a particular purpose in mind, not because you "should be doing it". You'll see in the github thread that the tooling discussion centers on identifying and meeting specific criteria that the developers want in a task runner.

Other answers in the thread suggest reasons to use make with C programming. Do those benefits sound like things you need?

If you choose to use make, I agree with SHiNKiROU, learn make yourself before you use tools to build Makefiles. I learned a little bit of make during the github discussion, and it was very enlightening.

2

Checkout the section on make in The Art of Unix Programming. It explains the motivation behind make and why we have tools that generate Makefiles.

To summarize, the creator of make had a time when he wasted time debugging a working program because he forgot to manually recompile an object code. Make is an automated tool that determines what files need to be recompiled based on timestamps and the dependencies between files. Make is crucial for multi-file projects. You need to learn how to write Makefile in order to declare the dependencies between the files. I recommend you to write medium-sized, manageable Makefiles before learning how to use tools that generate Makefiles.

0

Don't forget about GCC option "-MMD" and the line "-include *.d" at the end of your Makefiles. This option tells GCC to also generate an additional .d file for each compilation unit. This file contains a single line of text, in Make compatible syntax. It specifies additional dependencies for a make target based on any local includes in the source. The reason for this is because .c files can include other files. Without this, make generally won't know what other files (like header files) are depended on by a given .c file. Therefore, if you update a header file in your project, make will not automatically rebuild the .c files that include it. This can lead to strange bugs during development, especially if a function prototype changes. This solves that problem.

I've been getting into the habit of using Makefiles more and more in my projects.

It automates parameters like CFLAGS and LDFLAGS, which, in my experience, on most non-trivial programs, quickly balloon. Maybe it's just me who likes to use a lot of fancy compiler options and tweeks. "-fno-zero-initialized-in-bss" anyone? I'm sure the embedded devs can relate to working around compiler magic.

Make and other build system tools allow for platform specific configuration that can be more painful to do in source code. Some people might argue about this and how to do it, but the options are there and you can pass pre-processors definitions to your source code based on the selected configuration.

Since your Makefiles describe the build dependency graph of your project you can easily parallelize the build. You just have to use the -j option. Keep in mind, however, it is easy to incorrectly specify these dependencies in your Makefile and break your build so I encourage you to test this whenever you change your Makefiles and write your Makefiles with build dependency orders in mind. Do not just write multiple tasks in a recipes and assume they will always execute in the same order.

Make will detect what has changed and only rebuild what is needed based on the dependency graph. This becomes more important when you have a large project that takes a long time to build and you want to test small changes to a specific subsystem. In the project that I am working on now, it is getting to the size where build times are becomes slower than I'd like for quickly rebuilding everything. This feature will allow me to build everything once and then just rebuild what I change as I modify source files.

Portability. I have been developing for Linux, Windows, and MacOS. MinGW contains a port of the make program for Windows. Put that in the path and run your Makefiles anywhere.

When I first started, I was writing build scripts too. One issue that I ran into is that build scripts do not, by default, echo your commands. This makes it harder to validate that it is running what you expect (such as the correct CFLAGS) and see what command caused what errors/warnings/output. Also, if a build command fails, you will probably want your script to stop execution. This means that you need to include " || exit 1" or check the exit code of all commands with if statements in bash. There are ways to set bash to echo commands and die on a command that returned a non-zero exit code. Eventually, however, it starts to feel like you are recreating make in a script.

For better or worse, make is a standard tool. Using it in your project will allow people familiar with the prevailing workflow to more easily download and build your software without having to read your manual or worse, try to to read through your custom build system to figure out how to use it to build for their own purposes. I don't like to tell people how to do their stuff and ultimately it is your decision. There are plenty of things that I disagree with in the current *nix infrastructure. That being said, with such flexibility in your naming and build system, I would suggest that you don't deviate too much from standard convention if not needed. I have been on the other side of trying to build software, maybe trying to do static builds for initramfs or something, and fighting with someone software that is using some custom build system that doesn't accept the custom flags or doesn't give me standard options. Following standard convention and tools will also allow people to more easily understand and contribute to your code.

To conclude, it depends on what works for you and your projects. Build scripts used to be the way to build software. Is all the stuff I mentioned so important or needed? Could it be implemented in a script? Probably not always and yes it could. I think once you learn how to write Makefiles and have a standard Makefile template that works well, reinventing that wheel in a build script becomes less appealing.

1
  • "you need to include " || exit 1" or check the exit code ... set bash to die" -- putting set -e near top of script does wonders for reliability. If a command in the middle does something unexpected, we bail at once, rather than soldiering on with unpredictable side effects.
    – J_H
    May 23, 2023 at 20:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.