美文网首页
迅为IMX6ULL终结者开发板Ubuntu下C编程入门

迅为IMX6ULL终结者开发板Ubuntu下C编程入门

作者: TL_6cdd | 来源:发表于2020-05-11 11:54 被阅读0次

    我们在 Windows 下使用 C 语言开发的时候,一般都会有支持 Windows 的开发工具,比如我们学习 51 单

    片机或者 STM32,所使用的的 Keil 开发软件。此类开发工具一般都会集编辑、编译于一体,我们只需要编辑

    好代码,电机开发工具的编译按钮,就可以自动为我们编译出可执行的二进制文件了。Ubuntu 下的 C 语言

    开发与 Windows 下的 C 语言开发是不一样的,并没有图形界面的开发工具,而且编辑和编译是分开的。我

    们需要使用文本编辑软件先编写代码,然后使用编译工具(GCC)来编译,最终生成可执行的二进制文件。

    如果我们的工程中有多个源文件,在编译的时候我们通常会通过 Makefile 文件来管理整个工程的源文件。

    本章我们来学习如何在 Ubuntu 下进行 C 语言成的编辑,GCC 编译,Makefile 文件的使用。通过本章学习我

    们可以掌握 Linux 下的 C 语言开发基本方法,为后面的学习做好准备。

    3.1 x Linux下编写第一个 C C 程序 程序

    本章节开始的部分我们介绍了 Ubuntu 下 C 程序开发分成两部分:编辑和编译。Ubuntu 下有很多种文本

    编辑的工具,如 vim、Gedit、Emacs,这里我们使用前面章节介绍的 vim 文本编辑工具来编辑 C 程序。相信

    大家在学习 C 语言的时候都是从“Hello World”这个程序开始的吧,下面我们通过这个程序,来学习下 Linux

    下 C 程序开发的流程。

    3.1.1  代码编写

    首先我们在用户根目录下建立文件夹“work”,用于保存所有 C 语言的程序,运行结果如下图所示:

    然后进入创建的文件夹 work,为了方便管理,我们每个例程都创建单独的文件夹,首先我们创建文件

    夹“hello_world”来保存我们的第一个 C 程序,如下图所示:

    然后进入上图中的 hello_world 文件夹,使用 vi 命令新建文件“main.c”,然后在里面输入下面的代

    码:

    #include 

    int mian(int argc, char *argv[])

    {

    printf("Hello World!\n");

    return 0;

    }

    编写完以后保存并退出 vim 编辑器,然后可以使用 cat 命令查看代码是否保存成功,如下图所示:

    通过上图可以看到代码已经编辑完成了。

    3.1.2  代码编译

    Ubuntu 下使用 gcc 编译器来编译 C 程序,我们在安装 Ubuntu 系统的时候,gcc 编译器 morning 安装好

    了,我们可以在终端输入“gcc -v”来查看下 gcc 的版本,如下图所示:

    通过上图可以看到 gcc 的版本信息,说明我们的 Ubuntu 上已经安装了 gcc 编译器了,下面我们来看看

    怎么通过 gcc 编译我们的第一个 C 程序,我们在终端输入“gcc main.c -o main”,然后回车,会生成 main 文

    件,如下图所示:

    在上面的“gcc main.c -o main”这个命令里面“-o“用来指定编译生成的文件名字,我们指定的是 main,

    所以在图 3.1.2.2 中可以看到生成了文件“main”,如果我们想生成其它的文件名字,我们只需要修改“-o”

    后面的 main(修改成您希望生成的文件名字)。经过前面的步骤已经生成了可执行文件 main,现在我们演

    示下在终端如何运行一个可行性的程序,我们直接在终端当前目录下(可执行文件所在的目录下)输入

    “./main”,然后回车,就可以运行可执行文件 main 了,运行结果如下图所示:

    在上图中运行的命令“./main”,其中的“./”代表当前目录下。我们可以看到在终端打印出了 Hello

    World!。至此,Linux 下的 C 语言编辑和编译的一整套流程我们就介绍完了。

    3.2 c gcc 编译器 编译器

    3.2.1 gcc  命令分析

    在 3.1 节我们已经使用 gcc 编译了 Linux 下的第一个 C 程序,gcc 命令的格式如下:

    gcc [参数] [文件名]

    主要参数说明如下:

    -c 编译、汇编到目标代码(.o),不链接成可执行文件

    -g 生成调试信息

    -o 编译完成后生成的文件名,如果不使用该选项,默认生成 a.out 文件

    -O 对程序进行优化编译,产生的可执行文件执行效率高

    -w 不生成任何警告

    -S 仅编译到汇编语言,不进行汇编和链接

    3.2.2  编译警告错误处理

    我们是 Windows 下使用 Keil 或者其他开发工具,在编译的时候,如果程序有错误,开发工具会提示出

    具体的错误信息,可以很方便的定位到问题点,快速的修改出现的问题,gcc 同样也有类似的功能,下面我

    们来看下 gcc 的错误提示功能,首先我们在 work 目录下建立文件夹“test2”,然后使用 vim 在 test2 文件

    夹创建 main.c 文件夹,在 main.c 文件输入如下代码:

    #include 

    int main(int argc, char *argv[])

    {

    int a;

    a = 1

    printf("a=\n", a);

    return 0;

    }

    上面代码有两处错误:

    第 7 行 最后缺少“;”

    第 9 行 printf 语法不对,应该为:printf("a =%d\n", a);

    我们使用 gcc 编译 main.c,可以看到 gcc 会提示错误信息,如下图所示:

    从上图中可以看到在 mian.c 文件的第 9 行 printf 前面缺少“;”我们在第 7 行“a = 1”后面加上“;”,

    然后继续编译,如下图所示:

    从上图可以看出编译提示语法格式不对,我们把第 9 行修改成“printf("a=%d\n", a);”,然后在继续编

    译,如下图所示:

    我们可以看到这次 gcc 编译通过,最终生成了文件 main。我们在终端执行 main,运行结果如下图所示:

    从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出

    程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。

    从上图可以看到运行的结果和我们涉及到额结果一致,通过本例程可以看到 gcc 编译器不仅可以检测出

    程序的错误,而且还会标记处错误在哪个文件的哪一行,很方便的帮助我们去修改问题。

    3.2.3 gcc  编译流程

    gcc 的编译流程可以分成四个步骤:

    1.预处理,生成预编译文件(.文件)

    2.编译,生成汇编代码(.S 文件)

    3.汇编,生成目标文件(.o 文件)

    4.链接,生成可执行文件

    3 3.3  初识  Makefile

    3.3.1  什么是 Makefile

    在 3.2 章节我们了解了在 Ubuntu 系统下通过 gcc 编译器来编译 C 程序,在我们演示的历程中只有一个

    C 文件,我们直接在终端输入 gcc 的编译命令,就完成了 C 程序的编译。我们在实际开发过程中,如果我们

    的工程有几十个,或者几百几千个 C 文件,我们通过在终端输入 gcc 命令来编译,这显然是不现实的。为

    了解决这个问题我们可以使用“make”命令,它会解析 Makefile 文件中的指令(应该说是规则)来编译整

    个工程。在 Makefile 文件中描述了整个工程的所有文件的编译顺序,编译规则。

    作为一个专业的程序员,一定要掌握 Makefile 的,我们可以通过 Makefile 能了解到整个工程的处理过程

    的。

    由于Makefile涉及到很多的知识点,以至于可以单独写本书来讲述,所以本章我们只是讲解下Makefile

    的基础入门,如果详细的研究 Makefile,可以给大家推荐《跟我一起写 Makefile》这个电子文档,该文档

    已经放在了:i.MX6UL 终结者光盘资料\09_其它参考资料里面了。

    3.3.2  第一个 Makefile

    在本节我们建立这样一个工程,计算两个整形数的和,并将结果在终端显示出来。在这个工程中一共有

    main.c、calc.c 两个 C 文件和 calc.h 这个头文件。其中 main.c 是主文件,calc.c 负责接收 main.c 传过

    来的数据,然后进行相加。main.c 文件的内用如下:

    #include 

    include "calc.h"

    int main(int argc, char *argv[])

    {

    int a = 3, b = 6, sum;

    sum = calc(a, b);

    printf("%d + %d = %d\n", a, b, sum);

    return 0;

    }

    calc.c 文件内容如下:

    #include 

    int calc(int a, ing b)

    {

    return (a+b);

    }

    文件 calc.h 内容如下:

    #ifndef _CALC_H

    #define _CALC_H

    int calc(int a, int b);

    #endif

    上面就是我们这个工程的所有源文件,我们在终端使用 gcc 编译这个工程,在终端输入“gcc main.c

    calc.c -o main”,该命令的意思是用 gcc 对 main.c、calc.c 进行编译,然后输出可执行文件 main,运行

    结果如下图所示:

    通过上图可以看到生成了可执行文件 main,我们在终端运行 main 执行文件,运行结果如下图所示:

    我们可以看到上图的运行结果和我们设计的结果是一致的。由于我们的这个工程是有三个文件,如果

    工程有几百个,几千个的文件,或者如果有一个文件被修改,使用上面的命令将会编译所有的文件,如果

    我们的工程有上万个文件,编译完一次工程所需要的时间就很可怕。最优的方法就是编译过一次以后,如

    果后面在编译,只编译修改的文件,这样就会节约很多时间,因此我们修改下编译方法,命令如下:

    gcc -c main.c

    gcc -c calc.c

    gcc main.o calc.o -o main

    我们在终端输入上面的命令,结果如下图所示:

    上图的第一条和第二条命令里面使用了参数“-c”是把 main.c 和 calc.c 编译成对应的.o 文件,最后

    一条命令是把编译生成的.o 文件链接成可执行文件 main。假如我们修改了 main.c 这个文件。只需将 main.c

    这个一个文件重新编译下,然后在把所有的.o 文件重新链接成可执行文件,对应的命令如下:

    gcc -c main.c

    gcc main.o calc.o -o main

    可是这样还有一个问题,如果需要修改的文件有很多,这样工作量也会不小,所以我们需要一个工具:

    1.如果工程没有编译过,就会把工程中的.c 文件全部编译并连接成可执行文件

    2.如果工程中有某些文件修改了,只编译修改的文件并连接成可执行文件

    3.如果工程中的头文件修改了,那么就要编译所有引用这个头文件的.c 文件,并且连 接成可执

    行文件

    我们开头说的 Makefile 就是完成这个功能的,下面我们在工程中建立一个 Makefile 文件来实现这样的功

    能(注意:文件名字必须为 Makefile,大小写是区分的)。我们使用 vim 创建 Makefile 文件(Makefile

    和我们的 main.c、calc.c 在同一级目录下),然后输入下面的脚本:

    main:main.o calc.o

    gcc -o main main.o calc.o

    main.o:main.c

    gcc -c main.c

    calc.o:calc.c

    gcc -c calc.c

    clean:

    rm -rf *.o

    rm -rf main

    上面脚本缩进的行需要使用“Tab”键缩进,不要使用空格,这是 Makefile 的语法要求,编写完成的脚本

    如下图所示:

    编写好 Makefile,保存并退出,然后我们在终端输入“make”命令来编译我们的工程,make 命令会在

    当前目录下查找“Makefile”文件,如果存在的话就按照 Makefile 里面的规则进行编译,如下图所示:

    通过上图可以看到编译产生了 main.o、calc.o 和 main 执行文件,说明编译成功了。接下来我们修改

    下 main.c 这个文件,如下图所示:

    然后保存并退出,然后在终端输入“make”再次编译下工程,如下图所示:

    通过上图我们可以看到只重新编译了修改的 main.c,并最终重新链接生成可执行文件 main,我们在终

    端运行可执行文件 main,如下图所示:

    从上图的运行结果可以看到最后的结果等于 10 了,和我们程序的设计结果是一样的。

    3.4 e Makefile 语法 语法

    3.4.1  初识 Makefile

    Makefile 文件是由一些列的规则组合而成的,格式如下:

    target(目标文件) ...: prerequisites(依赖的文件) ...

    command(命令)

    ...

    ...

    比如 3.3.2 中写的 Makefile 的规则:

    main.o:main.c

    gcc -c main.c

    这条规则的 main.o 是目标文件(将要生成的文件),main.c 是依赖的文件(生成 main.o 需要的文件),

    “gcc -c main.c”是生成 main.o 需要运行的命令。e Makefile  中每行的脚本如果有缩进的情况,必须使用

    “ Tab ” 键缩进,切记不能使用空格缩进(这是 e Makefile  的语法要求),大家一定要切记!

    下面我们来分析一下图 3.3.2 章节中写的 Makefile 文件,脚本如下:

    1 1 main:main.o calc.o

    2 2 gcc -o main main.o calc.o

    3 3 main.o:main.c

    4 4 gcc -c main.c

    5 5 calc.o:calc.c

    6 6 gcc -c calc.c

    7 7

    8 8 clean:

    9 9 rm -rf *.o

    10 rm -rf main

    该脚本一共有 4 条规则,1、2 行是第一条规则,3、4 行是第二条规则,5、6 是第三条规则 8、9、10

    是第四条规则。我们在运行 make 命令的时候,会解析当前目录下的这个 Makefile 文件里面的规则,首先

    解析第一条规则,第一条规则中的目标文件是 main,只要完成了该目标文件的更新,整个 Makefile 的功能

    就完成了。在第一次编译的时候,由于目标文件 main 不存在,则会解析第一条规则,第一条规则依赖文件

    main.o、calc.o,make 命令会检查当前目录下是否有这两个.o 文件,经过检查发现没有,然后 make 会在

    Makefile 中查找分别以 main.o、calc.o 为目标的规则(第二条,第三条规则)。执行第二条规则依赖的文

    件是 main.c,make 命令检查发现当前目录下有这个文件,然后执行第二条规则的命令“gcc -c main.c”

    生成 main.o 文件。然后执行第三条规则,第三条规则的目标文件是 calc.o,依赖的文件是 calc.c,make

    命令检查发现当前目录下存在该文件,然后执行第三条规则的命令“gcc -c calc.c”生成 calc.o 文件,

    至此第一条规则依赖的 main.o、calc.o;两个文件已经生成了,然后运行第一条规则的命令“gcc -o main

    main.o calc.o”生成 main 文件。因为 make 命令运行的时候会从 Makefile 的第一条规则开始解析,然后

    根据第一条规则的依赖文件去遍历文件中的“对应规则”,然后在根据“对应规则”的依赖文件去遍历“对

    应的规则”,采用这样递归的方式会遍历出完成第一条规则所需要的所有规则。下面我们来看看第四条规

    则的目标文件是 clean,我们通过查看发现该规则与第一条规则没有关联,所以我们在运行 make 命令的时

    候,不会遍历到该规则。我们可以在终端输入“make clean”命令来运行第四条规则,第四条规则没有依

    赖的文件,所以执行运行命令“rm -rf *.o”和“rm -rf main”,这两条命令的功能是删除以.o 问结尾的

    所有文件,删除文件 main,运行如下图所示:

    通过上图可以看到 main.o、mcalc.o 和 main 三个文件已经删除了。通过该规则我们可以清除编译产生

    的文件,实现工程的清理。

    我们再来总结一下 make 命令的执行过程:

    1.make 命令会在当前目录下查找以 Makefile 命名的文件

    2.找到 Makefile 文件,就会按照 Makefile 里面的规则去编译,并生成最终文件

    3.当发现目标文件不存在或者所依赖的文件比目标文件新(修改时间),就会执行规则对应的命令来更新。

    我们可以看到 make 是一个工具,他会通过 Makefile 文件里面的内容来执行具体的编译过程。

    3.4.2 Makefile  的变量

    在 3.3.2 章节中的 Makefile 第一条规则:

    main:main.o calc.o

    gcc -o main main.o calc.o

    在该规则中 main.o、calc.o 这两个文件我们输入了两次,由于我们的额 Makefile 文件内容比较少,

    如果 Makefile 复杂的情况下,这种重复的输入就会非常占用时间,而且修改起来也会很麻烦,为了解决这

    个问题,Makefile 可以使用变量。Makefile 的变量是一个字符串。比如上面的规则我们声明一个变量,叫

    objects,objs 或者是 OBJ,反正不管是什么,只要能够表示 main.o、calc.o 就行了,我们修改上面的规

    1 1 objects = main.o calc.o

    2 2 main

    ( objects)

    3 3 gcc -o main $( objects)

    我们来分析下修改后的规则,首先第一行是我们定义了一个变量 objects,并给赋值“main.o calc.o”,

    第二行、第三行用到了变量 objects。Makefile 中的变量引用方式是“$(变量名)”,变量 objects 的赋值

    使用“=”,Makefile 中变量的赋值还可以使用“:=”、“?=”、“+=”,这四种赋值的区别如下:

    1. “= = ” 赋值符

    我们先在用户根目录的 work 目录下创建一个 Makefile 脚本,输入下面的内容:

    1 ceshi1 = test

    2 ceshi2 = $(ceshi1)

    3 ceshi1 = temp

    4

    5 out:

    6 @echo ceshi2

    (ceshi2)

    第一行我们定义了变量并赋值“test”,第二行定义了变量 ceshi2 并赋值变量 ceshi1,第三行修改变量

    ceshi1 的值为“temp”,第五行、第六行是输出变量 ceshi2 的值。我们在终端输入“make out”命令,如

    下图所示:

    在上图可以看到变量 ceshi2 的值是 temp,也就是变量 ceshi1 最后一次的赋值。

    2. “ := ” 赋值符

    我们修改“=”赋值符中的代码,第二行的“=”改成“:=”,代码如下:

    1 ceshi1 = test

    2 ceshi2 := $(ceshi1)

    3 ceshi1 = temp

    4

    5 out:

    6 @echo ceshi2

    (ceshi2)

    我们在终端输入“make out”命令,如下图所示:

    我们可以看到上图的运行结果输出变量 ceshi2 的值是 test,虽然在第三行我们修改了变量 ceshi1 的

    值,通过本实验我们可以看到“:=”赋值符的功能了。

    3. “ ?= ” 赋值符

    ceshi ?= test

    “?=”赋值符的作用是如果前面没有给变量 ceshi 赋值,那么变量就赋值“test”,如果前面已经赋值了,

    就使用前面的赋值。

    4. “ += ” 赋值符

    objs = main.o

    objs += calc.o

    上面的脚本最后变量 objs 的值是“main.o calc.o”,“+=”赋值符的功能是实现变量的追加。

    3.4.3  条件判断

    使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量

    的值,或是比较变量和常量的值。其语法有下面两种:

    1.

    <条件比较>

    [条件为真时执行的脚本]

    endif

    2.

    <条件比较>

    [条件为真时执行的脚本]

    else

    [条件为假时执行的脚本]

    endif

    条件比较用到的比较关键字有:ifeq、ifneq、ifdef、ifndef。

    ifeq 表示如果比较相等,语法如下:

    ifeq(<参数 1>, <参数 2>)

    ifneq 表示如果不相等,语法如下:

    ifneq(<参数 1>, <参数 2>)

    ifdef 表示如果定义了变量,语法如下:

    ifdef <变量名>

    ifndef 表示如果没有定义变量,语法如下:

    ifndef <变量名>

    3.4.4  使用函数

    在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所

    支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。

    函数的调用很像变量的使用,也是以“$”来标识的,语法如下:

    $(<函数名> <参数集合>)

    或者:

    ${<函数名> <参数集合>}

    函数名和参数集合之间以空格分隔,参数集合的参数通过逗号分隔。函数调用以“$”开头,以圆括号或花

    括号把函数名和参数括起。感觉很像一个变量。函数中的参数可以使用变量。为了风格的统一,函数和变

    量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。

    因为统一会更清楚,也会减少一些不必要的麻烦。

    接下来我们介绍几个常用的函数,其它的函数可以参考文档《跟我一起写 Makefile》。

    t 1.subst  函数

    $(subst ,,)

    此函数的功能是把字串中的字符串替换成,函数返回被替换过后的字符串。如下示例:

    $(subst ee,EE,feet on the street)

    以上脚本实现把字符串“feet on the street”中的“ee”字符串替换成“EE”字符串,替换后的字符串

    为“feet on the strEEt”。

    . 2. t patsubst  函数

    $(patsubst ,,)

    此函数的功能是查找中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式

    ,如果匹配的话,则以替换。这里可以包括通配符“%”,表示任意长

    度的字串。如果中也包含“%”,那么中的这个“%”将是中的那个

    “%”所代表的字串。(可以用“\”来转义,以“\%” 来表示真实含义的“%”字符)。函数返回被替换

    过后的字符串。如下示例:

    $(patsubst %.c,%.o,x.c bar.c)

    以上脚本实现把字串“x.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.o bar.o”

    p 3.strip  函数

    $(strip )

    此函数的功能是去掉字串中开头和结尾的空字符,函数返回被去掉空格的字符串值。如下示例:

    $(strip a b c )

    以上脚本实现把字串“a b c ”去掉开头和结尾的空格,结果是“a b c”。

    . 4. g findstring  函数

    $(findstring ,)

    此函数的功能是在字串中查找字串,如果找到,那么返回,否则返回空字符串,如下示

    例:

    $(findstring a,a b c)

    $(findstring a,b c)

    以上脚本,第一个返回“a”字符串,第二个返回空字符串。

    r 5.dir  函数

    $(dir )

    此函数的功能是从文件名序列中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部

    分。如果没有反斜杠,那么返回“./”。返回文件名序列的目录部分,如下示例:

    $(dir src/foo.c hacks)

    以上脚本运行结果返回“src/”。

    . 6. r notdir  函数

    $(notdir )

    此函数的功能是从文件名序列中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后

    的部分,返回文件名序列的非目录部分,如下示例:

    $(notdir src/foo.c)

    以上脚本返回字符串“foo.c”

    . 7. h foreach  函数

    $(foreach ,,)

    此函数的功能是把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含

    的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,

    最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函

    数的返回值。所以,最好是一个变量名,可以是一个表达式,而中一般会使用这

    个参数来依次枚举中的单词。如下示例:

    names := a b c d

    files := $(foreach n,$(names),$(n).o)

    以上脚本实现$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出

    一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以$(files)的值是“a.o b.o c.o d.o”。

    (注意,foreach 中的参数是一个临时的局部变量,foreach 函数执行完后,参数的变量将不

    在作用,其作用域只在 foreach 函数当中)。

    更多内容关注迅为电子

    相关文章

      网友评论

          本文标题:迅为IMX6ULL终结者开发板Ubuntu下C编程入门

          本文链接:https://www.haomeiwen.com/subject/zvxunhtx.html