linux下C编写及编译、运行、Makefile的使用

  本文参照正点原子文档,著作权归该作者所有,如有侵权请告知。
一、简介  
   Windows 下我们可以使用各种各样的 IDE 进行编程,这些IDE很多都可以直接编译运行。但在 Linux下这两部分是分开的,大多只是编辑器(如Vim),如果要编译的话就需要用到 GCC 编译器,使用 GCC 编译器肯定就要接触到 Makefile。
二、代码编写
  1、设置Vi:其TAB键默认跳转8个空格,并且默认不显示行号,我们C编辑常用4个字节,因此需要做如下设置。打开文件/etc/vim/vimrc,在该文件最后输入代码:set ts=4    再起一行输入 :set nu即可。光标只能通过上下左右键切换,set mouse=a  可以用鼠标定位光标。
  2、编辑代码:新建一个.C文件,使用vim打开,或使用vi +文件名新建并打开,此时为指令模式,我们输入i指令进入编辑模式,编辑完成按ESC进入指令模式,然后输入冒号,q退出,w保存,wq保存退出。
三、代码编译
  1、gcc简介:Ubuntu 下的 C 语言编译器是 GCC,我们 安装Ubuntu 的时候会默认安装,通过  gcc -v  可查看版本号。也可以看到该编译器可编译的目标对象:x86_64-linux-gnu,表示Ubuntu自带的编译器是针对X86架构的,编译的可执行文件只能运行于X86架构的CPU,如果想要编译在ARM架构上运行的程序就需要安装针对ARM架构的GCC编译器,俗称交叉编译器

    

  2、gcc编译代码:gcc main.c  编译完成后默认生成a.out 的可执行文件,执行方法:“./+可执行文件”。使用-o 可指定生成的可执行文件名字:gcc main.c -o main

    

    file +文件名  如file a.out   可查看该可执行文件运行于哪个架构。

  (1)、编译流程:预处理、编译、汇编和链接。预处理就是展开所有头文件、替换程序中的宏、解析条件编译并添加到文件中。编译是将经过预编译处理的代码编译成汇编代码,也就是我们常说的程序编译。汇编就是将汇编语言文件编译成二进制目标文件。链接就是将汇编出来的多个二进制目标文件链接在一起,形成最终的可执行文件,链接的时候还会涉及到静态库和动态库等问题。

  3、gcc命令——gcc [选项]  [文件名字]

     主要参数:-c:只编译不链接为可执行文件,编译器将输入的.c 文件编译为.o 的目标文件。

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

四、Makefile 

  1、简介:当有多个文件的时候用终端输入 GCC 命令的方法显然是不现实的。为此提出了一个解决大工程的编译工具:make,描述哪些文件需要编译、哪些需要重新编译的文件就叫做Makefile,跟脚本文件一样, 也可以执行系统命令。只需要一个 make命令即可完成整个工程的编译。

  2、gcc编译多个.c文件示例:假设我们有两个.c文件(calcu.c,input.c),以及对应的头文件(calcu.h,input.h),还有一个main.c文件。此时我们可以使用命令:gcc main.c calcu.c input.c -o main即可对这几个文件进行编译生成可执行文件mian。使用这种方式编译时,即使只有一个文件改变也会编译所有文件。

  但我们想要的是第一次编译整个工程,当工程比较大时,为节约时间,后面只编译被修改过的文件。命令如下:

    

    

   假如现在修改了 calcu.c 文件,只需要将 caclue.c 这个文件重新编译成.o 文件,然后在将所有的.o 文件链接成可执行文件即,命令如下:

    

  这样存在一个问题,如果修改的文件多,我们可能不记得哪个文件被修改过,然后忘记编译,为此我们需要这样一个工具:                

    (1)、如果工程没有编译过,那么所有.c 文件都要编译并且链接成可执行程序。
    (2)、如果工程中只有个别 C 文件被修改了,那么只编译这些被修改的 C 文件。
    (3)、如果工程的头文件被修改了,那么我们需要编译所有引用这个头文件的 C 文件,并且链接成可执行文件。 
  很明显,能够完成这个功能的就是Makefile了,在.c源文件同一目录下创建名为“Makefile”的文件,文件名区分大小写,输入如下代码:

      图1

  (1)、Makefile语法规则:

     

     比如下面这条规则:

     

     这条规则的目标是 main,main.o、input.o 和 calcu.o 是生成 main 的依赖文件。 命令列表中的每条命令必须以 TAB 键开始,不能使用空格。make 命令会在当前目录下查找是否存在“Makefile”这个文件,如果存在则会为 Makefile 中的每个以 TAB 键开始的命令创建一个 Shell 进程去执行。 

      上面图1我们编写的Makefile代码中一共有5条规则,1~2行:第一条规则,3~4行第二条规则,5~6行第三条规则,7~8行第四条规则,10~12行第五条规则。其执行命令如下:

    make 工具就是在 Makefile 中一层一层的查找依赖关系,并执行相应的命令,编译出最终的可执行文件。

    a、第一条规则依赖于 main.o、input.o 、 calcu.o 这个三个.o 文件,如果没有这三个文件(或者目标文件.o所依赖的.c文件比目标文件新)的话就会执行后面的命令来更新目标。

    b、更新 main.o 的是第二条规则,第二条规则里面的命令为“gcc–c main.c”,即编译 main.c但不链接,生成 main.o,其它两个.o 文件同理。

    c、最后一个规则目标是 clean,它没有依赖文件,因此默认依赖文件都是最新生成的,执行“make clean”命令后会删除当前目录下所有的.o 文件以及 main。

  编译:

  编译时如果提示如下错误:可能原因为:(1)、Makefile命令缩进没有使用TAB键,(2)、VI编辑器中使用了空格代替TAB键,在/etc/vim/vimrc配置文件最后加上:set noexpandtab,修改完后随便改一个.C文件(加几个空格)再次编译试试。

    

  编译成功如下 :会根据Makefile指示,在该目录下生成各种.O文件,及可执行文件,如果要清除这些文件则使用命令:make clean

    

  (2)、Makefile 变量

      

      上述Makefile语句中,main.o input.o 和 calcue.o 这三个依赖文件,我们输入了两遍,为避免重复输入,Makefile 支持变量。其变量都是字符串,类似 C 语言中的宏。

      

    a、赋值符“=” 

      使用“=”给变量赋值的时候,不一定使用定义好的值,也可以使用后面定义的值,取决于它所引用的变量的最后一次有效值,如下:

      

      按照C语言的经验我们会认为输出是:zzk,实际输出如下:

      

    b、赋值符“:=”  ——表示变量只能使用前面的赋值,不会使用后面的赋值,如上,改为":="后就会打印zzk,这就是“=”和“:=”两个的区别。 

    c、赋值符“?=”

      表示如果变量 curname 前面没有被赋值,那么此变量就用现在的值“zuozhongkai”,如果前面被赋值过就用前面的值。

     d、变量追加——使用符号“+=”

      

  (3)、Makefile模式规则
      上述图1中第3~8行是将对应的.C源文件编译为.O文件,每一个C文件都要写一个对应的规则,如果C文件很多显然不行。因此,通过模式规则可以将所有的.c文件编译为对应的.o文件。
      模式规则中,使用“%”对文件名的匹配,比如“%.c”表示所有以.c 结尾的文件,类似通配符,a.%.c 就表示以 a.开头,以.c 结束的所有文件。如下,可将第3~8行替换为:   

      

  (4)、Makefile自动化变量

      如何通过一行命令来从不同的依赖文件中生成对应的目标?自动化变量就是完成这个功能的,这种变量会把模式中所定义的一系列的文件自动的挨个取出,自动化变量只应该出现在规则的命令中,常用的自动化变量如下表 : 

      

      常用的三种:$@、$<、$^,使用自动化变量完成上述示例代码 中的 Makefile,使用命令:gcc -c $<  即可,如下: 

      

      至此,一个精简的Makefile文件就诞生了。

   (5)、Makefile伪目标

      Makefile 有一种特殊的目标——伪目标,为了避免 Makefile 中定义的执行命令的目标和工作目录下的实际文件出现名字冲突。一般目标名都是要生成的文件名字,而伪目标不是。 

      有时我们需要编写一些规则来执行一些命令,比如 clean:但是并不会创建clean文件,如果创建一个名为clean的文件,执行make clean时,规则因为没有依赖文件,因此后面的rm命令就不会执行,如下:

             

      为避免这个问题,我们可将clean声明为伪目标,如下:

      

       此时就算目录下有同名文件,使用make clean也能执行规则后的rm命令,如下:

      

   (6)、Makefile条件判断

      C 语言中通过条件判断语句来执行不同的分支,Makefile 也支持条件判断,语法有两种如下: 

      a、

        

      b、

        

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

        

      上述用法都是比较“参数 1”和“参数 2”是否相等,相等为真,“参数 1”和“参数 2”可以为函数返回值。同理ifneq 则是比较两个参数是否不相等,不相等则为真。 

         则表示:如果"变量名"的值非空,表达式则为真,否则为假,“变量名”同样可以是一个函数的返回值。ifndef同上,含义相反。

  (7)、Makefile 函数使用

      类似 C 语言,Makefile 支持函数,但函数是已经定义好的,我们直接使用,不支持自定义。函数不多,但是绝对够用,用法如下: 

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

      调用函数和引用普通变量一样,都是使用$符号,参数集合是函数的多个参数,参数间用逗号隔开,函数名和参数之间用空格分开。

     a、函数 subst

       函数 subst 用来完成字符串替换,调用形式如:$(subst<from>,<to>,<text>)   此函数的功能是将字符串<text>中的<from>内容替换为<to>,函数返回被替换以后的字符串,如下:   

      

      把字符串“my name is zzk”中的“zzk”替换为“ZZK”,替换完成返回字符串“my name is ZZK”。

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

      

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

      

      将字符串“a.c b.c c.c”中的所有符合“%.c”的字符串,替换为“%.o”,替换完成以后的字符串为“a.o b.o c.o”。
     c、函数 dir
      函数 dir 用来获取目录,使用方法如下: 

       

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

      

      提取文件“/src/a.c”的目录部分,也就是“/src”。 
     d、函数 notdir
      函数 notdir 看名字就是知道去除文件中的目录部分,也就是提取文件名,用法如下: 

      

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

       

      提取文件“/src/a.c”中的非目录部分,也就是文件名“a.c”。 
     e、函数 foreach
      foreach 函数用来完成循环,用法如下: 

       

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

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

       用法如下:

                 

                上面的代码是用来获取当前目录下所有的.c 文件,类似“%”。 
  (8)、makefile输出变量的值(调试用)

      

         执行:make mytest 命令

五、X86架构和ARM架构的区别

  1、简介:CPU只能在给定具体指令的情况下才能工作,不同架构的CPU能够识别的指令集不同。移动端及一些嵌入式系统上的应用并不是直接通过编写CPU指令来运行的,而是通过编程语言来编写的,编译器的目的就是将程序编译成不同指令集的指令,从而在ARM, X86等不同架构的CPU上运行。指令集就相当于软件和硬件之间的桥梁。

  2、本质区别:(1)、X86架构——复杂指令集:电路单元丰富,指令丰富,处理特殊任务效率高,缺点:面积大、功耗高;

          (2)、ARM架构——32位精简指令集(指令数有限):广泛运用于嵌入式系统,电路单元相对较少,制造工艺简单,功耗低(适用于移动通讯领域,便携式电子产品手机平板等),但实现复杂功能效率低(使用多个指令组合实现),ARM 公司本身并不制造 CPU ,而是将处理器架构授权给厂商。

  3、性能:X86结构比ARM结构的系统在性能方面要强得多。其CPU随便就是1G以上、双核、四核等等、而ARM结构的CPU通常才几百兆,最近才出现1G左右的。

  4、操作系统兼容性:几乎所有x86硬件平台都可以直接使用微软的视窗系统及现在流行的几乎所有工具软件;ARM系统几乎都采用Linux的操作系统,GOOGLE开发了开放式的Android系统后,才为ARM的发展提供了强大的支持和动力。

  除此之外,还有一些其他架构的处理器,这里不再赘述。

六、Ubuntu交叉编译工具链安装(ARM)

  1、交叉编译器安装

    Ubuntu 自带的 gcc 编译器是针对 X86 架构的,要编译 ARM 架构的代码,就需要一个可以在 X86 架构的 PC 上运行并且编译 ARM 架构代码的 GCC 编译器,即交叉编译器。

    安装ARM交叉编译链:执行  sudo apt-get install gcc-arm-linux-gnueabihf  命令,执行安装软件包命令前最好先执行: sudo apt-get update  命令来更新Ubuntu软件包库。

  2、交叉编译器报错问题

    使用如上编译器如果出现 error: unrecognized -march target: armv5 错误通常是因为 GCC 不支持 -march=armv5 选项,该选项指定生成代码运行在 ARMv5 指令集下。可能是因为 GCC 版本过低或是没有针对该指令集做过任何优化。可以使用 arm-linux-gnueabi-gcc 交叉编译工具链,它支持ARMv5指令集。而上面的编译器在 ARMv7+ 上启用了硬件浮点支持。

  3、设置交叉编译器环境变量,一般可在以下几个文件中进行:

    (1)、/etc/environment:系统级环境变量,对所有用户都有效

        如:CROSS_COMPILE=/path/to/toolchain/bin/arm-linux-gnueabihf-

    (2)、/etc/profile:系统级环境变量,用户登录时执行,通过修改 PATH 等环境变量可以设置编译器的路径,用户一般配置这个文件。

    (3)、~/.bashrc 用户级环境变量,每次 shell 启动时执行,通过修改 PATH 等环境变量可以设置编译器的路径

        如:export CROSS_COMPILE=/path/to/toolchain/bin/arm-linux-gnueabihf-

    (4)、/etc/bash.bashrc:系统级 Bash 配置文件,在每次 shell 启动时执行,是 /root/.bashrc 的全局版本

    配置完成需要执行 source +配置路径让更改生效,如:source /etc/profile

    可使用 echo $PATH 命令来检查 PATH 环境变量中包含哪些路径。

  4、如何修改只读文件权限:

     如图我们再用vim编辑~/.bash_profile提示如下:

     ,可输入 :w !sudo tee %  强制保存,会提示警告,按提示退出即可,如果 :q 不能退出,使用 :qa! ,退出再打开可以看到已经更改的。谨慎使用(可能会导致数据丢失或系统损坏)。

    安全起见可以先备份,可使用 sudo chmod a+w filename 更改只读文件的权限,编辑保存完文件后将可写权限删除sudo chmod a-w filename 恢复为只读状态。ls +文件名 -l 可查看权限。

 

  

 

 

 

 
 
posted @ 2023-04-19 12:23  耿通宇  阅读(7559)  评论(0编辑  收藏  举报