前言:本期将学习gcc/g++/gdb/git的使用
首先输入一段测试代码
#include
#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
但是我们要理解上面四个过程,就要划分成四条指令依次执行上述的四步翻译过程,在此期间理解选项的含义。
gcc -E test.c -o test.i
,其中-E
表示从现在开始,进行程序的翻译,当将预处理做完,就停下来。-o
指明形成的临时文件名称gcc -S test.i -o test.s
,把C语言转成汇编语言gcc -c test.s -o test.o
,把汇编语言变成可重定向目标二进制文件。od test.o
:打开二进制文件。gcc test.o -o test.out
,形成可执行二进制程序(库+你的代码)首先我们要清楚,我们自己写的代码和库是两码事。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
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual
C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
对于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
先运行以下两个代码
#include
int main()
{
// 首先执行的一定是printf,代码是顺序结构
// 先执行printf不等于数据先显示
printf("Heeeeello!\n"); // 第一个
printf("Heeeeello!"); // 第二个
sleep(2);
return 0;
}
我们发现,sleep尽管在printf语句的后面,但是执行不带\n的代码中显示器是仍然是先执行的sleep,这是什么原因呢?
实际上,这是一个行缓冲的问题,即确实在语言上先执行的printf,但却不是直接打印在显示器上,而是进入了缓冲区,而缓冲区是以\n为截止条件的,也就是说这一行中程序如果没有\n,就会暂时保留在缓冲区内部,直到出现\n或者程序执行完成。因此,上面的动图并没有直接执行printf是因为没有\n。
对于回车换行,实际上是两个概念,换行\n是换到下一行,而回车\r是光标回到这一行的起始位置,因此我们键盘上的enter键称之为回车换行实际上是两个功能合并在了一起。
因此为了解决上面的代码问题,可以用刷新缓冲区的办法实现:
#include
int main()
{
printf("Heeeeello!\r");
fflush(stdout);
sleep(2);
return 0;
}
#include
#include
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)。
对于进度条来说,通过最上面的行缓冲的知识,我们已经知道应该如何去规避了,因此在这里直接展示进度条,我将程序分成三个部分,即经典的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
#include
#include // 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;
}
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
Linux gcc/g++ 出来的二进制程序,默认是 release 模式
要使用 gdb 调试,必须在源代码生成二进制程序的时候 , 加上 -g 选项,这样才能进入Debug模式
安装gdb
sudo yum install -y gdb
程序的发布方式有两种, debug 模式和 release 模式
Linux gcc/g++ 出来的二进制程序,默认是 release 模式
要使用 gdb 调试,必须在源代码生成二进制程序的时候 , 加上 -g
选项
准备一份测试代码
// mytest.c
#include
#include
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
2 #include
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的使用”的知识啦~~ ,感谢友友们的阅读。后续还会继续更新,欢迎持续关注哟~
如果有错误❌,欢迎批评指正呀~让我们一起相互进步
如果觉得收获满满,可以点点赞支持一下哟~