【Linux】Commonly used tools (Part 2)

1. Linux project automated build tool - make/Makefile

The source files in a project are not counted. They are placed in several directories according to type, function, and module. The makefile defines a series of rules to specify which files need to be compiled first, which files need to be compiled later, and which files need to be recompiled. Compile and even perform more complex functional operations.

In Windows operating systems, such as the vs2019 compiler, with a graphical interface, we can directly generate solutions with one click, that is, compile; but in Linux systems, we need to use gcc or g++ to compile manually. When we When there are more files that need to be compiled, our work efficiency is reduced.

So, let’s learn a tool make and Makefile next

  • The benefit brought by Makefile is - "automated compilation" . Once written, only one make command is needed, and the entire project is completely automatically compiled, which greatly improves the efficiency of software development;
  • make is a command tool that interprets the instructions in the Makefile . Generally speaking, most IDEs have this command.

1. Dependencies and dependency methods

Let's take a brief look at the use of make and Makefile ; we first touch a Makefile file and an ordinary file :

Insert image description here

Let's just write some code in the test.c file:

Insert image description here

Then we enter the Makefile and write the dependencies and dependent methods:

Insert image description here

Among them, mytest:test.cit indicates that the executable program to be generated, mytest , depends on the test.c file; and gcc test.c -o mytestis the dependency method of the corresponding dependency relationship , that is, the solution, that is, how to make test.c get mytest.

.PHONYIt is to define a pseudo target clean . The characteristics of the pseudo target are always executed. We will introduce this feature later.

Then clean:it shows that clean has no dependencies, and its dependency method is rm -f mytest, this step will clean up the project.

Let's look at the usage. We execute make on the command line , which means compiling the project:

Insert image description here

Then we execute this program and observe:

Insert image description here

You can see that it is executed normally; then we clean up the project:

Insert image description here

This completes the compilation and cleaning of the project; note that when we use make , the first dependency and method are executed by default, and we need to specify the subsequent ones ourselves; for example, we reverse the dependencies and dependency methods of the above compilation and cleaning. Come here, as shown in the picture:

Insert image description here

Then we execute make and observe the results:

Insert image description here

You can see that the first dependency is executed by default, and then we execute the compiled dependencies and methods:

Insert image description here

This can also be used normally, but we usually use the first one, that is, compiling at the front and cleaning at the end.

2. False goals

What we said above .PHONY:is followed by a pseudo target. Generally, for our clean target files, we set it as a pseudo target and .PHONYmodify it with. The characteristics of the pseudo target are: it is always executed.

  • (1) How to understand that it is always executed?

First we try to execute make observation multiple times:

Insert image description here

We observed that the program was compiled only when make was executed for the first time, and was not compiled later; what if we clean the program multiple times? Our observations:

Insert image description here

We can observe that make cleanthe program is cleaned every time it is executed. This is how it should be understood that it is always executed. Because clean is a pseudo target, it is always executed.

  • (2) So why can’t it always be executed if it is not a pseudo goal?

First, let’s understand the ACM time of the file. stat + 文件名You can view the ACM time of the file, as shown below:

Insert image description here

Among them, Access time is the most recent time to access this file; Modify time is the most recent time to modify the content of this file ; Change is the most recent time to modify the attributes of this file .

In fact, Makefile and make do not allow us to recompile our code through the content modification time of a file , because our file is already the latest and there is no need to compile it again.

The detailed explanation is as follows: Suppose we have a source file test.c and a Makefile :

Insert image description here

When we compile for the first time , we must first have the source file ( test.c above ). At this time, it has not been compiled yet, and there must be no our target file ( mytest file), so the compilation must be successful, as follows:

Insert image description here

When we have the mytest file, the modification time of the mytest file > the modification time of the test.c file;

Insert image description here

Insert image description here

When we compile for the nth time, if we do not modify test.c , then the modification time of the mytest file is still greater than the modification time of the test.c file, and the compilation fails at this time, as shown below:

Insert image description here

When we modify the test.c file and update its modification time, the modification time of test.c > the modification time of mytest , the recompilation can pass at this time, as follows:

Insert image description here

Insert image description here

  • (3) Characteristics of Access time

We have the following phenomenon when we compile for the first time:

Insert image description here

When we want to view test.c alone , modify its Access time as follows:

Insert image description here

We found that its Access time has not been modified. Why is this?

Generally speaking, the frequency of a file being viewed is very high; the files we see are all stored on the disk, and the file = content + attributes , so the essence of changing the file time is actually accessing the disk, and accessing the disk The efficiency is very low, so in order to improve efficiency, Linux changes the Access time access interval or adds a limit; so if we want to update the Access time immediately, we can do it directly touch + 文件, as follows:

Insert image description here

3. make/Makefile has dependency derivation capabilities (syntax expansion)

make/Makefile has the ability to derive dependencies. We have the following code in the Makefile file, which is actually the compilation process of the program:

Insert image description here

After compilation it is as follows:
Insert image description here

It can be seen that this is the process of compiling our program. Let's execute and observe:

Insert image description here

However, it is not recommended to compile using this method, because we can directly use gcc to directly form an executable program.

We also have other syntax extensions, as follows:

Insert image description here

Among gcc -o $@ $^them, $@represents all the files to the left of the colon in the dependency relationship$^ ; represents all the files to the right of the colon in the dependency relationship ; after we compile it, it will be replaced with the following:

Insert image description here

4. Write a progress bar code

(1) Buffer

Let's look at the following code:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 
		  4 int main()
		  5 {
		  6     printf("hello, world\n");
		  7     sleep(3);                                                                                                                     
		  8     return 0;
		  9 }

We print "hello, world", then wrap the line, and execute sleep(3); to stop the program for three seconds before continuing. Let's observe the results:

The first execution is like this:

Insert image description here

After three seconds:

Insert image description here

We observed that it took three seconds for the program to redisplay the command line.

Let’s look at the following code again:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 
		  4 int main()
		  5 {
		  6     printf("hello, world");
		  7     sleep(3);                                                                                                                     
		  8     return 0;
		  9 }

In the above code, we deleted the \n in the first section of code, and then we executed this code to observe the results:

After execution:
Insert image description here

After three seconds:

Insert image description here

We observed that hello, world was not printed on the screen after executing the program, and it was printed after three seconds; first, the program was executed from top to bottom, and the printf statement was definitely executed first, but it was not printed first. ,Why is this? Next we introduce a concept - buffer zone.

In fact, in the above phenomenon, after the program executes printf , the content printed by printf is stored in the buffer. In C/C++ , we will be provided with a default buffer for standard output, but before the buffer is refreshed , our content will not be output.

And \n is a refresh strategy---line refresh. So after we add \n , the buffer is refreshed and the content is printed.

The buffer is not refreshed when we do not use \nfflush(stdout) , but we can use to force the buffer to be refreshed to print out the content, for example:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 
		  4 int main()
		  5 {
		  6     printf("hello, world");
		  7     fflush(stdout);    
		  8     sleep(3);                                                                                                           
		  9     return 0;
		 10 }

Output result:

Insert image description here

Three seconds later:

Insert image description here

We can see that the contents of the buffer are forcibly refreshed.

(2)\n and\r

We first write a simple countdown program, for example:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 
		  4 int main()
		  5 {
		  6     int cnt = 10;
		  7     while(cnt >= 0)
		  8     {
		  9         printf("%d\n", cnt);
		 10         cnt--;                                                                                                                    
		 11         sleep(1);                                                       
		 12     }                                                                   
		 13                                                                         
		 14     return 0;                                                           
		 15 } 

Observation results:

Insert image description here

But this is not the countdown we want. We expect it to be displayed on the same line, so we should not use \n . In fact, \n is what we call a carriage return, which causes the cursor to change lines and return to the beginning of that line. position; at this time we should use \r . \r only returns the cursor to the initial position of the current line. We modify the above code as follows:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 
		  4 int main()
		  5 {
		  6     int cnt = 10;
		  7     while(cnt >= 0)
		  8     {
		  9         printf("%d\r", cnt);
		 10		    fflush(stdout); 
		 11         cnt--;                                                                                                                    
		 12         sleep(1);                                                       
		 13     }                                                                   
		 14                                                                         
		 15     return 0;                                                           
		 16 } 

The execution results are as follows:

Insert image description here
Insert image description here

It can be seen from the above results that although our countdown is on the same line, there is still a problem with the output format, because by default %d is printed as one character, and we want to print it as two characters, so we only Just modify line 9 as follows:

		  9         printf("%2d\r", cnt);

The result is as follows:

Insert image description here
Insert image description here

At this time, our countdown is basically completed, but when the countdown reaches single digits, there is an empty character in front, which is not very beautiful. This is because %2d is right-aligned by default. We add a negative sign in front of it, which is left-aligned. , so we continue to modify:

		  9         printf("%-2d\r", cnt);

The result is as follows:

Insert image description here

Insert image description here

At this point our countdown is complete.

(3) Progress bar code

  • Simple version:

First, we create the Makefile , ProgressBar header file, function implementation file and main function file in a new directory , as follows:

Insert image description here

We first edit the Makefile to establish dependencies and dependent methods:

		  1 ProgressBar:main.c ProgressBar.c
		  2     gcc -o $@ $^
		  3 .PHONY:clean
		  4 clean:
		  5     rm -f ProgressBar     

Then create the main function. In the main function, we only need to call our progress bar function, as follows:

		#include "ProgressBar.h"
		
		int main()
		{
		    ProgressBar_v1();
		    return 0;
		}

Then we enter the declaration part of the function and declare the variables and functions we need to use:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 #include <string.h>
		  4 
		  5 void ProgressBar_v1();
		  6 #define SIZE 101      //数组大小
		  7 #define MAX_RATE 100  //加载进度最大值
		  8 #define STYLE '#'     //加载符号
		  9 #define STIME 1000*40 //时间

After we declare all variables, we enter the implementation part of the function:

		  1 #include "ProgressBar.h"
		  2 const char *str = "|/-\\"; // 加载光标
		  3 
		  4 void ProgressBar_v1()
		  5 {
		  6     // 当前进度
		  7     int rate = 0;
		  8     char bar[SIZE];
		  9     memset(bar, '\0', sizeof(bar));
		 10     
		 11     // 加载光标的数组长度
		 12     int num = strlen(str);
		 13     
		 14     // 当进度没有加载满
		 15     while(rate <= MAX_RATE)
		 16     {
		 17         printf("[%-100s][%d%%][%c]\r", bar, rate, str[rate % num]);
		 18         fflush(stdout);
		 19         usleep(STIME);                    
		 20         bar[rate++] = STYLE;              
		 21     }
		 22     printf("\n");                         
		 23 }

Among them, we used usleepthe function when using the delay function. sleepCompared with , the unit sleepis s ; the unit usleepis us .

Next we make make to generate an executable program, then run it and observe the results:

Insert image description here

After loading is complete, it will look like the picture above. This is a simple version that implements a progress bar. Below we will further improve this progress bar.

  • Advanced version (practical application)

In actual applications, our progress bars are generally used in downloading software. Let's simply take downloading a software as an example and simply implement this code:

First we implement the implementation part of the function:

		 // 不能一次将进度条打印完毕,否则无法平滑的和场景结合                                            
		 // 该函数,应该根据rate,自动的打一次                                                 
		 void ProgressBar_v2(int rate)                                                                
		 {                                                                        
		     // 设置为静态数组,每次进来不会清零                                                                                
		     static char bar[SIZE] = {0};                                                          
		                                                                     
		     // 加载光标的数组长度                                                                                                         
		     int num = strlen(str);                                                                 
		                                                                     
		     // 当进度没有加载满                                                                            
		     if(rate >= 0 && rate <= MAX_RATE)                                                                   
		     {                                                                                            
		         printf("[%-100s][%d%%][%c]\r", bar, rate, str[rate % num]);
		         fflush(stdout);                                                              
		         bar[rate] = STYLE;                                           
		     }                                                               
		 }   

Let's look at the declaration part again, where we increase the size of the download target and the download speed each time:

		  1 #include <stdio.h>
		  2 #include <unistd.h>
		  3 #include <string.h>
		  4 
		  5 void ProgressBar_v1();
		  6 void ProgressBar_v2(int);
		  7 #define SIZE 101      //数组大小
		  8 #define MAX_RATE 100  //加载进度最大值
		  9 #define STYLE '#'     //加载符号
		 10 #define STIME 1000*40 //时间
		 11 
		 12 #define TARGET_RATE 1024*1024   //下载目标的大小 1MB
		 13 #define DSIZE 1024*10   //下载速度

Finally, look at the main function part:

		  1 #include "ProgressBar.h"
		  2 
		  3 void download()
		  4 {
		  5     int target = TARGET_RATE;
		  6     int total = 0;
		  7     while(total < target)
		  8     {
		  9         // 用简单的休眠时间,模拟本轮下载花费的时间
		 10         usleep(STIME);
		 11 
		 12         // 每次加上下载速度
		 13         total += DSIZE;
		 14 
		 15         // 按百分比传入 v2 函数
		 16         int rate = total*100/target;
		 17         ProgressBar_v2(rate);
		 18     }                                                                                                                             
		 19     printf("\n");
		 20 }
		 21 
		 22 int main()
		 23 {
		 24     // 下载的软件
		 25     download();
		 26     return 0;
		 27 }

This is a simple progress bar code for practical application; in the main function part, we can also use the callback function for optimization, as follows:

We first add the declaration of the function pointer to the function declaration:

		typedef void (*callback_t)(int); // 函数指针  

Then modify the main function part:

		  1 #include "ProgressBar.h"
		  2 
		  3 void download(callback_t cb)
		  4 {
		  5     int target = TARGET_RATE;
		  6     int total = 0;
		  7     while(total < target)
		  8     {
		  9         // 用简单的休眠时间,模拟本轮下载花费的时间
		 10         usleep(STIME);
		 11 
		 12         // 每次加上下载速度
		 13         total += DSIZE;
		 14 
		 15         int rate = total*100/target;
		 16         cb(rate); // 回调函数                                                                                                     
		 17     }
		 18     printf("\n");
		 19 }

The results of the above progress bar operation are as shown in the figure below:

Insert image description here

2. Linux version controller-git

The so-called version controller, simply put, is software that manages the managed content (text) or program according to changes; the ultimate goal is that whichever version of the text or program we want can be we provide.

The gitee/github we commonly use are websites built based on git software, with the purpose of visualizing versions.

If git is not installed in our Linux , you can execute it to install it.sudo yum install -y git

1. git clone

We use git mainly to store our own code in a remote warehouse. Here we use gitee as the remote warehouse and upload our own code in Linux ; first we need to create a warehouse in gitee , as follows:

Insert image description here

Then we copy the address of the warehouse and run the following command in Linux :

	git clone https://gitee.com/YoungMLet/temp

as follows:

Insert image description here

We are then required to enter our email address and username, so let’s introduce how to configure our email address and username.

2. git config

We can use the command git config to configure our email address and username. For example, we need to configure the username :

		git config --global user.name "xxx" 

If you need to configure an email address , you can execute the following command:

		git config --global user.email "[email protected]"

Note that the username and email address here need to be consistent with the username and email address in the remote warehouse.

We can view our current configuration information using the following command :

		git config -l

If we need to modify our email address and username , there is a way. First we need to delete the email address and username, and delete the username:

		git config --global --unset user.name

Delete email address:

		git config --global --unset user.email

Next we learn git Sanbanaxe .

3. git add

First, let’s briefly understand three terms: workspace, temporary storage area, and version library.

The workspace is the directory where we write code; the staging area is the area where the code is temporarily stored after using git add ; the version library is the area where the code is submitted after being submitted from the staging area (that is, git commit ).

When we need to send our code to the remote warehouse in the workspace, we must first git add our code to the staging area. At this time we need to execute git addthe command, as shown below:

Insert image description here

You only need to execute this command to send study5git status in the workspace to the staging area. At this time, we can use the command to view the status of the warehouse at this time, as follows:

Insert image description here

It means that the add has been successful. At this time, we need to submit our code to the repository.

4. git commit

When our code is already in the staging area, we need to submit it to the repository. At this time, we need to execute the following command:

		git commmit -m "这里写上日志"

Note that it must be added here -m.

Suppose we submit the progress bar written above to the repository, as follows:

Insert image description here

We can also use git logthe command to view the logs we submitted; after we submit the code to the repository, we have the last step to upload our code to the remote warehouse.

5. git push

At this point we only need to execute the following command to upload the code to the remote warehouse:

		git push

At this time we need to enter the user name and password, we can enter:

Insert image description here

At this point, our code is uploaded to the remote warehouse.

6. git pull

In some cases, when many of us collaborate on a project, the code we submit and the code submitted by others may cause our local warehouse and the remote warehouse to be out of sync. At this time, we need to execute to synchronize git pull.

3. Linux debugger-gdb

There are two ways to release programs, debug mode and release mode; binary programs compiled by Linux gcc/g++ default to release mode; to use gdb debugging, you must add the -g option when generating binary programs from source code ; As follows, when we edit the Makefile , we need to add the -g option to the executable program when establishing the dependency method :

Insert image description here

At this point we exit the Makefile , execute make , and then execute the mytest executable file gdb mytestto debug, as follows:

Insert image description here

As shown in the picture above, you have entered the gdb debugging mode , exit debugging q or quit , and now we start using gdb for debugging;

1. View instructions

In gdb, list (abbreviated as l) can view the source code; among them, l + number can view the code from line number; in addition, gdb will record the recent historical commands, and pressing enter is the previous command; so we press enter directly, The columns will continue from the last position, 10 rows at a time. For example look at our code:

Insert image description here

Continue to press Enter at this time:

Insert image description here

Check out our complete code:

Insert image description here

This is the view command.

2. Breakpoint

The instruction for breakpoint is: b + numberor b + function, where number is the line number and function is the function name. For example, we take the above code as an example to break the point on a certain line:

Insert image description here

At this time, check our breakpoint command: info b, as follows:

Insert image description here

The command to delete a breakpoint is , where the serial number is the Numd + 序号 in front of the breakpoint . For example, let's make a few more breakpoints first:

Insert image description here

As above, each breakpoint has a corresponding sequence number. Suppose we need to delete a certain breakpoint:

Insert image description here

3. Start debugging

The command to start debugging is: runin short r, if there is a breakpoint, the program will stop when it encounters the breakpoint, otherwise the program will run directly to the end.

In vs, we can use F10 and F11 for process-by-process and statement-by-statement debugging. We can also use this operation in gdb , where process-by-process is n, which is F10 in vs; statement-by-statement is s, which is in vs. F11.

For example, we only have one breakpoint now, and we use process-by-process and statement-by-statement debugging in sequence:

Insert image description here

Let’s run it first . The program will stop at line 16. At this time we press n:

Insert image description here

Insert image description here

At this point we encounter a function, press s to proceed statement by statement:

Insert image description here

At this point, the program jumps to the entrance of the function. If we continue to go down, just continue n. Suppose we want to view the variable name and address of the variable during debugging. We can use the instruction and use it directly. For example, we are currently entering the loop displaybody display + 变量. , want to view the current value of ret :

Insert image description here

If you want to view the address of ret :

Insert image description here

If you want to cancel the display of a certain variable, you can use undisplay + 序号, where the serial number is the serial number displayed before display , as follows:

Insert image description here

The display of &ret is currently cancelled .

In addition, we can also turn on and off breakpoints, as shown below. In the Enb column, y indicates that the current breakpoint is turned on:

Insert image description here

If we want to turn off this breakpoint, we can execute the command disable + 序号as follows:

Insert image description here

Reopen this breakpoint to execute the command: enable + 序号:

Insert image description here

4. Other instructions

When we enter a loop body, but the number of times of this loop is very high, and we want to skip this loop directly, we can use the command: until + 行号, run to the specified position;

You can also use it finishto run to the end of the current function;

We can also run between breakpoints, running directly from one breakpoint to the next breakpoint, the corresponding instructions: continue(简写c).

btCan view the stack;

set varYou can change the value of a variable, such as set var i = xxx ;

5. Summary

The gdb commands we use are mainly common ones. Please learn the others independently. Let’s summarize our gdb commands below:

				查看代码:l + number
				打断点:b + number
				删断点:d + 序号
				查看断点:info b
				开始调试:r
				禁止断点:disable + 序号
				开启断点:enable + 序号
				逐过程:n
				逐语句:s
				查看变量:display + 变量
				取消查看变量:undisplay + 序号
				运行至指定的位置:until + number
				运行到当前函数的结尾:finish
				从一个断点运行至下一个断点:c
				查看调用堆栈:bt
				更改变量的值:set var + 需改变量 = 改的值
				退出gdb:q

Guess you like

Origin blog.csdn.net/YoungMLet/article/details/132913462