The source of the original article is unknown.
0) Introduction
~~~~~~~~~~~~~~~
This article will first explain why you need to separate your C source code into several reasonable independent files, and when and how to split the files. Then it will tell you how GNU make automates your compilation and connection steps. For users of other make tools, the content in this article is still very useful, although you need to make appropriate adjustments when using other similar tools. If you have doubts about your own programming tools, you can try it, but read the user manual first.
1) Multi-File Project
~~~~~~~~~~~~~~~~~~~~~~
1.1 why use them?
First, what are the benefits of Multi-file projects?
They seem to make things extremely complicated. The header file and the extern Declaration are required. If you need to find a file, you need to search for more files.
However, we have a strong reason to break down a project into small pieces. When you change a line of code, the compiler needs to recompile all the code to generate a new executable file. However, if your project is separated into several small files, when you change one of the files, the target file (object files) of other source files already exists, so there is no reason to recompile them. All you need to do is reproduce the modified file and reconnect all the target files. In large projects, this means that the recompilation process is shortened from a long time (several minutes to several hours) to a dozen or even a few seconds.
By breaking a project into multiple small files through basic planning, you can easily find a piece of code. It is very simple. You can break down your code into different files based on the function of the Code. When you look at a piece of code, you can find it in that file accurately.
Generating a package (Library) from many target files is much better than generating a single large target file. Of course, whether this is really an advantage is determined by the system you use. However, when a program is connected to a program using gcc/LD (a gnu c compilation/connector), it tries not to connect unused parts during the connection process. However, it can only exclude a complete target file from the package at a time. Therefore, if you refer to any symbol in a target file in a package, the target file will be connected. If a package is fully decomposed, the executable file obtained after connection is much smaller than the file obtained by connecting to the package consisting of a large target file.
And because your program is very modular and the sharing between files is minimized, there are many advantages-it is easy to track bugs, these modules can often be used in other projects, while others can easily understand what your code is. Of course, there are many other benefits ......
1.2 When to break down your project
Obviously, it is unreasonable to break everything down. Simple programs like "hello World" cannot be divided, because there is nothing to be divided. It is meaningless to break down the small program used for testing. But in general, I will take a project when it facilitates layout, development, and ease of operation. In most cases, this applies. (The so-called "world, hello!" is both 'Hello world'. It is just an example program used to introduce a programming language. It will display a line of 'Hello world' on the screen '. Is the simplest program .)
If you need to develop a fairly large project, Before you begin, consider how you will implement it and generate several files (with the appropriate name) to put your code. Of course, you can create new files during the development of your project, but if you do so, it means that you may have changed your original idea, you should think about whether you need to adjust the overall structure.
You can also use the above skills for medium-sized projects, but you can also start to enter your code, when your codes are too many to be difficult to manage, you can break them into different files. But in my experience, a general solution is formed in my mind at the beginning, and I try to follow it as much as possible, or modify it as the program needs during development, it makes development easier.
1.3 How to break down projects
First of all, this is my personal opinion. You can (maybe you will ?) In other ways. This will touch the issue of encoding style, and we have never stopped the debate on this issue. Here I just give my favorite practices (and also give the reasons for doing so ):
I) do not use a header file to point to multiple source code files (except for the header file of the package ). Defining a source code file with a header is more effective and easier to search. Otherwise, you must recompile several files to change the structure of a source file (and its header file.
Ii) If possible, you can use more than one header file to point to the same source code file. Sometimes it is very useful to separate function prototypes and type definitions that cannot be publicly called from their C source files. Use a header file to hold public symbols. Using another private symbol means that if you change the internal structure of the source code file, you can just recompile it without re-compiling other source files that use its public header files.
Iii) do not repeatedly define information in multiple header files. If necessary, # include the other in one header file, but do not enter the same header twice. The reason is that if you change this information later, you only need to change it once without searching and changing another duplicate information.
Iv) in each source code file, # include all header files that declare the characters in the source code file. In this way, the conflicting declarations made to some functions in the source and header files can be easily discovered by the compiler.
1.4 Comments on Common Errors
A) identifier conflicts in the source code file: in C, the missing state of variables and functions is public. Therefore, any C source code file can reference the universal (global) function and common variables in other source code files, even if this file does not have the declaration or prototype of the variable or function. Therefore, you must ensure that the same symbol name cannot be used in different two files; otherwise, there will be a connection error or a warning during compilation.
One way to avoid this error is to add a prefix that is related to the source file to the public symbol. For example, all functions in GFX. C are prefixed with "GFX _". If you are very careful about breaking down your program, using meaningful function names, and not over-using common variables, of course this is not a problem at all.
To prevent a symbol from being seen outside the defined source file, you can add the keyword "static" before its definition ". This is useful for simple functions that are used only within one file, and are not used by other files.
B) Multiple defined symbols: the header file will be replaced by the # include position in your source file by words. Therefore, if the header file is # include to more than one source file, all definitions in this header file will appear in each relevant source file. This will enable the symbols in them to be defined more than once, resulting in a connection error (see above ).
Solution: do not define variables in the header file. You only need to declare them in the header file and then define them (once) in the appropriate C source code file (which should # include the header file ). Definition and declaration are confusing for beginners. The purpose of the Declaration is to tell the compiler that the declared symbol should exist and have a specified type. However, it does not make the compiler allocate storage space. The definition requires the compiler to allocate storage space. As a declaration rather than a definition, put the keyword "extern" before the declaration ".
For example, we have a variable called counter. To make it public, we define it in a source code program (only in one): "int counter; ", and then declare it in the relevant header file:" extern int counter ;".
The function prototype implies the meaning of extern, so you don't need to worry about this problem.
C) repeated definitions, repeated declarations, and conflict types:
Consider if # include two files a in a C source code file. H and B. h, and. h # include B. H (the reason is B. file H defines. h), what will happen? At this time, the C source code file # include B. H twice. Therefore, # define in B. H occurs twice, and each declaration occurs twice. In theory, because they are completely identical copies, there should be no problems, but in practice, this is not in line with the c syntax and may cause errors during compilation, or at least warning.
The solution is to ensure that each header file is included only once in any source code file. We generally use a pre-processor to achieve this goal. When we enter every header file, we # define a huge set command for this header file. The subject of this header file is used only when this collection instruction is not defined. In practical application, we just need to simply put the following code segment at the beginning of each header file:
# Ifndef filename_h
# Define filename_h
Then put the following line of code at the end:
# Endif
Replace filename_h with the header file name (in upper case), and use the bottom line to replace the point in the file name. Some people like to add comments to # endif to remind them what this # endif refers. For example:
# Endif/* # ifndef filename_h */
I personally do not have this habit, because it is obvious. Of course, this is just a matter of different styles.
You only need to add this technique to header files with compilation errors. However, it is a good habit to add this technique to all header files.
1.5 re-compile a multi-File Project
It is important to clearly differentiate compilation and connection. The compiler uses the source code file to generate some form of object files ). In this process, external symbolic references are not explained or replaced. Then we use the connector to connect these target files and some standard packages plus the package you specified, and finally connect to generate an executable program. At this stage, a reference to the symbols in other files in a target file is interpreted, and a reference that cannot be interpreted is reported. It is generally reported as an error message.
The basic step is to compile your source code files one by one into the format of the target file, and finally connect all the target files with the required packages into an executable file. The specific implementation is determined by your compiler. Here I only give commands related to GCC (GNU C compiler), which may also apply to your non-GCC compiler.
GCC is a multi-target tool. It calls other components (preprocessing programs, compilers, combinations, connectors) as needed ). The specific components that are called depend on the type of the input file and the switch you pass to it.
In general, if you only give it the C source code file, it will pre-process, compile, and combine all the files, connect the obtained target file to an executable file (the generally generated file is named. out ). Of course you can do this, but this will undermine the benefits of splitting a project into multiple files.
If you give it a-c switch, GCC will only compile the file for it into the target file, use the name of the source file name, but put its suffix by ". C "or". cc "to". O ". If you give it a list of target files, GCC will connect them into executable files. The default file name is A. Out. You can change the default name by using the-o switch followed by the specified file name.
Therefore, after you change the source code file, you need to recompile it: 'gcc-C filename. C 'and reconnect to your project: 'gcc-O exec_filename *. O '. If you change a header file, you need to recompile all the source code files that # include this file. You can use 'gcc-C file1.c file2.c file3.c' to connect them as above.
Of course, this is very tedious. Thanks to some of our tools, this step has become simple. The second part of this article describes one of the tools: GNU make.
(Good guy, now I can see the real chapter. Have you learned something ?)
2) GNU make Tool
2.1 BasicMakefile
Structure
The main task of GNU make is to read a text file, makefile. This document mainly describes which files (the 'target' target files) are generated from which other files (the 'dependencies' depend on files, what command is used for this generation process. With this information, make checks the files on the disk. If the timestamp of the target file (the time when the file is generated or changed) make will execute the corresponding command to update the target file if at least one of its dependent files is old. (The destination file is not necessarily the final executable file. It can be any file .)
Makefile is generally called "makefile" or "makefile ". Of course, you can specify another file name in the make command line. If you do not specify it, it will find "makefile" or "makefile". Therefore, using these two names is the simplest.
A makefile mainly contains a series of rules as follows:
:...
(Tab)
(Tab)
.
.
.
For example, consider the following makefile:
=== Makefile start ===
Myprog: Foo. O Bar. o
GCC Foo. O Bar. O-o myprog
Foo. O: Foo. c Foo. H bar. h
Gcc-C Foo. C-o Foo. o
Bar. O: bar. c bar. h
Gcc-C bar. C-o Bar. o
=== Makefile ended ===
This is a very basic makefile -- Make starts from the top, putting the first purpose on the top, 'myprog ', as its main goal (a final goal that needs to be proved to be always the latest ). As long as the file 'myprog' is earlier than the file 'foo. o' or 'bar. o', the next line of commands will be executed.
However, before checking the timestamps of file Foo. O and bar. O, it will look for rules that use Foo. O or bar. O as the target file. It finds the rules for foo. O. The files depend on foo. C, foo. H, and bar. h. It cannot find the rules for generating these dependent files from below, and it starts to check the timestamps of these dependent files on the disk. If any of these files has a new timestamp than Foo. O, the command 'gcc-O Foo. O Foo. c' will be executed to update the file Foo. O.
Next, we will perform a similar check on the file bar. O, depending on the file bar. C and bar. h.
Now, make returns to the 'myprog' rule. If either of the two rules is executed, myprog needs to be rebuilt (because one. o file is newer than 'myprog'). Therefore, the connection command will be executed.
Hopefully, you can see the benefits of using the make tool to build a program-all the tedious inspection steps in the previous chapter are done by make for you: Check the timestamp. A simple change in your source code file will cause that file to be re-compiled (. o file dependency. c file), and the executable file is reconnected (because. o file changed ). In fact, the real benefit is that when you change a header file, you no longer need to remember that source code file depends on it, because all the information is in makefile. Make will easily re-compile all the source code files changed because of this header file for you. If necessary
Reconnect.
Of course, you must make sure that the rules you write in makefile are correct, and only list the header files that are # include in the source code file ......
2.2 compile make rules (Rules)
The most obvious (and simplest) method of writing rules is to check the source code files one by one and use their target files as the purpose, the C source code file and Its # include header file depend on the file. However, you also need to list other header files that are included by these header files # include as dependent files, as well as the files included in the included files ...... Then you will find that you need to manage more and more files, and then your hair begins to fall off, your temper begins to deteriorate, and your face turns into a dish, you are on the road and start to collide with the power pole. Finally, you destroy your computer monitor and stop programming. Is there any easier way?
Of course! Ask the compiler! When compiling each source code file, it should know what header files should be included. When using gcc, use the-M switch. It will output a rule for every c file you give it, and use the target file as the target file, the C file and all header files that should be # include will be used as dependent files. Note that this rule adds all header files, including files surrounded by Angle brackets ('<', '>') and double quotation marks. In fact, we can be quite certain that the system header files (such as stdio. h and stdlib. h) will not be changed. If you use
-Mm is used to replace-M and pass it to GCC. header files surrounded by ampersand will not be included. (This will save some Compilation Time)
The rules output by GCC do not contain commands. You can write your own commands or write nothing, and make use of its implicit rules (refer to section 2.4 below ).
2.3MakefileVariable
The makefiles mentioned above mainly contains some rules. The other things they contain are variable definitions.
The variables in makefile are like an environment variable (environment variable ). In fact, environment variables are interpreted as make variables during the make process. These variables are case-sensitive and generally use uppercase letters. They can be referenced from almost any place or used to do many things, such:
I) store a list of file names. In the preceding example, the rule for generating executable files contains some target file names. In the command line of this rule, the same files are delivered to GCC as command parameters. If you use a variable to store all the target file names, adding a new target file will become simple and error-prone.
Ii) store executable file names. If your project is used in a non-GCC system, or if you want to use a different compiler, you must change all the places where the compiler is used to use the new compiler name. However, if you use a variable to replace the compiler name, you only need to change the command name in one place, and the command name in all the places changes.
Iii) store the compiler flag. Suppose you want to pass the same set of options (for example,-wall-o-g) to all your compilation commands. If you save this set of options into a variable, you can place this variable in all the call compilers. When you want to change the option, you only need to change the variable content in one place.
To set a variable, you only need to write the name of the variable at the beginning of a line, followed by a = sign, followed by the value of the variable you want to set. You will reference this variable in the future and write a $ symbol, followed by the variable name enclosed in brackets. For example, we can rewrite the previous makefile using variables below:
=== Makefile start ===
Objs = Foo. O Bar. o
Cc = gcc
Cflags =-wall-o-g
Myprog: $ (objs)
$ (CC) $ (objs)-O myprog
Foo. O: Foo. c Foo. H bar. h
$ (CC) $ (cflags)-C Foo. C-o Foo. o
Bar. O: bar. c bar. h
$ (CC) $ (cflags)-C bar. C-o Bar. o
=== Makefile ended ===
There are also some preset internal variables that are defined according to the content of each rule. The three most useful variables are $ @, $ <and $ ^ (these variables do not need to be enclosed in parentheses ). $ @ Extension to the target file name of the current rule, $ <extension to the first dependent file in the dependency list, $ ^ is extended to the entire dependent list (removing all duplicated file names ). With these variables, we can write the makefile above:
=== Makefile start ===
Objs = Foo. O Bar. o
Cc = gcc
Cflags =-wall-o-g
Myprog: $ (objs)
(Cc) $ ^-o $ @
Foo. O: Foo. c Foo. H bar. h
$ (CC) $ (cflags)-C $ <-o $ @
Bar. O: bar. c bar. h
$ (CC) $ (cflags)-C $ <-o $ @
=== Makefile ended ===
You can use variables to do many other things, especially when you mix them with functions. For more information, see the GNU make manual. ('Man make', 'Man makefile ')
2.4 implicit rules (implicit rules)
Note that in the above example, several commands that generate the. o file are the same. All generate. O files from. c files and related files. This is a standard step. In fact, make already knows how to do it-it has some built-in rules called implicit rules, which tell it what to do when you do not give some commands.
If you delete the commands that generate Foo. O and bar. O from their rules, make will find its implicit rules and then find an appropriate command. Its command will use some variables, so you can set it as you want: it uses the variable CC as the compiler (as in the previous example ), and pass the variable cflags (to C compiler, C ++ compiler uses cxxflags), cppflags (C pre-processor flag), target_arch (do not consider this now ), then it adds the flag '-C', followed by the variable $ <(the first dependent name), followed by the flag'-O' and variable
$ @ (Target file name ). The specific command for compiling a C program will be:
$ (CC) $ (cflags) $ (cppflags) $ (target_arch)-C $ <-o $ @
Of course, you can define these variables according to your own needs. This is why the-M or-mm switch output code of GCC can be directly used in a makefile.
2.5 phony targets)
Assume that two executable files need to be generated at the end of your project. Your primary goal is to generate two executable files, but these two files are independent of each other-if one file needs to be rebuilt, it does not affect the other. You can use "hypothetical purpose" to achieve this effect. An imaginary object is almost the same as a normal object, but this object does not exist. Therefore, make always assumes that it needs to be generated. After updating its dependent files, it will execute the command lines in its rules.
If you enter:
ALL: exec1 exec2
Exec1 and exec2 are the two executable files for our purpose. Make uses this 'all' as its main purpose. During each execution, it tries to update 'all. However, since this rule does not contain any command to act on an actual file named 'all' (in fact, all is not actually generated on the disk ), therefore, this rule does not really change the 'all' status. However, since this file does not exist, make will try to update the all rule, so it will check whether it depends on exec1 and exec2 to be updated. If necessary, update them, to achieve our goal.
The hypothetical purpose can also be used to describe a group of non-preset actions. For example, if you want to delete all files generated by make, you can set a rule in makefile:
Veryclean:
RM *. o
Rm myprog
The premise is that no other rule depends on this 'veryclean' purpose, and it will never be executed. However, if you explicitly use the 'Make veryclean' command, make will take this purpose as its main goal and execute those RM commands.
If a file named veryclean exists on your disk, what will happen? In this case, because there is no dependency on files in this rule, the target file must be up-to-date (all dependent files are up-to-date ), so even if you explicitly run make to generate it again, nothing will happen. The solution is to demonstrate all hypothetical purposes (use. phony), which tells make not to check whether they exist on the disk or find any implicit rules. It simply assumes that the specified object needs to be updated. Add the following rule in makefile that contains the preceding rule:
. Phony: veryclean
You can. Note: This is a special make rule. Make knows. phony is a special purpose. Of course, you can add any hypothetical purpose you want to use in its reliance, and make knows that they are all hypothetical purposes.
2.6 Functions)
Functions in makefile are very similar to their variables. When using makefile, you use a $ symbol with parentheses, function names, spaces, and a column of parameters separated by commas, end with parentheses. For example, in GNU make, there is a function named 'wildcard ', which has a parameter to expand into a column of all the file names that match the description of its parameters, files are separated by spaces. You can use this command as follows:
Sources = $ (wildcard *. c)
This will generate a list of all files ending with '. c' and store them in the sources variable. Of course, you do not need to store the result into a variable.
Another useful function is the patsubst (Patten substitude) function. It requires three parameters-the first is a pattern to be matched, the second represents what to use to replace it, and the third is a word column to be processed separated by spaces. For example, to process the variable defined above,
Objs =$ (patsubst %. C, %. O, $ (sources ))
This row will process all words in the sources column (a column name). If it ends with '. C','. o' will replace '. C. Note that the "%" symbol matches one or more characters, and each matching string is called a "stem ). In the second parameter, % is interpreted as the handle matched by the first parameter.
2.7 a relatively validMakefile
With what we have learned, we can create a very effective makefile. This makefile can be used directly in most projects without too many changes.
First, we need a basic makefile to build our program. We can search for the current directory, find the source code file, and assume that they all belong to our project, and put a variable named sources. If all *. CC files are included here, it may be more secure, because the source code file may be c ++ code.
Sources = $ (wildcard *. C *. CC)
With patsubst, we can generate the target file name by the source code file name. We need to compile these target files. If our source code file contains both. C and. CC files, we need to call the same patsubst function:
Objs =$ (patsubst %. C, %. O, $ (patsubst %. CC, %. O, $ (sources )))
In the top layer of a patsubst call, the. CC file is replaced by a suffix. The result is processed by the outer patsubst call and replaced by the. c file suffix.
Now we can set up a rule to create an executable file:
Myprog: $ (objs)
Gcc-O myprog $ (objs)
Further rules are not required. GCC already knows how to generate the target file (object files ). Below we can set rules for generating reliance on information:
Depends: $ (sources)
Gcc-M $ (sources)> depends
If a file named 'depends 'does not exist, or any source code file is newer than an existing depends file, a depends file will be generated. The depends file will contain the source code file rules generated by GCC (note-M switch ). Now let make use of these rules as part of the MAKEFILE file. The technique used here is similar to the # include System in C language-We asked make to include this file into makefile, as shown below:
Include depends
GNU make sees this and checks whether the 'depends 'object has been updated. If not, it uses the command we gave it to re-generate the depends file. Then it will include this set of (new) Rules and continue to process the final target 'myprog '. When you see the rules about myprog, it will check whether all the target files are updated-use the rules in the depends file, of course, these rules are now updated.
This system is actually very inefficient, because every time a source code file is changed, all the source code files will be preprocessed to generate a new 'depends' file. In addition, it is not 100% secure, because when a header file is modified, the information will not be updated. But for basic work, it is also quite useful.
2.8 a better makefile
This is a makefile I designed for most projects. It can be used in most projects without modification. I mainly use it on djgpp, which is a DOS GCC compiler. Therefore, you can see that the command name, 'alleg' package, and RM-F variables reflect this.
=== Makefile start ===
######################################
#
# Generic makefile
#
# By George foot
# Email: george.foot@merton.ox.ac.uk
#
# Copyright (c) 1997 George foot
# All Rights Reserved.
# Retain all copyrights
#
# No warranty, no liability;
# You use this at your own risk.
# No insurance, not responsible
# Use this to take risks on your own
#
# You are free to modify and
# Distribute this without giving
# Credit to the original author.
# You can change and distribute this file at will
# Without the need to honor the original author.
# (What do you mean ?)
#
######################################
### Customising
# User settings
#
# Adjust the following if necessary; executable is the target
# Executable's filename, and libs is a list of libraries to link in
# (E.g. alleg, stdcx, iostr, etc). You can override these on make's
# Command line of course, if you prefer to do it that way.
#
# Adjust the following items if needed. Executable is the target executable file name, Libs
# Is a list of packages to be connected (such as alleg, stdcx, and iostr ). Of course you
# You can overwrite them on the make command line.
#
Executable: = mushroom.exe
Libs: = alleg
# Now alter any implicit rules 'variables if you like, e.g .:
#
# Change the variables in any implicit rule you want to modify, for example
Cflags: =-g-wall-O3-mnames
Cxxflags: = $ (cflags)
# The next bit checks to see whether RM is in your djgpp Bin
# Directory; if not it uses del instead, but this can cause (harmless)
# 'File not found 'error messages. If you are not using DOS at all,
# Set the variable to something which will unquestioningly remove
# Files.
#
# Check whether there are rm commands in your djgpp command directory. If not, use
# Del command, but it is possible to give us the 'file not found' error message.
# What is the problem. If you are not using DOS, set it as a command to delete files without any nonsense.
# (In fact, this step is redundant in Unix systems, but it is convenient for DOS users. UNIX
# You can delete these five lines .)
Ifneq ($ (wildcard $ (djdir)/bin/rm.exe ),)
RM-F: = Rm-F
Else
RM-F: = del
Endif
# You shouldn't need to change anything below this point.
#
# From here on, you should not need to change anything. (I don't believe it. It's too Nb !)
Source: = $ (wildcard *. c) $ (wildcard *. CC)
Objs: =$ (patsubst %. C, %. O, $ (patsubst %. CC, %. O, $ (source )))
Deps: = $ (patsubst %. O, %. D, $ (objs ))
Missing_deps: = $ (filter-out $ (wildcard $ (deps), $ (deps ))
Missing_deps_sources: = $ (wildcard $ (patsubst %. D, %. C, $ (missing_deps ))\
$ (Patsubst %. D, %. CC, $ (missing_deps )))
Cppflags + =-MD
. Phony: Everything deps objs clean veryclean rebuild
Everything: $ (executable)
Deps: $ (deps)
Objs: $ (objs)
Clean:
@ $ (RM-F) *. o
@ $ (RM-F) *. d
Veryclean: clean
@ $ (RM-F) $ (executable)
Rebuild: veryclean everything
Ifneq ($ (missing_deps ),)
$ (Missing_deps ):
@ $ (RM-F) $ (patsubst %. D, %. O, $ @)
Endif
-Include $ (deps)
$ (Executable): $ (objs)
Gcc-o $ (executable) $ (objs) $ (addprefix-L, $ (libs ))
=== Makefile ended ===
There are several points worth explaining. First, I use the = rather than the = symbol when defining most variables. It immediately expands the number of functions and variables referenced in the definition. If = is used, the function and variable reference will stay there, that is to say, changing the value of a variable will also change the value of other variables. For example:
A = foo
B = $ ()
# B is $ (A) and $ (a) is 'foo '.
A = bar
# B is still $ (a), but its value has changed to 'bar.
B: = $ ()
# The value of B is 'bar '.
A = foo
# The value of B is still 'bar '.
Make ignores all text after the # symbol until the end of that line.
The ifneg... else... endif system is a tool in makefile that allows conditional invalidation/validity of some codes. Ifeq uses two parameters. If they are the same, it adds the code straight to else (or endif, if there is no else) to makefile. If they are different, add a code segment from Else To endif to makefile (if else exists ). Ifneq is used in the opposite way.
The 'filter-out' function uses two lists separated by spaces to delete all the items in the second list that exist in the first list. I use it to process the deps List, delete all existing projects, and only keep the missing ones.
As I mentioned earlier, cppflags have some flag used for implicit rules to pass to the Preprocessor. The-MD switch is similar to the-M switch, but from the source code file. C or. the file name in CC is suffixed. D (this explains how to form the deps variable ). The file mentioned in deps was later added to the makefile with '-include', which hides all error messages generated because the file does not exist.
If any dependent file does not exist, makefile will delete the corresponding. o file from the disk, so that make can recreate it. Because cppflags specifies-MD, its. d file is also regenerated.
Finally, the 'addprefix' function adds the first parameter value to each prefix of the second parameter list.
The purpose of this makefile is (these can be directly used by the command line passed to make ):
Everything: (default) updates the main executable program and generates or updates a '. d' file and a'. o' file for each source code file.
Deps: only generates or updates a '. d' file for each source code program.
Objs: generates or updates the '. d' file and target file for each source code program.
Clean: delete all mediation/dependent files (*. D and *. O ).
Veryclean: Perform 'clean' and delete executable files.
Rebuild: Do 'verclean' first and then 'everthing '; both completely rebuilt.
Except for the preset everything, only the headers clean, veryclean, and rebuild are meaningful to users.
I have not found that when a directory of the source code file is provided, this makefile will fail unless the file is corrupted. If this mess occurs, you only need to input 'make clean' to delete all target files and dependent files, and the problem should be solved. Of course, it is best not to confuse them. If you find that this makefile cannot complete its work under certain circumstances, please let me know and I will fix it.
3. Summary
I hope this article will explain in detail how multi-file projects work and how to use them securely and reasonably. At this point, you should be able to easily use the GNU make tool to manage small projects. If you fully understand the following parts, it should not be difficult for you.
GNU make is a powerful tool. Although it is mainly used to build programs, it has many other functions. If you want to know more about this tool, its syntax, functions, and many other features, you should refer to its reference file (info pages, similar to other gnu tools, view their info pages .).