写在前面

本文主要介绍了关于Linux C编程的相关内容以及介绍了Makefile的简单使用教程。

VIM编辑器

如果要在终端模式下进行文本编辑或者修改文件就可以使用 VI/VIM 编辑器,Ubuntu 自带了 VI 编辑器,但是 VI 编辑器使用不方便,比如不能使用键盘上的上下左右键调整光标位置。

VIM 编辑器是 VI 编辑器升级版本, VI/VIM 编辑器都是一种基于指令式的编辑器,不需要鼠标,也没有菜单,仅仅使用键盘来完成所有的编辑工作。
需要先安装 VIM 编辑器,命令如下:

sudo apt-get install vim

VIM 默认是以只读模式打开的文档,因此我们要切换到输入模式,切换到输入模式的命令如下:

切换输入指令:

指令 介绍
i 在当前光标所在字符的前面,转为输入模式。
I 在当前光标所在行的行首转换为输入模式。
a 在当前光标所在字符的后面,转为输入模式。
A 在光标所在行的行尾,转换为输入模式。
o 在当前光标所在行的下方,新建一行,并转为输入模式。
O 在当前光标所在行的上方,新建一行,并转为输入模式。
s 删除光标所在字符。
r 替换光标处字符。

移动光标指令:

指令 介绍
h(或左方向键) 光标左移一个字符。
l(或右方向键) 光标右移一个字符。
j(或下方向键) 光标下移一行。
k(或上方向键) 光标上移一行。
nG 光标移动到第 n 行首。
n+ 光标下移 n 行。
n- 光标上移 n 行。

屏幕翻滚指令:

指令 介绍
Ctrl+b 屏幕向上翻一页,相当于上一页。
Ctrl+f 屏幕向下翻一页,相当于下一页。

复制、删除和粘贴指令:

指令 介绍
cc 删除整行,并且修改整行内容。
dd 删除改行,不提供修改功能。
ndd 删除当前行向下 n 行。
x 删除光标所在的字符。
X 删除光标前面的一个字符。
yy 复制当前行.
nyy 复制当前行及其下面 n 行。
p 粘贴最近复制的内容。

保存文档的命令是在底行模式中,要先进入到指令模式,进入底行模式的方式是先进入指令模式下,然后在指令模式下输入“:”进入底行模式, 常用的命令如下:

指令 介绍
x 保存当前文档并且退出。
q 退出。
w 保存文档。
q! 退出 VI/VIM,不保存文档。

GCC 编译器

gcc 命令

gcc 命令格式如下:

gcc [选项] [文件名字]
选项 介绍
-c 只编译不链接为可执行文件,编译器将输入的.c 文件编译为.o 的目标文件。
-o<输出文件名> 用来指定编译结束以后的输出文件名,如果不使用这个选项的话 GCC 默认编译出来的可执行文件名字为 a.out。
-g 添加调试信息,如果要使用调试工具(如 GDB)的话就必须加入此选项,此选项指示编译的时候生成调试所需的符号信息。
-O 对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进行优化,这样产生的可执行文件执行效率就高。
-O2 比-O 更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。

编译流程

GCC 编译器的编译流程是:预处理、汇编、编译和链接。预处理就是对程序中的宏定义等相关的内容先进行前期的处理。汇编是先将 C 文件转换为汇编文件。当 C 文件转换为汇编文件以后就是文件编译了,编译过程就是将 C 源文件编译成.o 结尾的目标文件。编译生成的.o 文件不能直接执行,而是需要最后的链接,如果你的工程有很多个 c 源文件的话最终就会有很多.o 文件,将这些.o 文件链接在一起形成完整的一个可执行文件。

编程第一课:hello world!

编写代码包括两部分:代码编写和编译。

创建一个test.c编写代码:

 #include "stdio.h"int main(){    printf("hello_Word!\n");}

然后进行编译

gcc test.c -o test

输入ls命令进行查看,此时已经生成了test可执行文件。

此时进行运行可执行文件即可打印出编程实现的结果:

Makefile

使用 GCC 编译器在 Linux 进行 C 语言编译,通过在终端执行 gcc 命令来完成 C
文件的编译,在使用GCC进行编程的时候,实际上的编程流程是先把c文件进行编译,生成.o文件,然后将所有编译出来的.o文件进行重新编译执行成可执行文件。

在进行编译的过程中,可能会碰到修改过后不知道哪个的文件被修改了,或者希望自己只把个别文件进行编译等情况,上述情况使用GCC编译器也可以进行解决,但是,GCC编译默认情况下是对所有的文件进行编译,一旦工程量变大这将花费大量的时间,通样,GCC也可以对指定文件进行编译,但是操作过于繁琐,大部分都是重复性的工作,所以就有了Makefile这样的工具解决编译中的重复操作的问题,提高开发的效率。

示例工程

编写一个实验makefile的示例工程用于学习makefile的使用。

main.c

#include <stdio.h>
#include "test.h"
#include "test1.h"
void main(){test1();test2();
}

test.h

#ifndef _TEST_H_
#define _TEST_H_
#include <stdio.h>
void test1();#endif // !_TEST_H_

test.c

#include "test.h"
void test1(){printf("this is test1\n");
}

test1.h

#ifndef _TEST1_H_
#define _TEST1_H_
#include <stdio.h>
void test2();
#endif // !_TEST_H_

test1.c

#include "test1.h"
void test2(){printf("this is test2\n");
}

Makefile格式

Makefile 里面是由一系列的规则组成的,这些规则格式如下:

目标…... : 依赖文件集合……命令 1命令 2……

比如:

main : main.o test.o test1.ogcc -o main main.o test.o test1.o

这条规则的目标是 main, main.o,test.o 和 test1.o 是生成 main 的依赖文件,如果要更新目标 main,就必须要先更新它的所有依赖文件,如果依赖文件中的任何一个有更新,那么目标也必须更新,“更新”就是执行一遍规则中的命令列表。

为上述demo工程创建一个Makefile文件,在终端中键入vim Makefile创建Makefile文件,然后在Makefile文件中键入以下内容:

main:main.o test.o test1.ogcc -o main main.o test.o test1.o
main.o:main.cgcc -c main.c
test.o:test.cgcc -c test.c
test1.o:test1.cgcc -c test1.cclean:rm *.orm main

在Makefile文件中,第一条规则会成为默认的目标,默认的目标更新的工作,也即Makefile文件执行就为了完成整个更新工作。在首次编译时,main文件未存在,所以第一条规则会正常执行,同时该规则依赖于main.o test.o test1.o三个.o文件,所以要进行更新三个.o文件,然后Makefile检索到更新.o文件的规则,gcc -c *.c,也即不链接编译.c文件,生成.o。最后一个规则目标是
clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o 文件以及 main,因此,clean 的功能就是完成工程的清理。

总结一下 Make 的执行过程:

  1. make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。
  2. 当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
  3. 当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。

Makefile变量

跟 C 语言一样 Makefile 也支持变量,对于重复输入的内容,可以进行定义变量进行简化Makefile的编写。和C语言不太一样的是,Makefile 中的变量都是
字符串。所以简化上面的Makefile文件后为下所示:

obj = main.o test.o test1.o
main: $(obj)gcc -o main $(obj)
#main:main.o test.o test1.o
#   gcc -o main main.o test.o test1.o
main.o:main.cgcc -c main.c
test.o:test.cgcc -c test.c
test1.o:test1.cgcc -c test1.cclean:rm *.orm main

赋值符 “=”

使用“=”在给变量的赋值的时候,不一定要用已经定义好的值,也可以使用后面定义的值。可以将变量的真实值推到后面去定义,也就是变量的真实值取决于它所引用的变量的最后一次有效值。

赋值符 “:=”

赋值符“:=”和“=”类似,但是不同的是,“:=”不会使用后面定义的变量,只能使用前面已经定义好的,这就是“=”和“:=”两个的区别。

赋值符 “?=”

如果变量前面没有被赋值,那么此变量就是赋值符右边的值,如果前面已经赋过值了,那么就使用前面赋的值。

变量追加 “+=”
Makefile 中的变量是字符串,有时候我们需要给前面已经定义好的变量添加一些字符串进去,此时就要使用到符号“+=”。

模式规则与自动化变量

上述 Makefile 中第 3~8 行是将对应的.c 源文件编译为.o 文件,每一个 C 文件都要写一个对应的规则,如果工程中 C 文件很多,这样操作会增加冗余工作量。所以可以使用 Makefile 中的模式规则,通过模式规则就可以使用一条规则来将所有的.c 文件编译为对应的.o 文件。

模式规则中,至少在规则的目标定定义中要包涵“%”,否则就是一般规则,目标中的“%”表示对文件名的匹配,“%”表示长度任意的非空字符串,比如“%.c”就是所有的以.c 结尾的文件,类似与通配符,a.%.c 就表示以 a.开头,以.c 结束的所有文件。

当“%”出现在目标中的时候,目标中“%”所代表的值决定了依赖中的“%”值,使用方法如下:

%.o : %.c命令

对于规则中把c文件编译为o文件已经简化,为了简化命令操作,同时引入自动化变量的内容。目标和依赖都是一系列的文件,每一次对模式规则进行解析的时候都会是不同的目标和依赖文件,而命令只有一行。自动化变量可以把命令进行通用化处理。

自动化变量 就是这种变量会把模式中所定义的一系列的文件自动的挨个取
出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中。

自动化变量 描述
$@ 规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模式中定义的目标集合。
$% 当目标是函数库的时候表示规则中的目标成员名,如果目标不是函数库文件,那么其值为空。
$< 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%” )定义的, 那么“$<”就是符合模式的一系列的文件集合。
$? 所有比目标新的依赖目标集合,以空格分开。
$^ 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,“$^”会去除重复的依赖文件,值保留一份。
$+ 和“$^”类似,但是当依赖文件存在重复的话不会去除重复的依赖文件。
$* 这个变量表示目标模式中"%"及其之前的部分,如果目标是 test/a.test.c,目 标模式为 a.%.c,那么“$*”就是 test/a.test。

使用自动化变量来完成Makefile,最终代码如下所示:

obj = main.o test.o test1.o
main: $(obj)gcc -o main $(obj)
%.o : %.cgcc -c $<clean:rm *.orm main

Makefile 伪目标

Makefile 有一种特殊的目标——伪目标,一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
使用伪目标的主要是为了避免 Makefile 中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,有时候我们需要编写一个规则用来执行一些命令,但是这个规则不是用来创建文件的。比如在该文件下创建了一个clean文件,而在Makefile中又创建了一个clean的命令,此时执行make clean时,无法进行正常执行Makefile中的清除指令。为了解决这个“共存”问题,可以对clean进行声明为伪目标。

.PHONY : clean

Makefile 条件判断

Makefile 也支持条件判断,语法有两种如下:

<条件关键字><条件为真时执行的语句>
endif

以及:

<条件关键字><条件为真时执行的语句>
else<条件为假时执行的语句>
endif

其中条件关键字有 4 个: ifeq、 ifneq、 ifdef 和 ifndef,这四个关键字其实分为两对、 ifeq 与 ifneq、 ifdef与 ifndef,先来看一下 ifeq 和 ifneq, ifeq 用来判断是否相等, ifneq 就是判断是否不相等, ifeq 用法如下:

ifeq (<参数 1>, <参数 2>)
ifeq ‘<参数 1 >’,‘ <参数 2>’
ifeq “<参数 1>”, “<参数 2>”
ifeq “<参数 1>”, ‘<参数 2>’
ifeq ‘<参数 1>’, “<参数 2>”

上述用法中都是用来比较“参数 1”和“参数 2”是否相同,如果相同则为真,“参数 1”和“参数 2”可以为函数返回值,ifneq 的用法类似。

Makefile 函数

Makefile 支持函数,不支持我们自定义函数,只能使用定义好的函数。函数的用法如下:

$(函数名 参数集合)   or   ${函数名 参数集合}

调用函数和调用普通变量一样,使用符号“”来标识。参数集合是函数的多个参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“”来标识。参数集合是函数的多个参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“”来标识。参数集合是函数的多个参数,参数之间以逗号“,”隔开,函数名和参数之间以“空格”分隔开,函数的调用以“”开头。

函数 subst
函数 subst 用来完成字符串替换,调用形式如下:

$(subst <from>,<to>,<text>)

此函数的功能是将字符串<text>中的<from>内容替换为<to>,函数返回被替换以后的字符串。
函数 patsubst

函数 patsubst 用来完成模式字符串替换,使用方法如下:

$(patsubst <pattern>,<replacement>,<text>)

此函数查找字符串<text>中的单词是否符合模式<pattern>,如果匹配就用<replacement>来替换掉,<pattern>可以使用包括通配符“ %”,表示任意长度的字符串,函数返回值就是替换后的字符串。如果<replacement>中也包涵“%”,那么<replacement>中的“%”将是<pattern>中的那个“%”所代表的字符串。

比如:

$(patsubst %.c,%.o,a.c b.c c.c)

将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o
b.o c.o”。

函数 dir
函数 dir 用来获取目录,使用方法如下:

$(dir <names…>)

此函数用来从文件名序列<names>中提取出目录部分,返回值是文件名序列<names>的目录部分。

函数 notdir
函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下:

$(notdir <names…>)

此函数用与从文件名序列<names>中提取出文件名非目录部分。

函数 foreach
foreach 函数用来完成循环,用法如下:

$(foreach <var>, <list>,<text>)

此函数的意思就是把参数<list>中的单词逐一取出来放到参数<var>中,然后再执行<text>所包含的表达
式。每次<text>都会返回一个字符串,循环的过程中, <text>中所包含的每个字符串会以空格隔开,最后当整个循环结束时, <text>所返回的每个字符串所组成的整个字符串将会是函数 foreach 函数的返回值。

函数 wildcard
通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开,这个时候就要用到函数 wildcard,使用方法如下:

$(wildcard PATTERN…)

比如:

$(wildcard *.c)

上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。

References

  1. 正点原子开发视频
  2. 正点原子ZYNQ之linux开发教程
  3. 跟我一起写 Makefile

ZYNQ - 嵌入式Linux开发 -05- Linux C编程和Makefile相关推荐

  1. 嵌入式物联网开发,linux,单片机32(仅供借鉴,代码不共享 自行编写)

    嵌入式物联网开发,linux,单片机32 一.嵌入式介绍 二.linux系统介绍 1.ubuntu使用安装 以下自行查阅资料学习,内容较多不是一时半会能学完的编程内容,最基本是C语言的熟练掌握 2.s ...

  2. <Linux开发>linux开发工具- 之-TFTP

    <Linux开发>linux开发工具- 之-TFTP tftp命令的作用和nfs命令一样,都是用于通过网络下载东西到DRAM中,只是tftp命令使用的TFTP协议,Ubuntu主机作为TF ...

  3. <Linux开发>linux开发工具- 之-geany编辑器

    <Linux开发>linux开发工具- 之-geany编辑器 安装命令: sudo apt-get install geany 使用方法: 命令: geany 文件路径名 &

  4. <Linux开发>linux开发工具- 之-samba共享文件夹

    <Linux开发>linux开发工具- 之-samba共享文件夹 本文章主要讲解ubuntu下的文件 能够 在windows下访问的实现.目的是方便文件在Windows下操作,便于开发. ...

  5. <Linux开发>linux开发工具- 之-开发使用linux命令记录

    <Linux开发>linux开发工具- 之-开发使用linux命令记录 本文章主要记录开发过程中涉及使用的linux命令. 1.查看磁盘大小分区情况 命令: df -hl` 可查看分区的路 ...

  6. ZYNQ中裸机开发和Linux开发有什么区别?

    ZYNQ架构特征 从高层次的角度去看,Zynq架构包含PS和PL两部分以及之间的互连线.如图2.3所示.互联遵循AXI标准,AXI标准是Arm开发的片上通信技术标准. 将PS和PL集成到一起一方面能够 ...

  7. <Linux开发> linux应用开发-之-uart通信开发例程

    一.简介 串口全称叫做串行接口,串行接口指的是数据一个一个的按顺序传输,通信线路简单.使用两条线即可. 实现双向通信,一条用于发送,一条用于接收.串口通信距离远,但是速度相对会低,串口是一种很常用的工 ...

  8. 【Linux开发】linux设备驱动归纳总结(一):内核的相关基础概念

    linux设备驱动归纳总结(一):内核的相关基础概念 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...

  9. 【嵌入式流媒体开发】Linux ALSA 声卡数据采集与播放

    文章目录 ALSA框架 环境搭建 ALSA 交叉编译移植 alsa-lib alsa-util 配置USB声卡 查看声卡设备并测试 查看音卡: 录音测试 播放测试 音频采集常见参数 WAV文件头 编程 ...

  10. 【嵌入式系统开发05】ARM汇编语言编程入门实践

    本文目的是主要介绍如何搭建并配置Keil嵌入式开发环境,完成一个基于STM32汇编程序的编写.记录build生成的 hex文件各段的大小,了解Hex文件格式及其前8个字节内容含义,学习在没有硬件条件下 ...

最新文章

  1. XmlHttp学习笔记
  2. 算法 分析 (收集)
  3. .NET Core 3.0深入源码理解HttpClientFactory之实战
  4. python中random什么意思_python中的random
  5. Astro Panel Pro for Mac - ps天文景观插件 支持ps2021
  6. 电子游戏市场的演变————零售
  7. java中自定义输入数字格式_Java 创建并使用自定义数字格式、35;###、####.#####和语言环境...
  8. 可怕的春运,烦人的火车票!
  9. 构建程序员快捷代码键盘
  10. 深层网络搜索核心技术研讨
  11. 相机标定 棋盘格 图_【连载2.3.1】结构光系统标定
  12. 云计算基础架构即服务、平台即服务、软件即服务的三种服务类型的介绍
  13. 1118 Birds in Forest (25分)
  14. RT-Thread学习笔记六——线程间通讯(信号量的使用)
  15. java.lang.IllegalStateException: stream has already been operated upon or closed 异常
  16. python在数据分析方面的应用-数据分析应用方面Python和spss有什么区别?
  17. Android-app内部调整字体大小
  18. centos ssh免密登录 多台互相免密登录
  19. 用matlab从rosbag文件里读取rbg图片和depth图片
  20. RestTemplate 发送请求 清除Cookie

热门文章

  1. Python的基本语法(十一)(持续更新)
  2. 利用USRP探索软件无线电(1)
  3. 人到中年这种茶要多喝,越喝血管越干净!坚持一个月,头晕眼花不再来!
  4. 用Python爬取购物节当天热门商品销量数据,看看大家喜欢什么
  5. KBEngine warring项目源码阅读(一) 项目简介和注册
  6. 诺基亚symbian 手册汇编
  7. js 年会大屏_ECharts + Jquery 做大屏展示
  8. 湖北颁发首批测绘资质新证
  9. 红米k40和红米k40pro的区别 哪个好
  10. html粘贴excel表格大小不一样,为什么excel表格粘贴的时候显示因为单元格形状大小不一样无法粘贴呢...