gcc编译器的使用

1 GCC和gcc

1.1 GCC和gcc有什么不同

GCC(GNU Compiler Collection): GNU编译器集合,包含众多的语言编译器(C、C++、Java、D、Objective-C等)。

gcc: 特指GCC中的C语言编译器。

1.2 GCC和嵌入式

多数嵌入式操作系统都基于GCC进行源码编译:Linux、VxWorks、Android等。

在实际开发中,内核开发使用gcc,应用开发使用gcc、g++、gdc等。

1.3 如何用gcc进行交叉编译

交叉编译的概念:
由于嵌入式设备往往资源受限,不可能在嵌入式上直接对设备进行编程。我们往往在开发主机(PC)上对源码进行编译,最终生成目标主机(嵌入式设备)的可执行程序,这就是交叉编译的概念。

gcc如何进行交叉编译:

  • 配置目标主机的编译工具链(如:arm-linux)
  • 配置工具链的版本
    • 根据具体的目标代码选择相应的工具链版本。
    • 正确使用关于硬件体系结构的特殊编译选项。
      在这里插入图片描述

2 gcc编译过程

一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)和链接(linking)等4步才能变成可执行文件。

对于编译器来说,就可以分为预处理器、编译器、汇编器和链接器。
在这里插入图片描述

2.1 gcc分步编译过程

在这里插入图片描述
在这里插入图片描述

预处理:

gcc -E -o hello.i hello.c

预处理主要做了如下事情:

  • 处理所有的注释,以空格代替。
  • 将所有的#define删除,并且展开所有的宏定义。
  • 处理条件编译指令#if,#ifdef,#elif,#else,#endif
  • 处理#include,展开被包含的文件
  • 保留编译器需要使用的#pragma指令

编译:

gcc -S -o hello.s hello.i

编译会做如下事情:

  • 对预处理文件进行词法分析,语法分析和语义分析
    • 词法分析:分析关键字、标识符、立即数是否合法
    • 语法分析:分析表达式是否遵循语法规则
    • 语义分析:在语法分析的基础上进一步分析表达式是否合法
  • 分析结束后进行代码优化生成相应的汇编代码文件

汇编:

gcc -c -o hello.o hello.s

汇编:

  • 汇编器将汇编代码转变成机器可以执行的指令
  • 每条汇编语句几乎都对应一条机器指令

链接:

gcc -o hello hello.o xxx.o

链接器的主要作用就是把各个模块之间相互引用的部分处理好,使得各个模块之间能够正确的衔接。

预处理和编译由cc1命令完成,汇编由as命令完成,链接由collect2命令完成。

如下编译过程中输出的细节,编译时加上-v选项即可查看到(整体编译,直接输出可执行文件):

cc1 main.c -o /tmp/ccXCx1YG.s
as         -o /tmp/ccZfdaDo.o /tmp/ccXCx1YG.s
 
cc1 sub.c -o /tmp/ccXCx1YG.s
as         -o /tmp/ccn8Cjq6.o /tmp/ccXCx1YG.s
 
collect2 -o test /tmp/ccZfdaDo.o /tmp/ccn8Cjq6.o ....

2.2 编译多个文件

// ① 一起编译、链接:
gcc  -o test  main.c  sub.c
// ② 分开编译,统一链接:
gcc -c -o main.o  main.c
gcc -c -o sub.o   sub.c
gcc -o test main.o sub.o

2.3 gcc默认是进行动态链接的

gcc  -o test  test.c

对于如上命令,gcc默认编译出来的可执行文件是动态链接的。如果需要静态链接,则需要按如下形式进行编译:

gcc -static test test.c

如果是对于自己的C文件,只要不编译生成动态库,都是静态链接到可执行文件中的。


3 常用编译选项

常用选项描述
-E预处理,开发过程中想快速确定某个宏可以使用“-E -dM”
-c把预处理、编译、汇编都做了,但是不链接
-o指定输出文件
-I指定头文件目录
-L指定链接时库文件目录
-l指定链接哪一个库文件

编译选项举例:

gcc -E main.c   // 查看预处理结果,比如头文件是哪个
gcc -E -dM main.c  > 1.txt  // 把所有的宏展开,存在1.txt里
gcc -Wp,-MD,abc.dep -c -o main.o main.c  // 生成依赖文件abc.dep,后面Makefile会用(-Wp,-MD,abc.dep之间不能有任何空格)
gcc -Wl,-Map=main.map main.c	// 生成映射文件
gcc -D'TEST="HELLO"' main.c		// 宏定义
gcc -M main.c	// 获取目标的完成依赖文件
gcc -MM main.c	// 获取目标的部分依赖文件

4 制作、使用库文件

4.1 制作、使用静态库

静态链接由链接器在链接时将库的内容直接加入到可执行程序中。
在这里插入图片描述

gcc -c -o main.o  main.c
gcc -c -o sub.o   sub.c
ar  crs  libsub.a  sub.o  sub2.o  sub3.o(可以使用多个.o生成静态库)	// ar -q是一样的
gcc  -o  test  main.o  libsub.a  (如果.a不在当前目录下,需要指定它的绝对或相对路径)
// gcc -o test main.o -L. -lsub	(上面的一条命令也可以写成这种形式)

运行:不需要把静态库libsub.a放到板子上。

4.2 制作、使用动态库

对于动态链接来说:

  • 可执行程序在运行时才动态加载库进行链接。
  • 库的内容不会进入可执行程序当中。
    在这里插入图片描述

制作、编译:

gcc -c -o main.o  main.c
gcc -c -o sub.o   sub.c
gcc -shared  -o libsub.so  sub.o  sub2.o  sub3.o(可以使用多个.o生成动态库)
# 如果是64位环境则必须加上-fPIC选项
# gcc -shared -fPIC -o libsub.so  sub.o  sub2.o  sub3.o
gcc -o test main.o  -lsub  -L /libsub.so/所在目录/

运行:
① 先把libusb.so放到PC/lib目录,然后就可以运行test程序。
② 如果不想把libusb.so放到/lib,也可以放在某个目录比如/a,然后如下执行:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a  
./test

我们也可以通过另一种方式使用动态库:

// 编译动态库源码
gcc -shared dlib.c -o dlib.so
// 使用动态库编译
gcc main.c -ldl -o main.out

通过这种方式需要如下几个系统调用的配合:

  • dlopen:打开动态库
  • dlsym:查找动态库中的函数并返回调用地址
  • dlclose:关闭动态库文件
#include <stdio.h>
#include <dlfcn.h>

int main()
{
    void* pdlib = dlopen("./dlib.so", RTLD_LAZY);

    char* (*pname)();
    int (*padd)(int, int);

    if( pdlib != NULL )
    {
        pname = dlsym(pdlib, "name");
        padd = dlsym(pdlib, "add");
 
        if( (pname != NULL) && (padd != NULL) )
        {
            printf("Name: %s\n", pname());
            printf("Result: %d\n", padd(2, 3));
        }

        dlclose(pdlib);
    }
    else
    {
        printf("Cannot open lib ...\n");
    }

    return 0;
}



参考资料:

  1. 韦东山全系列视频第1季快速入门
  2. 嵌入式操作系统原理课
  3. C语言进阶剖析教程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值