How to organize and build multi file C language programs! UNIX program and makefile programming!


Prepare your favorite drinks, editors and compilers, play some music, and then start building a C language program composed of multiple files.


It is often said that the art of computer programming is partly dealing with complexity and partly naming something. In addition, I think “sometimes you need to add drawings” is largely correct.

In this article, I will write a small C program, name something, and deal with some complexity.


Excellent UNIX Programming Philosophy

First of all, you should know that this C program is a   Unix   Command line tools.

This means that it runs on (or can be ported to) operating systems that provide UNIX C running environment. When Bell Labs invented UNIX, it was full of design philosophy from the beginning.

In my own words: the program only does one thing, does it well, and does some operations on the file. Although “do one thing and do it well” makes sense, the “do something with the file” section seems a little inappropriate.

Facts have proved that the abstract “file” in UNIX is very powerful.

A UNIX file is a byte stream that ends with the end of file (EOF) flag, that’s all.

Any other structure in the file is imposed by the application, not the operating system.

The operating system provides system calls that enable programs to perform a standard set of operations on files: open, read, write, address, and close (among others, but that’s complicated).

Standardized access to files allows different programs to share the same abstraction and work together, even if they are programs written by different people in different languages.

Having a shared file interface makes it easy to buildComposableThe program becomes possible.

The output of one program can be used as the input of another program.

The operating system of UNIX family provides three files by default when executing programs: standard input (stdin), standard output (stdout) and standard error (stderr).

Two of these files are write only: stdout   and   stderr。 and   Stdin is read-only. When we use file redirection in common shells such as bash, we can see its effect.

$ ls | grep foo | sed -e ‘s/bar/baz/g’ > ack

This instruction can be briefly described as: LS   The result of is written to the standard output, which redirects to   grep   Standard input, grep   Redirect standard output to   sed   Standard input for, sed   Redirect the standard output of to the current directory with the file name   ACK file.

We want our program to work well in this flexible and excellent ecosystem, so let’s write a program that can read and write files.


Meow meow: stream encoder / decoder concept

When I was a child with open teeth, I learned a lot of coding schemes when I learned computer science.

Some of them are used to compress files, some are used to package files, and others are useless and therefore very stupid.

In order to make our program useful, I updated one for it   21st century   And implements the concept of a coding scheme called “meow meow”.

The basic idea here is to get the file and encode each half byte (half byte) with the text “meow”. Lowercase letters represent 0 and uppercase letters represent 1.

Because it will replace 4 bits with 32 bits, it will expand the size of the file. Yes, it makes no sense. But when you see this code, you will be surprised.

$ cat /home/your_sibling/.super_secret_journal_of_my_innermost_thoughts



Final implementation

The complete source code can be found in   GitHub   I found it up there.

Now that I have determined to write a program to encode and decode files in “meow meow” format, I execute the following command in the shell:

$ mkdir meowmeow

$ cd meowmeow

$ git init

$ touch Makefile     # Method of compiling program

$ touch main.c       # Processing command line options

$ touch main.h       # Global constants and definitions

$ touch mmencode.c   # Realize the coding of meow meow file

$ touch mmencode.h   # Description encoding API

$ touch mmdecode.c   # Realize the decoding of meow meow file

$ touch mmdecode.h   # Description decoding API

$ touch table.h      # Define encoding lookup table

$ touch .gitignore   # The file name in this file will be ignored by GIT

$ git add .

$ git commit -m “initial commit of empty files”

Simply put, I created a directory full of empty files and submitted it to GIT.

Even if there is no content in these files, you can still infer the purpose of each file from its file name. In case you don’t understand, I’m here   touch   The command is briefly described later.

Usually, the program starts from a simple   main.c   At the beginning of the file, there are only two or three problem-solving functions.

Then the programmer rashly showed the program to his friend or boss, and then the number of functions in the file quickly exploded in order to support all new “functions” and “requirements”.

The first rule of “program club” is not to talk about “program club”. The second rule is to minimize the functions in a single file.


But what is makefile?

I know the next blockbuster app is for you good kids   “Ultimate code breaker 3000”   It is written in the integrated development environment, and the construction project is mixed with a series of complex keys such as ctrl-meta-shift-alt-super-b.

But now, using   Makefile   Files can help do a lot of useful work when building C programs.

Makefile   Is a text file that contains how files are handled, and programmers can use it to automatically build binaries (and other things) from source code

Take the following little thing as an example:

00 # Makefile

01 TARGET= my_sweet_program

02 $(TARGET): main.c

03    cc -o my_sweet_program main.c

#   Symbol   The following text is a comment, such as line 00.

Line 01 is a variable assignment, which will   TARGET   The variable is assigned to a string   my_ sweet_ program。 According to the Convention and my habit, all   Makefile   Variables are capitalized and words are separated by underscores.

Line 02 contains the file name to be created by recipe in this step and the file it depends on. In this case, the build target is   my_ sweet_ Program, which depends on   main.c。

The last 03 lines use a tab instead of four spaces. This is the command that will execute to create the target. In this case, we use   C compiler front end   cc   Compile link as   my_ sweet_ program。

use   Makefile   It’s very simple.

$ make

cc -o my_sweet_program main.c

$ ls

Makefile  main.c  my_sweet_program

Build our meow meow encoder / decoder   Makefile   It is more complex than the above example, but its basic structure is the same. I’ll break it down into Barney style in another article.


Form is accompanied by function

My idea is that the program reads it from one file, converts it, and stores the converted results in another file.

The following is what I imagine when I interact with the program command line:

$ meow < clear.txt > clear.meow

$ unmeow < clear.meow > meow.tx

$ diff clear.txt meow.tx


We need to write code for command line parsing and processing input / output streams. We need a function stream to encode and write the results to another stream.

Finally, we need a function to decode the stream and write the result to another stream.

But in the above example, I called two instructions: meow   and   unmeow?

I know you might think it will lead to more and more complexity.


Minor content: argv [0] and LN instructions

Recall that the structure of the C language main function is as follows:

int main(int argc, char *argv[])

among   argc   Is the number of command line arguments, argv   Is a list of character pointers (strings), argv [0]   Is the path to the file containing the program being executed.

In UNIX system, many programs with complementary functions (such as compression and decompression) look like two commands, but in fact, they are a program with two names in the file system. This technique is achieved by using   ln   Command creates a file system link to implement the of two names.

In my laptop  / usr/bin   An example is as follows:

$ ls -li /usr/bin/git*

3376 -rwxr-xr-x. 113 root root     1.5M Aug 30  2018 /usr/bin/git

3376 -rwxr-xr-x. 113 root root     1.5M Aug 30  2018 /usr/bin/git-receive-pack

here   git   and   git-receive-pack   Is the same file but has different names.

We say they are the same files because they have the same inode value (first column).

Inode is a feature of UNIX file system, and its introduction goes beyond the scope of this article.

First, we write a program based on its   argv[0]   We then make sure to create a link for the name that causes the behavior.

In our   Makefile   Middle, unmeow   Links are created in the following ways:

# Makefile


        $(LN) -f $< [email protected]


I tend to   Makefile   Parameterize everything, rarely using “bare” strings.

I put all the definitions in   Makefile   The top of the file so that you can easily find and change them. When you try to migrate the program to a new platform, you need to   cc   Change to a   cc   This will be convenient.

Except for two built-in variables  [email protected]  and  $

The first is a shortcut to the target of this step, in this case  $ I can remember this because  @  The symbol looks like a target).


Things will certainly become complicated, but it is still under management.


Finally, whether you are changing careers, beginners or advanced, if you want to learn programming~

[noteworthy] myC / C + + programming learning exchange club![Click to enter]

Q & A, learning and communication, technical discussion, and a great collection of programming resources. Zero basic videos are also great~