您当前的位置: 首页 > 

韦东山

暂无认证

  • 3浏览

    0关注

    506博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

6_Makefile与GCC

韦东山 发布时间:2022-01-13 16:02:34 ,浏览量:3

第六章 Makefile与GCC 6.1 交叉编译器 6.1.1 什么是交叉编译

​ 简单地说,我们在PC机上编译程序时,这些程序是在PC机上运行的。我们想让一个程序在ARM板子上运行,怎么办?

​ ARM板性能越来越强,可以认为ARM板就相当于一台PC,当然可以在ARM板上安装开发工具,比如安装ARM版本的GCC,这样就可以在ARM板上编译程序,在ARM板上直接运行这个程序。

​ 但是,有些ARM板性能弱,或者即使它的性能很强也强不过PC机,所以更多时候我们是在PC机上开发、编译程序,再把这个程序下载到ARM板上去运行。

​ 这就引入一个问题:

​ 1) 我们使用工具比如说gcc编译出的程序是给PC机用的,这程序里的指令是X86指令。

​ 2)那么能否使用同一套工具给ARM板编译程序?

​ 显示不行,因为X86的指令肯定不能在ARM板子上运行。所以我们需要使用另一套工具:交叉编译工具链。

​ 为何叫“交叉”?

​ 首先,我们是在PC机上使用这套工具链来编译程序;

​ 然后再把程序下载到ARM板运行;

​ 如果程序不对,需要回到PC机修改程序、编译程序,再把程序下载到ARM板上运行、验证。如此重复。

​ 在这个过程中,我们一会在PC上写程序、编译程序,一会在ARM板上运行、验证,中间来来回回不断重复,所以称之为“交叉”。对于所用的工具链,它是在PC机上给ARM板编译程序,称之为“交叉工具链”。

​ 有很多种交叉工具链,举例如下:

​ 1) Ubuntu平台:交叉工具链有arm-linux-gcc编译器

​ 2) Windows 平台:利用ADS(ARM开发环境),使用armcc编译器。

​ 3) Windows平台:利用cygwin环境,运行arm-elf-gcc编译器。

6.1.2 为什么需要使用交叉编译

​ 1) 因为有些目的平台上不允许或不能够安装所需要的编译器,而我们又需要这个编译器的某些功能;

​ 2) 因为有些目的平台上的资源贫乏,无法运行我们所需要编译器;

​ 3) 因为目的平台还没有建立,连操作系统都没有,根本谈不上运行什么编译器。

6.1.3 验证实例

​ 下面这个例子,我们准备源文件main.c,然后我们采用gcc编译后可执行程序放在目标板上运行看看是否能运行起来,如下:

​ main.c

01 	#include 
02
03	int main()
04	{
05		printf("100ask\n");
06		return 0;
07	}

​ 在虚拟机编译运行:

$ gcc main.c –o 100ask
$ ./100ask
100ask
$

​ 在上面的运行结果,没有任问题,然后我们将这个可执行程序放到目标板上,如下:

$ chmod 777 100ask
$ ./100ask
./100ask: line 1: syntax error: unexpected “(”
$

​ 报错无法运行。说明为X86平台制作的可执行文件,不能在其他架构平台上运行。交叉编译就是为了解决这个问题。

​ 为了方便实验,我们在Ubuntu中使用gcc来做实验,如果想使用交叉编译,参考章节《第二章1.2 安装SDK、设置工具链》,安装好工具链,设置好环境变量后,将所有的gcc替换为arm-linux- gcc就可以完成交叉编译。

​ 其中:

​ gcc是在x86架构指令用的。

​ arm-linux- gcc是RSIC(精简指令集)ARM架构上面使用。

​ 他们会把源程序编译出不同的汇编指令然后生成不同平台的可执行文件。

6.2 gcc编译器1_gcc常用选项__gcc编译过程详解 6.2.1 gcc编译过程详解

​ 一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和连接(linking)等4步才能生成可执行文件,编译流程图如下。

6.1.2.1 预处理:

​ C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。

6.1.2.2 编译:

​ 对预处理后的源码进行词法和语法分析,生成目标系统的汇编代码文件,后缀名为“.s”。

6.1.2.3 汇编:

​ 对汇编代码进行优化,生成目标代码文件,后缀名为“.o”。

6.1.2.4 链接:

​ 解析目标代码中的外部引用,将多个目标代码文件连接为一个可执行文件。

​ 编译器利用这4个步骤中的一个或多个来处理输入文件,源文件的后缀名表示源文件所用的语言,后缀名控制着编译器的缺省动作

后缀名语言种类后期操作.cC源程序预处理、编译、汇编.CC++源程序预处理、编译、汇编.ccC++源程序预处理、编译、汇编.cxxC++源程序预处理、编译、汇编.mObjective-C源程序预处理、编译、汇编.i预处理后的C文件编译、汇编.ii预处理后的C++文件编译、汇编.s汇编语言源程序汇编.S汇编语言源程序预处理、汇编.h预处理器文件通常不出现在命令行上

​ 其他后缀名的文件被传递给连接器(linker),通常包括:

​ .o:目标文件(Object file,OBJ文件)

​ .a:归档库文件(Archive file)

​ 在编译过程中,除非使用了“-c”,“-S”或“-E”选项(或者编译错误阻止了完整的过程),否则最后的步骤总是连接。在连接阶段中,所有对应于源程序的.o文件,“-l”选项指定的库文件,无法识别的文件名(包括指定的“.o”目标文件和“.a”库文件)按命令行中的顺序传递给连接器。

6.2.2 gcc命令

​ gcc命令格式是:

gcc [选项] 文件列表

​ gcc命令用于实现c程序编译的全过程。文件列表参数指定了gcc的输入文件,选项用于定制gcc的行为。gcc根据选项的规则将输入文件编译生成适当的输出文件。

​ gcc的选项非常多,常用的选项,它们大致可以分为以下几类 。并且使用一个例子来描述这些选项,创建一个mian.c源文件,代码为如下:

​ main.c:

01 	#include 
02
03	#define HUNDRED 100
04
05	int main()
06	{
07		printf("%d ask\n",HUNDRED);
08		return 0;
09	}

注明: 代码目录在裸机Git仓库 NoosProgramProject/ (6_Makefile与GCC/001_gcc_01001_gcc_01)文件夹下。

6.2.2.1 过程控制选项

​ 过程控制选项用于控制gcc的编译过程。无过程控制选项时,gcc将默认执行全部编译过程,产生可执行代码。常用的过程控制选项有:

​ (1)预处理选项(-E)

​ C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。使用例子如下:

$ gcc -E main.c -o main.i

​ 运行结果,生成main.i,main.i的内容(由于头文件展开内容过多,我将截取部分关键代码):

extern int ftrylockfile (FILE *__stream) __attribute__  ((__nothrow__ , __leaf__)) ;

extern void funlockfile (FILE *__stream) __attribute__  ((__nothrow__ , __leaf__));

# 942 "/usr/include/stdio.h" 3 4

# 2 "main.c" 2

# 5 "main.c"

int main()

{

printf("%d ask\n",100);

return 0;

}

你会发现头文件被展开和printf函数中调用HUNDRED这个宏被展开。

​ (2)编译选项(-S)

​ 编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码。使用例子如下:

$ gcc -S main.c -o main.s

​ 运行结果,生成main.s,main.s的内容:

  1         .file   "main.c"
  2         .text
  3         .section        .rodata
  4 .LC0:
  5         .string "%d ask\n"
  6         .text
  7         .globl  main
  8         .type   main, @function
  9 main:
 10 .LFB0:
 11         .cfi_startproc
 12         pushq   %rbp
 13         .cfi_def_cfa_offset 16
 14         .cfi_offset 6, -16
 15         movq    %rsp, %rbp
 16         .cfi_def_cfa_register 6
 17         movl    $100, %esi
 18         leaq    .LC0(%rip), %rdi
 19         movl    $0, %eax
 20         call    printf@PLT
 21         movl    $0, %eax
 22         popq    %rbp
 23         .cfi_def_cfa 7, 8
 24         ret
 25         .cfi_endproc
 26 .LFE0:
 27         .size   main, .-main
 28         .ident  "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
 29         .section        .note.GNU-stack,"",@progbits

​ (3)汇编选项(-c)

​ 汇编就是将上述的“.s”文件汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件)

$ gcc -c main.c -o main.o

​ 运行结果,生成main.o(将源文件转为一定格式的机器代码)。

6.2.2.2 输出选项

​ 输出选项用于指定gcc的输出特性等,常用的选项有:

​ (1)输出目标选项(-o filename)

​ -o选项指定生成文件的文件名为filename。使用例子如下

$ gcc main.c -o main

​ 运行结果,生成可执行程序main,如下:

$ ls
main.c main
$ ./main
$ 100 ask

​ 其中,如果无此选项时使用默认的文件名,各编译阶段有各自的默认文件名,可执行文件的默认名为a.out。使用例子如下:

$ gcc main.c

​ 运行结果,生成可执行文件a.out,如下:

$ ls
a.out main.c
$ ./a.out
$ 100 ask

​ (2)输出所有警告选项(-Wall)

​ 显示所有的警告信息,而不是只显示默认类型的警告。建议使用。我们见上面的main.c稍微修改一下,b此节代码目录在裸机Git仓库 NoosProgramProject/(6_Makefile与GCC/001_gcc_02)文件夹下,如下:

​ main.c:

01 	#include 
02
03	#define HUNDRED 100
04
05	int main()
06	{
07		int a = 0;
08		printf("%d ask\n",HUNDRED);
09		return 0;
10	}

​ 编译不添加-Wall选项编译,没有任何警告信息,编译结果如下:

$ gcc main.c -o main.c

​ 编译添加-Wall选项编译,现实所有警告信息,编译结果如下:

$ gcc main.c -Wall -o main.c

main.c: In function ‘main’:

main.c:7:6: warning: unused variable ‘a’ [-Wunused-variable]

 int a=0;

   ^
6.2.2.3 头文件选项

​ 头文件选项(-Idirname)

​ 将dirname目录加入到头文件搜索目录列表中。当gcc在默认的路径中没有找到头文件时,就到本选项指定的目录中去找。在上面的例子中创建一个目录,然后创建一个头文件test.h。然后main.c里面增加#include“test.h”,代码目录在裸机Git仓库 NoosProgramProject/(6_Makefile与GCC/001_gcc_03) 文件夹下,使用例子如下:

$ tree

.

├── inc

│  └── test.h

└── main.c

 

1 directory, 2 files

$

​ test.h:

01 #ifndef __TEST_H

02 #define __TEST_H

03 /*

04   code

05 */

06 #endif

​ 运行结果,这样就可以引用指定文件下的目录的头文件,如下:

$ gcc main.c -I inc -o main

​ 如果不添加头文件选项,编译运行结果,如下:

$ gcc main.c -o main

  main.c:2:18: fatal error: test.h: No such file or directory

  compilation terminated.

​ 会产生错误提示,无法找到test.h头文件。

6.2.2.3 链接库选项

(详细使用方法查看下一节:gcc编译器2_深入讲解链接过程)

​ 1) 添加库文件搜索目录(-Ldirname)

​ 将dirname目录加入到库文件的搜索目录列表中。

​ 2) 加载库名选项(-lname)

​ 加载名为libname.a或libname.so的函数库。例如:-lm表示链接名为libm.so的函数库。

​ 3) 静态库选项(-static)

​ 使用静态库。注意:在命令行中,静态库夹在的库必须位于调用该库的目标文件之后。

6.2.2.4 代码优化选项

​ gcc提供几种不同级别的代码优化方案,分别是0,1,2,3和s级,用-Olevel选项表示。默认0级,即不进行优化。典型的优化选项:

​ (1)-O :基本优化,使代码执行的更快。

​ (2)-O2:胜读优化,产生尽可能小和快的代码。如无特殊要求,不建议使用O2以上的优化。

​ (3)-Os:生成最小的可执行文件,适合用于嵌入式软件。

6.2.2.5 调试选项

​ 代码目录在**git仓库(6_Makefile与GCC/001_gcc_02)**文件夹下

​ gcc支持数种调试选项:

​ -g 产生能被GDB调试器使用的调试信息。

​ 调试例子如下,首先需要编译,操作步骤如下:

$ gcc main.c -g -o main

​ GDB调试示例:

​ (1)run命令

​ 调试运行,使用run命令开始执行被调试的程序,run命令的格式:

​ run [运行参数]

$ gdb -q main		            
关注
打赏
1658827356
查看更多评论
0.1846s