【Linux详解】——gcc/g++/gdb/git的使用

📖 前言:本期将学习gcc/g++/gdb/git的使用


🕒 1. 程序的翻译过程

  1. 预处理(头文件展开,去注释,宏替换,条件编译)
  2. 编译:把C变成汇编语言
  3. 汇编:把汇编变成二进制(不是可执行,二进制目标文件不能被执行)
  4. 链接:把你下的代码和C标准库中的代码合起来

🕒 2. 理解选项的含义

首先输入一段测试代码

#include<stdio.h>
#define M 666
int main()
{            
	// 测试注释
	printf("Hello 1\n");
	// printf("Hello 1\n");                                                
	// printf("Hello 2\n");
	// printf("Hello 3\n");
	// printf("Hello 4\n")   
	printf("Hello 5\n");   
                 
	//测试条件编译
#ifdef SHOW             
	printf("Hello show!\n");
#else             
	printf("Hello default\n");
#endif                      
    // 测试宏                 
   	printf("宏:%d\n", M);
   	return 0;
}            

直接执行会一步到位输出结果

[hins@VM-12-13-centos testLinux]$ vim test.c
[hins@VM-12-13-centos testLinux]$ gcc test.c
[hins@VM-12-13-centos testLinux]$ ll
total 16
-rwxrwxr-x 1 hins hins 8408 Oct 29 12:04 a.out
-rw-rw-r-- 1 hins hins  414 Oct 29 12:03 test.c
[hins@VM-12-13-centos testLinux]$ ./a.out
Hello 1
Hello 5
Hello default
宏:666

但是我们要理解上面四个过程,就要划分成四条指令依次执行上述的四步翻译过程,在此期间理解选项的含义。

  1. 预处理: gcc -E test.c -o test.i,其中-E表示从现在开始,进行程序的翻译,当将预处理做完,就停下来。-o指明形成的临时文件名称
    在这里插入图片描述
  2. 编译: gcc -S test.i -o test.s,把C语言转成汇编语言
  3. 汇编: gcc -c test.s -o test.o,把汇编语言变成可重定向目标二进制文件。od test.o:打开二进制文件。
  4. 链接: gcc test.o -o test.out,形成可执行二进制程序(库+你的代码)

🕒 3. 动态链接和静态链接

首先我们要清楚,我们自己写的代码和库是两码事。C标准库是别人给我们准备好的,让我们直接使用的。我们所有使用库中函数的代码(如printf()),其中我们自己只写了该函数的调用,没有对应的实现!只有当链接的时候,对应的实现才和我们的代码关联起来!

那么这就引入了链接,链接的本质:就是我们调用库函数的时候和标准库如何关联的问题。这种关联就包括动态和静态。
在这里插入图片描述

  • 动态链接: 受库升级或者被删除的影响,形成的可执行程序小,节省资源。
  • 静态链接: 不受库升级或者被删除的影响,形成的可执行程序较大! – 网络,磁盘,内存

在Linux下库的命名:

  • 动态库:lib XXX.so 例:libc.so.6就是c标准库
  • 静态库:lib XXX.a

在Windows下:

  • 动态库:.dll
  • 静态库:.lib

当我们执行查看c标准库的时候,就可以看到具体的信息,并发现此标准库默认是.so结尾的动态库。

[hins@VM-12-13-centos testLinux]$ ls /lib64/libc.so.6 -l
lrwxrwxrwx 1 root root 12 Jul 25 16:58 /lib64/libc.so.6 -> libc-2.17.so
[hins@VM-12-13-centos testLinux]$ ls /lib64/libc-2.17.so -al
-rwxr-xr-x 1 root root 2156592 May 19 00:18 /lib64/libc-2.17.so

对于动态库和静态库来说,动态库是系统自带的,即系统安装完毕就可以使用,而静态库则一般需要我们自己安装,这也说明了静态库并不是直接拷贝动态库的内容。因此我们需要手动安装一下静态库:sudo yum install -y glibc-static

安装静态库之后,我们就可以通过 在已有的指令基础上加上-static指定静态库编译:

[hins@VM-12-13-centos testLinux]$ gcc test.c -o test2.s -static	# 静态编译指令
-rwxrwxr-x 1 hins hins   8360 Oct 29 20:41 test1.s # 动态编译
-rwxrwxr-x 1 hins hins 861288 Oct 29 20:41 test2.s # 静态编译

[hins@VM-12-13-centos testLinux]$ file test1.s
test1.s: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=0d515023c156fe158cca3be209a14ff5924814e1, not stripped

[hins@VM-12-13-centos testLinux]$ file test2.s
test2.s: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=0c504898c44f70be42f625ac057dbc6ed3cae69b, not stripped

安装C++版本的gcc(g++):sudo yum install -y gcc-c++

安装C++静态库:sudo yum install -y libstdc++-static

🕒 4. Linux项目自动化构建工具-make/Makefile

🕘 4.1 背景

  • 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
  • makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual
    C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
  • make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

🕘 4.2 使用

对于makefile,若想利用make命令,则必须创建makefile命名的文件(m大写也可),在内部编写一定的依赖规则之后,我们通过make就可以对应的执行程序,就省略了类似于这种gcc test.c -o test的编译指令,我们来看看如何操作:

[hins@VM-12-13-centos testLinux]$ touch makefile # 创建一个makefile文件
[hins@VM-12-13-centos testLinux]$ vim makefile # 编辑内部依赖关系
# 写入下面指令
mycode:mycode.c		# 依赖关系:即生成的mycode是依赖mycode.c实现的
	gcc mycode.c -o mycode		# 依赖方法:即上述我们需要省略的gcc指令,必须以Tab键开头
.PHONY:clean		# 被.PHONY修饰的对象是一个伪目标,它总是被执行,例子在下面
clean:
	rm -f mycode
[hins@VM-12-13-centos testLinux]$ make	# 这里make默认执行第一个gcc指令,生成mycode可执行文件,指令等价于make mycode
gcc mycode.c -o mycode
[hins@VM-12-13-centos testLinux]$ ll
total 24
-rw-rw-r-- 1 hins hins   74 Oct 30 11:32 makefile
-rwxrwxr-x 1 hins hins 8360 Oct 30 11:32 mycode		# 可执行文件
-rw-rw-r-- 1 hins hins   77 Oct 29 20:39 mycode.c

[hins@VM-12-13-centos testLinux]$ ./mycode
Hello World!		# 输出结果

[hins@VM-12-13-centos testLinux]$ make	# 重复执行make指令
make: `mycode' is up to date.		# 提示已是最新,不用再编译,原因是mycode.c没有任何修改

[hins@VM-12-13-centos testLinux]$ make clean  # 执行clean对象
rm -f mycode
[hins@VM-12-13-centos testLinux]$ ll
total 12
-rw-rw-r-- 1 hins hins  74 Oct 30 11:32 makefile
-rw-rw-r-- 1 hins hins  77 Oct 29 20:39 mycode.c
[hins@VM-12-13-centos testLinux]$ make clean	# mycode文件已删除,但仍能执行,原因是被.PHONY修饰的对象总是被执行
rm -f mycode

🕒 5. Linux的第一个小程序-进度条

🕘 5.1 缓冲区概念

先运行以下两个代码

#include<stdio.h>
int main()
{
    // 首先执行的一定是printf,代码是顺序结构
    // 先执行printf不等于数据先显示
    printf("Heeeeello!\n");		// 第一个
	printf("Heeeeello!");		// 第二个
    sleep(2);

    return 0;
}

请添加图片描述
我们发现,sleep尽管在printf语句的后面,但是执行不带\n的代码中显示器是仍然是先执行的sleep,这是什么原因呢?

实际上,这是一个行缓冲的问题,即确实在语言上先执行的printf,但却不是直接打印在显示器上,而是进入了缓冲区,而缓冲区是以\n为截止条件的,也就是说这一行中程序如果没有\n,就会暂时保留在缓冲区内部,直到出现\n或者程序执行完成。因此,上面的动图并没有直接执行printf是因为没有\n。

🕘 5.2 回车换行概念

对于回车换行,实际上是两个概念,换行\n是换到下一行,而回车\r是光标回到这一行的起始位置,因此我们键盘上的enter键称之为回车换行实际上是两个功能合并在了一起。

🕘 5.3 fflush(stdout)

因此为了解决上面的代码问题,可以用刷新缓冲区的办法实现:

#include<stdio.h>
int main()
{
	printf("Heeeeello!\r");		
	fflush(stdout);
    sleep(2);
    return 0;
}

🕘 5.4 倒计时实现

#include<stdio.h>
#include<unistd.h>
int main()
{
    int cnt = 10;
    while(cnt)
    {
        printf("倒计时:%2d\r", cnt);
        fflush(stdout);
        cnt--;
        sleep(1);
    }
    return 0;
}

上述实际上有一定的细节,我们知道/r只是回到起始位置,但如果不控制格式2d,就会出现打印10,90,80……的情况,因为我们每次只覆盖了第一个位置,因此在这里要控制格式,并且fflush(stdout)。
请添加图片描述

🕘 5.5 进度条实现

对于进度条来说,通过最上面的行缓冲的知识,我们已经知道应该如何去规避了,因此在这里直接展示进度条,我将程序分成三个部分,即经典的main.c/process.c/process.h,并且将makefile中的依赖对象也改变,对于依赖对象来说,只要-o后面最靠近的是要生成的即可。

# Makefile
ProcessOn:main.c process.c 
	gcc -o ProcessOn main.c process.c -DN=3  # 利用命令行传参,选择进度条的形状

.PHONY:clean 
clean:
	rm -f ProcessOn
// process.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<unistd.h>      // usleep的头文件

#define NUM 101
#define S_NUM 5

extern void ProcessOn();  // 函数的声明
// process.c
#include"process.h"

char style[S_NUM] = {'-','.','#','>','+'};

void ProcessOn()    // 函数的定义
{
    int cnt = 0;
    char bar[NUM];

    memset(bar,'\0',sizeof(bar));

    //reverse
    const char* lable = "|\\-/";

    //101次
    while(cnt <=100)
    {
        //printf("[%-100s][%d%%][%c]\r", bar,cnt,lable[cnt%4]);
        printf("\033[42;34m[%-100s][%d%%][%c]\033[0m\r", bar,cnt,lable[cnt%4]); 		// 修改颜色
        fflush(stdout);
        bar[cnt++] = style[N];
        usleep(50000);
    }
    printf("\n");
}

颜色修改
printf(“'\033[字背景颜色;字体颜色m字符串\033[0m” ); printf(“'\033[47;31mhelloworld\033[5m”);

// main.c
#include"process.h"
int main()
{
    ProcessOn();  // 函数调用
    return 0;
}

请添加图片描述

🕒 6. git的使用

  • git clone + [url]:克隆远程仓库,这里的 url 就是项目的链接.

.gitignore介绍:凡是这个文件内部的后缀,都不会被上传到gitee上的。

所谓的git仓库,本质就是一个目录,以及里面的内容。而push到远端就是将.git的内容同步到gitee上

  • git add + 文件:将新增的文件添加到本地仓库
  • git commit [-m] "日志":提交,-m 后面加上提交的日志
  • git push:将本地内容推送到远端
  • git log:查看提交日志
  • git status:查看当前状态
  • git pull:把远端拉到本地同步。(如果远端和本地都同步进行修改了,起冲突了,直接先pull一下)

配置免密码提交
🔎 git本地免密码和账号pull、push

🕒 7. gdb(调试工具)的使用

Linux gcc/g++ 出来的二进制程序,默认是 release 模式
要使用 gdb 调试,必须在源代码生成二进制程序的时候 , 加上 -g 选项,这样才能进入Debug模式

安装gdb

sudo yum install -y gdb

程序的发布方式有两种, debug 模式和 release 模式
Linux gcc/g++ 出来的二进制程序,默认是 release 模式
要使用 gdb 调试,必须在源代码生成二进制程序的时候 , 加上 -g 选项

准备一份测试代码

// mytest.c
#include <stdio.h>
#include <time.h>

void Print(int sum)
{
    long long  timestamp = time(NULL);
    printf("result = %d, timestamp: %lld\n", sum, timestamp);
}

int AddToVal(int from, int to)
{
    int sum = 0;
    for(int i = from; i < to; i++)
    {
        sum += i;
    }

    return sum;
}

int main()
{
    int sum = AddToVal(0, 100);
    printf("hello a\n");
    printf("hello b\n");
    printf("hello c\n");
    printf("hello d\n");
    printf("hello e\n");
    printf("hello f\n");
    Print(sum);
    return 0;
}
# Makefile
mytest_g:mytest.c
	gcc -o mytest_g mytest.c -g --std=c99   # gcc默认支持c89标准,若要支持c99需要加上--std=c99
.PHONY:clean
clean:
	rm -f mytest_g

进入gdb:gdb mytest_g

常见选项

  • l + 行号:从指定的行号开始往下显示源代码,每次显示10行 (l – list);(注:gdb 有自动记忆命令的功能,即当我们第一次使用 l 显示源代码后,我们下一次再使用 l 或者下一次按下 enter 键时,它会接着上次的位置往下显示)
(gdb) l			# 每次显示10行(此处位置是随机)
warning: Source file is more recent than executable.
15	        sum += i;
16	    }
17	
18	    return sum;
19	}
20	
21	
22	int main()
23	{
24	    int sum = AddToVal(0, 100);
(gdb) l 0		# 从第1行开始显示
1	#include <stdio.h>
2	#include <time.h>
3	
4	void Print(int sum)
5	{
6	    long long  timestamp = time(NULL);
7	    printf("result = %d, timestamp: %lld\n", sum, timestamp);
8	}
9	
10	int AddToVal(int from, int to)
(gdb) l			# 第二次接着上次的位置往后显示
11	{
12	    int sum = 0;
13	    for(int i = from; i < to; i++)
14	    {
15	        sum += i;
16	    }
17	
18	    return sum;
19	}
20	
(gdb) 			# 使用enter键相当于执行上次的命令
21	
22	int main()

......
  • l + 函数:列出某个函数的源代码 (l – list);
  • b + 行号:在某一行打一个断点,相当于VS中的F9 (b – breakpoint);
  • info b:查看断点;
  • d + 断点编号:删除断点 (d – delete);(注:每个断点都有自己的编号,我们删除断点时需要指明对应的断点编号)
  • r:调试运行,如果程序中有断点,则在断点处停下来,如果没有,则直接将程序跑完,相当于VS中的F5 (r – run);
  • n:逐过程调试,相当于VS中的F10 (n – next);
  • s:逐语句调试,相当于VS中的F11 (s – step);
  • c:运行至下一个断点处停下 (c – continue);(注:如果断点所在行不是一条语句,比如 “{” “}” 或者 空行,那么它会继续往下到有效行处停下 )
  • bt:查看调用堆栈 (breaktrace);
  • p + 变量:查看变量值 (p – print);
  • display/undisplay + 变量:跟踪查看一个变量,每次停下来都显示它的值,undisplay 取消对先前设置的那些变量的跟踪;
  • finish:把当前函数运行完;
  • disable breakpoints:禁用断点;
  • enable breakpoints:启用断点;
  • until + X行号:跳至X行
  • info(i) locals:查看当前栈帧局部变量的值
  • quit:退出 gdb;
(gdb) b 24
Breakpoint 1 at 0x400628: file mytest.c, line 24.
(gdb) b 26
Breakpoint 2 at 0x400644: file mytest.c, line 26.
(gdb) b 31
Breakpoint 3 at 0x400676: file mytest.c, line 31.
(gdb) info b					# 查看断点信息
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400628 in main at mytest.c:24
2       breakpoint     keep y   0x0000000000400644 in main at mytest.c:26
3       breakpoint     keep y   0x0000000000400676 in main at mytest.c:31
(gdb) r							# 相当于VS的F5
Starting program: /home/hins/testLinux/gdb_test/mytest_g 

Breakpoint 1, main () at mytest.c:24
24	    int sum = AddToVal(0, 100);
(gdb) s							# 相当于VS的F11
AddToVal (from=0, to=100) at mytest.c:12
12	    int sum = 0;
(gdb) n							# 相当于VS的F10
13	    for(int i = from; i < to; i++)
(gdb) bt						# 查看调用堆栈
#0  AddToVal (from=0, to=100) at mytest.c:13
#1  0x0000000000400637 in main () at mytest.c:24
(gdb) display i					# 监视窗口
1: i = 0
(gdb) display sum
2: sum = 0
(gdb) 
(gdb) n
15	        sum += i;
2: sum = 0
1: i = 0
(gdb) n
13	    for(int i = from; i < to; i++)
2: sum = 0
1: i = 0
(gdb) n
15	        sum += i;
2: sum = 0
1: i = 1
(gdb) n
13	    for(int i = from; i < to; i++)
2: sum = 1
1: i = 1
(gdb) n
15	        sum += i;
2: sum = 1
1: i = 2
(gdb) n
13	    for(int i = from; i < to; i++)
2: sum = 3
1: i = 2
(gdb) finish					# 把当前函数运行完
Run till exit from #0  AddToVal (from=0, to=100) at mytest.c:13
0x0000000000400637 in main () at mytest.c:24
24	    int sum = AddToVal(0, 100);
Value returned is $1 = 4950
(gdb) n
25	    printf("hello a\n");
(gdb) c
Continuing.
hello a

Breakpoint 2, main () at mytest.c:26
26	    printf("hello b\n");
(gdb) c							# 运行至下一个断点停下
Continuing.
hello b
hello c
hello d
hello e
hello f

Breakpoint 3, main () at mytest.c:31
31	    Print(sum);
(gdb) finish
The program is not being run.

OK,以上就是本期知识点“gcc/g++/gdb/git的使用”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟📌~
💫如果有错误❌,欢迎批评指正呀👀~让我们一起相互进步🚀
🎉如果觉得收获满满,可以点点赞👍支持一下哟~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值