LINUX下的C编程(Emacs、GCC、Gdb、Makefile)

文本编辑器

Linux 系统提供了一个完整的编辑器家族系列,如 Ed、Ex、Vi 和 Emacs 等。按功能它们可以分为两大类:行编辑器(Ed、Ex)和全屏幕编辑器(Vi、Emacs)。行编辑器每次只能对一行进行操作,使用起来很不方便。而全屏幕编辑器可以对整个屏幕进行编辑,用户编辑的文件直接显示在屏幕上,从而克服了行编辑的那种不直观的操作方式,便于用户学习和使用,具有强大的功能。

  • vi编辑器的使用(vim)
    命令格式:
vi  文件路径 文件名

例:
输入命令:

vi /home/hello.c    (或者输入vim  /home/hello.c)

进入界面按任意键进入编辑模式:
在这里插入图片描述
退出并保存:
先按下Esc键,然后在最后一行键入:wq(存档并退出);
在这里插入图片描述
若键入:q!可强制退出。
vi/vim各模式功能键
(1)命令行模式下

I 切换到插入模式,此时光标当于开始输入文件处
A 切换到插入模式,并从目前光标所在位置的下一个位置开始输入文字
O 切换到插入模式,且从行首开始插入新的一行
[ctrl]+[b] 屏幕往“后”翻动一页
[ctrl]+[f] 屏幕往“前”翻动一页
[ctrl]+[u] 屏幕往“后”翻动半页
[ctrl]+[d] 屏幕往“前”翻动半页
0(数字 0) 光标移到本行的开头
G  光标移动到文章的最后
nG 光标移动到第 n 行
$ 移动到光标所在行的“行尾”
n<Enter> 光标向下移动 n 行
/name 在光标之后查找一个名为 name 的字符串
?name 在光标之前查找一个名为 name 的字符串
X 删除光标所在位置的“后面”一个字符
dd 删除光标所在行
ndd 从光标所在行开始向下删除 n 行
yy  复制光标所在行
nyy  复制光标所在行开始的向下 n 行
p  将缓冲区内的字符粘贴到光标所在位置(与 yy 搭配)
U  恢复前一个动作

(2)底行模式常见功能键

:w 将编辑的文件保存到磁盘中
:q 退出 Vi(系统对做过修改的文件会给出提示)
:q! 强制退出 Vi(对修改过的文件不作保存)
:wq 存盘后退出
:w [filename]  另存一个命为 filename 的文件
:set nu  显示行号,设定之后,会在每一行的前面显示对应行号
:set nonu  取消行号显示
  • Emacs文档编辑器的使用
    可以看到vi或vim的文档编辑器界面看起来并不那么愉快且每次只能对一行操作,使用起来不太方便,我们使用另外一种文档编辑器Emacs。
    如果系统中没有Emacs可以利用命令j进行安装:
sudo  yum  install  emacs

安装过程中若弹出提醒按y键即可。
启动emacs:

emacs 文件名

文档编辑器界面如下:
在这里插入图片描述
(1)例:
输入命令打开并编辑文件:

emacs /home/hello2.c  

写入以下代码:

#include<stdio.h>
    void main()
    {
    	int a,b,c;
    	for(a=1;a<=9;a++)
    	{
    		for(b=1;b<=a;b++)
    			printf("%d*%d=%d  ",b,a,c=a*b);
    		printf("\n");
    	}
    }

点击save退出即可。
(2)光标的移动:鼠标点击或移动光标,也可以使用命令(C为ctrl,M为Alt键)

C-f 向前移动一个字符
M-b  向后移动一个单词
C-b  向后移动一个字符
C-a  移动到行首
C-p  移动到上一行
C-e  移动到行尾
C-n  移动到下一行
M-< (M 加“小于号”)移动光标到整个文本的开头

(3)复制文本(C为ctrl,M为Alt键)
在 Emacs 中的复制文本包括两步:选择复制区域和粘贴文本。
选择复制区域的方法是:首先在复制起始点(A)按下“C-Spase”或“C-@(C-Shift-2)”使它成为一个表示点,再将光标移至复制结束电(B),再按下“M-w”,就可将 A 与 B 之间的文本复制到系统的缓冲区中。在使用功能键 C-y 将其粘贴到指定位置。

Gcc编译器

GNU CC(简称为 Gcc)是 GNU 项目中符合 ANSI C 标准的编译系统,能够编译用 C、C++和 Object C 等语言编写的程序。Gcc 不仅功能强大,而且可以编译如 C、C++、Object C、Java、Fortran、Pascal、Modula-3 和 Ada 等多种语言,而且 Gcc 又是一个交叉平台编译器,它能够在当前 CPU 平台上为多种不同体系结构的硬件平台开发软件,因此尤其适合在嵌入式领域的开发编译。本章中的示例,除非特别注明,否则均采用 Gcc 版本为 4.0.0。

  • GCC支持的后缀名解释
    在这里插入图片描述

  • Gcc的使用
    命令模式:

gcc  命令选项  文件或目录  -o  文件名

上述命令中 -o 文件名这个命令这个操作是为了把输出文件输出到另一个文件里变为一个可执行文件。
查看编译结果(执行可执行文件):

./  可执行文件名
  • gcc选项:
-c  只是编译不链接,生成目标文件“.o”
-S  只是编译不汇编,生成汇编代码
-E  只进行预编译,不做其他处理
-g  在可执行程序中包含标准调试信息
-o file   把输出文件输出到 file 里
-v  打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir   在头文件的搜索路径列表中添加 dir 目录
-L dir  在库文件的搜索路径列表中添加 dir 目录
-static  链接静态库
-llibrary  连接名为 library 的库文件
-ansi  支持符合 ANSI 标准的 C 程序
-pedantic  允许发出 ANSI C 标准所列的全部警告信息
-pedantic-error  允许发出 ANSI C 标准所列的全部错误信息
-w  关闭所有告警
-Wall  允许发出 Gcc 提供的所有有用的报警信息
-werror  把所有的告警信息转化为错误信息,并在告警发生时终止编译过程
  • 例子:
    (1)
    我们先对上面提到的用vi编辑的程序进行编译:
    在这里插入图片描述
    命令:
gcc /home/hello.c  -o  c

结果:
在这里插入图片描述

没有错误没有警告
执行文件:

./c

结果:
在这里插入图片描述
(2)用emacs编辑的程序进行编译:
程序(99乘法表):

#include<stdio.h>
    void main()
    {
    	int a,b,c;
    	for(a=1;a<=9;a++)
    	{
    		for(b=1;b<=a;b++)
    			printf("%d*%d=%d  ",b,a,c=a*b);
    		printf("\n");
    	}
    }

命令:

gcc /home/hello2.c -o -c2

执行:

./c2

结果:
在这里插入图片描述

Gdb调试器的使用

调试是所有程序员都会面临的问题。如何提高程序员的调试效率,更好更快地定位程序中的问题从而加快程序开发的进度,是大家共同面对的。就如读者熟知的 Windows 下的一些调试工具,如 VC 自带的如设置断点、单步跟踪等,都受到了广大用户的赞赏。那么,在 Linux下有什么很好的调试工具呢?
Gdb 调试器是一款 GNU 开发组织并发布的 UNIX/Linux 下的程序调试工具。虽然,它没有图形化的友好界面,但是它强大的功能也足以与微软的 VC 工具等媲美。
下面就请跟随笔者一步步学习 Gdb 调试器。

  • gdb使用流程
    首先,打开 Linux 下的编辑器 Vi 或者 Emacs,编辑如下代码:

/*test.c*/
#include <stdio.h>
int sum(int m);
int main()
{
int i,n=0;
sum(50);
for(i=1; i<=50; i++)
{
n += i;
}
printf("The sum of 1-50 is %d \n", n );
}
int sum(int m)
{
int i,n=0;
for(i=1; i<=m;i++)
n += i;
printf("The sum of 1-m is %d\n", n);
return 0;
}

在保存退出后首先使用 Gcc 对 test.c 进行编译,注意一定要加上选项“-g” ,这样编译出
的可执行代码中才包含调试信息,否则之后 Gdb 无法载入该可执行文件。

gcc -g /home/test.c  -o test

虽然这段程序没有错误,但调试完全正确的程序可以更加了解 Gdb 的使用流程。接下来
就启动 Gdb 进行调试。注意,Gdb 进行调试的是可执行文件,而不是如“.c”的源代码,因
此,需要先通过 Gcc 编译生成可执行文件才能用 Gdb 进行调. 也就是对上述的test文件进行调试。
执行命令:

gdb test 

开始调试,出现下列提示:

[root@localhost adminxu]# gdb test
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/adminxu/test...done.
(gdb) 

可以看出,在 Gdb 的启动画面中指出了 Gdb 的版本号、使用的库文件等信息,接下来就
进入了由“(gdb)”开头的命令行界面了。

  • 查看文件
    在 Gdb 中键入“l”(list)就可以查看所载入的文件,如下所示:
l
1	#include <stdio.h>
2	int sum(int m);
3	int main()
4	{
5	int i,n=0;
6	sum(50);
7	for(i=1; i<=50; i++)
8	{
9	n += i;
10	}
(gdb) l
11	printf("The sum of 1-50 is %d \n", n );
12	}
13	int sum(int m)
14	{
15	int i,n=0;
16	for(i=1; i<=m;i++)
17	n += i;
18	printf("The sum of 1-m is %d\n", n);
19	return 0;
20	}
(gdb) 

键入l后加enter键。若代码没有显示完,再次键入l。可以看出,Gdb 列出的源代码中明确地给出了对应的行号,这样就可以大大地方便代码的定位。

  • 断点的设置与使用
    (1)设置断点
    设置断点是调试程序中是一个非常重要的手段,它可以使程序到一定位置暂停它的
    运行。因此,程序员在该位置处可以方便地查看变量的值、堆栈情况等,从而找出代码
    的症结所在。
    在 Gdb 中设置断点非常简单,只需在“b”后加入对应的行号即可(这是最常用的方式,另外还有其他方式设置断点。
(gdb) b 6
Breakpoint 1 at 0x40053c: file /home/test.c, line 6.

要注意的是,在 Gdb 中利用行号设置断点是指代码运行到对应行之前将其停止,如上例中,代码运行到第 5 行之前暂停(并没有运行第 5 行)。
(2)查看断点情况
在设置完断点之后,用户可以键入“info b”来查看设置断点情况,在 Gdb 中可以设置
多个断点。

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040053c in main at /home/test.c:6

(3)运行代码
接下来就可运行代码了,Gdb 默认从首行开始运行代码,可键入“r”(run)即可(若想从程序中指定行开始运行,可在 r 后面加上行号)。

(gdb) r
Starting program: /home/adminxu/test 
Breakpoint 1, main () at /home/test.c:6
6	sum(50);

可以看到,程序运行到断点处就停止了(断点设置在第六行)。
(4)查看变量值
在程序停止运行之后,程序员所要做的工作是查看断点处的相关变量值。在 Gdb 中只需
键入“p”+变量值即可。

(gdb) p n
$1 = 0
(gdb) p i
$2 = 0

在断点之前变量n和i都没有赋值,所以为0;
(5)单步运行
单步运行可以使用命令“n”(next)或“s”(step),它们之间的区别在于:若有函数调用的时候,“s”会进入该函数而“n”不会进入该函数。因此,“s”就类似于 VC 等工具中的“stepin”,“n”类似与 VC 等工具中的“step over”。

(gdb) n
The sum of 1-m is 1275
7	for(i=1; i<=50; i++)
(gdb) s
9	n += i;

若是在第6行,先执行命令s

(Gdb) s
sum (m=50) at test.c:16 
16 int i,n=0;

则会进入引用得函数sum()函数中执行,而n不会。
(6)恢复程序运行
在查看完所需变量及堆栈情况后,就可以使用命令“c”(continue)恢复程序的正常运行
了。这时,它会把剩余还未执行的程序执行完,并显示剩余程序中的执行结果。以下是之前
使用“n”命令恢复后的执行结果:
(7)退出,键入q即可退出gdb调试。

(gdb)c
The sum of 1-50 is 1275 
[Inferior 1 (process 6387) exited with code 031]

  • 工作环境相关命令
set args 运行时的参数指定运行时参数,如 set args 2
show args 查看设置好的运行参数
path dir 设定程序的运行路径
show paths 查看程序的运行路径
set enVironment var [=value] 设置环境变量
show enVironment [var] 查看环境变量
cd dir 进入到 dir 目录,相当于 shell 中的 cd 命令
pwd 显示当前工作目录
shell command 运行 shell 的 command 命令
  • 设置断点与恢复命令
bnfo b 查看所设断点
break 行号或函数名 <条件表达式>   设置断点
tbreak 行号或函数名 <条件表达式>   设置临时断点,到达后被自动删除
delete [断点号]  删除指定断点,其断点号为“info b”中的第一栏。若缺省断点号则删除所有断点
disable [断点号]]  停止指定断点,使用“info b”仍能查看此断点。同 delete 一样,省断点号则停止所有断点。
enable [断点号]  激活指定断点,即激活被 disable 停止的断点
condition [断点号] <条件表达式>  修改对应断点的条件
ignore [断点号]<num>  在程序执行中,忽略对应断点 num 次
step 单步恢复程序运行,且进入函数调用
next  单步恢复程序运行,但不进入函数调用
finish  运行程序,直到当前函数完成返回
c  继续执行函数,直到函数结束或遇到新的断点
  • gdb源码查看相关命令
list <行号>|<函数名>  查看指定位置代码
file [文件名]  加载指定文件
forward-search 正则表达式  源代码前向搜索
reverse-search 正则表达式  源代码后向搜索
dir dir   停止路径名
show directories  显示定义了的源文件搜索路径
info line  显示加载到 Gdb 内存中的代码
  • Gdb 中查看运行数据相关命令
print 表达式|变量   查看程序运行时对应表达式和变量的值
x <n/f/u>  查看内存变量内容。其中 n 为整数表示显示内存的长度,f 表示显示的格式,u 表示从当前地址往后请求显示的字节数
display 表达式  设定在单步运行或其他情况中,自动显示的对应表达式的内容
  • gdb使用注意
    · 在 Gcc 编译选项中一定要加入“-g”。
    · 只有在代码处于“运行”或“暂停”状态时才能查看变量值。
    · 设置断点后程序在指定行之前停止。
Makef工程管理器
  • 为什引入Make工程管理?
    所谓工程管理器,顾名思义,是指管理较多的文件的。读者可以试想一下,有一个上百个文件的代码构成的项目,如果其中只有一个或少数几个文件进行了修改,按照之前所学的 Gcc编译工具,就不得不把这所有的文件重新编译一遍,因为编译器并不知道哪些文件是最 近更新的,而只知道需要包含这些文件才能把源代码编译成可执行文件,于是,程序员就不 能不再重新输入数目如此庞大的文件名以完成最后的编译工作。所以,人们就希望有一个工程管 理器能够自动识别更新了的文件代码,同时又不需要重复输入冗长的命令行,这样,Make 工程管理器也就应运而生了。
    比如,使用gcc编译一个文件的时候,我们只需要一个命令即可:
gcc hello.c -o c

但是1000个文件呢?

gcc hello1.c hello2.c..............-o c

是不是被恶心到了,而且这样编译所有的文件都必须在同一个文件夹中。

  • makefile基本结构
    需要由 make 工具创建的目标体(target),通常是目标文件或可执行文件;
    要创建的目标体所依赖的文件(dependency_file);
    创建每个目标体时需要运行的命令(command)。
    首先早对应文件目录下创建makefile文件。注意文件的命名格式为makefile或Makefile,否则无法使用makefile。键入命令:emacs /home/test/makefile(emacs编辑)或vim /home/test/makefile(vim编辑器)。
    进入编辑模式开始编辑makefile
    格式为:
target(目标文件): dependency_files(依赖文件)
[TAB]command

例如,有两个文件分别为 hello.c 和 hello.h,创建的目标体为 hello.o,执行的命令为 gcc编译指令:
gcc –c hello.c,那么,对应的 Makefile 就可以写为:

#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c –o hello.o

接着就可以使用 make 了。使用 make 的格式为:make target,这样 make 就会自动读入Makefile(也可以是首字母小写 makefile)并执行对应 target 的 command 语句,并会找到相
应的依赖文件。如下所示:

[root@localhost makefile]# make hello.o
gcc –c hello.c –o hello.o
[root@localhost makefile]# ls
hello.c hello.h hello.o Makefile

可以看到,Makefile 执行了“hello.o”对应的命令语句,并生成了“hello.o”目标体。

  • makefile编译原理
    我们以一个例子说明。
    现在一个文件夹下分别编辑文件main.c,my1.h.my2.h,my1.c,my2.c.fen,分别编辑以下代码:
    代码引用:https://www.cnblogs.com/missliuxin/p/3540531.html
#include "my2.h"
int main()
{
my1_print("hello my1!");
my2_print("hello my2!");
return 0;
}

名称为my1.h,代码如下:#ifndef _MY _ 1 _ H

#define _MY_1_H
void my1_print(char *print_str);
#endif

m2.h

#define _MY_2_H
void my2_print(char *print);
#endif

名称为my1.c,代码如下:#include “my1.h”

#include <stdio.h>
void my1_print(char *print_str)
{
printf("This is my2 print %s\n", print_str);
}

名称为my2.c,代码如下:#include “my2.h”

#include <stdio.h>
void my2_print(char *print_str)
{
printf("This is my2 print %s\n", print_str);

键入命令:

emacs /home/test/makefile

编辑makefile文件:

main:main.o my1.o my2.o
	gcc main.o my1.o my2.o -o main
main.o:main.c my1.h my2.h
	gcc -c main.c
my1.o:my1.c my1.h
	gcc -c my1.c
my2.o:my2.c my2.h
	gcc -c my2.c
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 

(gcc加-c选项表示只编译不链接,生成目标文件.o)
我们需要清楚以下几点:
#第一个目标是我们的最终标,由上面的程序可知我们要的最终目标是将main.o,my1.o,my2.o链接生成一个可执行文件。
#make的基本工作流程,我们不难发现make的工作流程其实类似于一个递归的算法。首先我们先根据最终目标是生成执行文件main,那么久需要可链接文件main.o,my1.o,my2.o.,那么就会向下执行寻找main.o,my1.o,m2.o,再找到最后一个文件后,再从下到上依次执行gcc -c my2.c,产生了三个点o类型的连接文件。最后连接生成可执行文件main。
#.PHONY(伪目标):一个文件只能有一个最终目标我们才能执行到最终的操作,但是我们又想要执行其他操作,比如我们不想要make工作后产生的.o文件,怎样再来一个目标进行操作来进行删除。我们就需要用到伪目标。如上述程序所做的,我们可以在伪目标后加上相关操作,其中cleanall和clean的名称并不是固定的,这样做不会影响最终目标的执行,又增加了新的操作。
接下来验证makefil是否建立成功
键入命令:

make main

main为最终目标名:
在这里插入图片描述
执行可执行文件main:

./main

结果:
在这里插入图片描述

我们查看一下目前目录下的文件:
在这里插入图片描述

键入命令执行伪目标清除文件:

make cleanall

在这里插入图片描述

  • makefile的自定义变量
    Makefile 中的变量分为用户自定义变量、预定义变量、自动变量及环境变量。如上例中的 OBJS 就是用户自定义变量,自定义变量的值由用户自行设定,而预定义变量和自动变量为通常在 Makefile 都会出现的变量,其中部分有默认值,也就是常见的设定值,当然用户可以对其进行修改。
    (1)自定义变量的使用
    Make 中的变量使用均使用格式为:$(VAR)。(也就是dollor符加变量名)
    例:接上面makefile例子,我们进一步做简化
    为了更加突出变量的简化作用,我们在原来的makefile程序中加入更多的gcc选项
    原来的命令格式
main:main.o my1.o my2.o
	gcc main.o my1.o my2.o -o main
main.o:main.c my1.h my2.h
	gcc -c -g -Wall main.c
my1.o:my1.c my1.h
	gcc -c -g -Wallmy1.c
my2.o:my2.c my2.h
	gcc -c -g -Wall my2.c
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 

我们用变量名OBJS代替main.o my1.o my2.o,用cflags代表gcc的命令选项-c -g -Wall,用cc代表gcc,如下,使用表变量时要加$( )

OBJS=main.o my1.o my2.o
cc=gcc
cflags=-c -g -Wall
main:$(OBJS)
	$(cc) $(OBJS) -o main
main.o:main.c my1.h my2.h
	$(cc) $(cflags) main.c 
my1.o:my1.c my1.h
	$(cc) $(cflags) my1.c 
my2.o:my2.c my2.h
	$(cc) $(cflags) my2.c
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 

键入命令make main之后结果仍一样。

  • Makefile的自动变量
$* 不包含扩展名的目标文件名称
$+  所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$<  第一个依赖文件的名称
$? 所有时间戳比目标文件晚的依赖文件,并以空格分开
$@ 目标文件的完整名称
$^  所有不重复的依赖文件,以空格分开
$%  如果目标是归档成员,则该变量表示目标的归档成员名称

我们继续依次改写之前的例子:

cc=gcc
cflags=-c -g -Wall
main:main.o my1.o my2.o
	$(cc) $^ -o $@
main.o:main.c my1.h my2.h
	$(cc) $(cflags) $< 
my1.o:my1.c my1.h
	$(cc) $(cflags) $< 
my2.o:my2.c my2.h
	$(cc) $(cflags) $<
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 

执行后结果仍一样。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值