嵌入式之 C 语言编译器(五)

我们在嵌入式的开发中经常会见到 GCC 和 gcc,那么它们两有何不同呢?GCC(GNU Compile Collection) 是指 GNU 编译器集合,包含众多语言的编译器,如 C、C++、Java、D、Objective-C 等;而 gcc 则是特指 GCC 中的 C 语言编译器。那么 GCC 与嵌入式的关系是怎样的呢?多数嵌入式操作系统都是基于 GCC 进行源码编译,如 Linux、VxWorks 以及 Android 等。在实际的开发中,内核相关的开发用的是 gcc,而应用开发用的是 gcc/g++/gdc 等。

下来我们来看看一个嵌入式开发中的高端大气上档次的词语:交叉编译。那么为什么会有交叉编译呢?在以往的嵌入式设备往往都是资源受限的,不可能直接在嵌入式上直接对处理器进行编程。那么此时的解决方案便是在开发主机(PC)上对源码进行编译,最终生成目标主机(嵌入式设备)的可执行程序。gcc 是如何进行交叉编译的呢?1、配置目标主机的编译工具链(如arm-linux);2、配置工具链的具体版本:根据具体的目标代码选择相应的工具链版本,正确使用关于硬件体系的特殊编译选项。下来我们来看看大型企业的嵌入式开发环境是怎样的,如下

这个服务器集群相当于是我们自己公司的内部服务器,版本控制则是指由原来的版本经过我们一些代码的修改之后产生的新版本,用于各个版本的控制的。文件追踪则是指在服务器上面可以看到那部分的代码是具体由哪个人进行改写的,可具体到文件以及部分代码。我们来看看编译器是怎样的,如下

编译器其实是由预处理期、编译器、汇编器以及链接器构成的。我们平时所说的由哪个编译器编译生成的文件,此时的编译器便是指广范围的编译器。那么狭义上的编译器则是指我们在平时所听到的生产一个某语言的编译器,此时的编译器则是指将具体的语言翻译成目标平台代码而已。我们来看看一个 .c 文件是怎样编译成 .o 文件的,具体步骤如下所示

我们看到并不是我们所想象的直接一步就由 .c 文件直接编译成为 .o 可执行文件了,而是经过那么多的步骤才会生成最终的可执行程序的。那么此时便扩展一个问题,我们是如何理解“多语言混合开发”?我们在平时可能会听到多语言混合开发,是指由好几种语言混合进行一个应用程序开发的。那么为什么会产生这种混合的开发方式呢?比如说一个项目是由 C++ 完成的,但是其中的某些部分是可以通过 C# 完成的,此时精通 C++ 的人很少(相应工资就要的很高了),而 C# 的工程师由一大堆,我们就可以需要两个精通 C++ 的工程师和好几个 C# 的工程师来共同完成这个项目,达到以最小的开支完成此项目的效果。或者是你们小组内每个人擅长的语言方向不一样,为了发挥每个人的最大效率便可以采取这种混合开发的方式。下来我们来看看几种多语言混合开发的方式

方式一,如下

此方式是通过由几种语言经过汇编得到目标平台的汇编语言,再由目标平台汇编器统一链接生成可执行程序。行业典型的案例就是 .net framework,它便是由 C#、C++ 以及 VB 混合开发得到的,如下

方式二,如下

它是由各自的语言生成相应的库再通过目标平台链接器统一链接为可执行程序。典型的案例便是 QQ 了,如下

 方式三,如下

它是经过各自的编译器先生成可执行程序 .exe,再通过进程间通信协议进而生成可执行程序。行业案例:Eclipse,如下

下来我们来看看 gcc 关键编译选项。

gcc 关键编译选项一:a> 预处理指令是:gcc -E file.c -o file.i;b > 编译指令:gcc -S file.i -o file.s;c> 汇编指令:gcc -c file.s -o file.o

下来我们来看看效果分别是怎样的

func.h 源码

#include <stdio.h>

void func()
{
#ifdef TEST
    printf("TEST = %s\n", TEST);
#endif

    return;
}

test.c 源码

#include <stdio.h>
#include "func.h"

int g_global = 0;
int g_test = 1;

int main(int argc, char *argv[])
{
    func();
    
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
    
    return 0;
}

我们来看看预处理的效果,打开 test.i 文件看看,开头是这样的

第一行的 1 表示下面的内容是属于 test.c 文件的内容,下面是一些头文件的包含。

# 2 "test.c" 2 的意思是 test.c 头文件的包含已经结束了,# 1 "func.h" 1 表示 func.h 相关内容的开始。最后便是 test.c 文件 main 函数的内容了。下面看看编译指令生成的 .s 文件

都是一些生成的汇编命令。下面来看看最后的汇编指令生成 .o 文件

gcc 关键编译选项二:a> 生成映射文件:gcc -WI,-Map=test.map file.c;b> 宏定义:gcc -D'TEST="test"' file.c;c> 获取系统头文件路径:gcc -v file.c

gcc 关键编译选项三:生成依赖关系。a> 获取目标完整的依赖关系:gcc -M test.c;b> 获取目标的部分依赖关系:gcc -MM test.c

下来我们来看看 -M 和 -MM 的效果分别是怎样的,如下

我们看到包含了那么多的头文件,它的格式类似于 makefile 中的目标与依赖的关系。其中依赖是 test.c 和众多的头文件以及我们自己包含的 func.h 头文件。再来看看 -MM 的效果

我们看到 -MM 的效果是指依赖于 test.c 和 func.h,并没有那些别的头文件。

gcc 关键编译选项四:指定库文件及库文件搜索路径。-L 选项是指定库文件的搜索路径;-l 是指定库文件,如 gcc test.c -L -lfunc

func.c 源码

#include <stdio.h>

void func()
{
#ifdef TEST
    printf("TEST = %s\n", TEST);
#endif

    return;
}

test.c 源码

#include <stdio.h>

int g_global = 0;
int g_test = 1;

int main(int argc, char *argv[])
{
    func();
    
    printf("&g_global = %p\n", &g_global);
    printf("&g_test = %p\n", &g_test);
    printf("&func = %p\n", &func);
    printf("&main = %p\n", &main);
    
    return 0;
}

编译结果如下

我们看到经过 ar crs 命令将 func.o 打包成 libfunc.a 文件后,再通过 gcc test.c -L. -lfunc 命令生成可执行程序 a.out(其中 -L 后面的点代表在当前目录下)。

原文地址:http://blog.51cto.com/12810168/2134673

时间: 2024-10-16 01:27:13

嵌入式之 C 语言编译器(五)的相关文章

嵌入式 Linux C语言(五)——指针与字符串

嵌入式 Linux C语言(五)--指针与字符串 一.字符串简介 1.字符串声明 声明字符串的方式有三种:字面量.字符数组.字符指针. 字符串字面量是用双引号引起来的字符序列,常用来进行初始化,位于字符串字面量池中,字符字面量是用单引号引起来的字符. 字符串字面量池是程序分配的一块内存区域,用来保存组成字符串的字符序列.多次用到一个字符串字面量时,字符串字面量池中通常只保存一份副本,一般来说字符串字面量分配在只读内存中,是不可变的,但是当把编译器有关字面量池的选项关闭时,字符串字面量可能生成多个

嵌入式linux C++语言(五)——友元

嵌入式linux C++语言(五)--友元 面向对象编程的类的设计机制实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,是类与外部的通信接口.在实践中,类外的某些函数需要频繁地访问类的数据成员,将类外的函数定义为类的友元函数.除了友元函数外,还有友元类,两者统称为友元.友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员. 友元可以是一个函数,该函数被称为友元函数. 一.

第5课 嵌入式C语言编译器

1. GCC与gcc (1)GCC:(GNU Compiler Collection) GNU编译器集合,包含众多语言的编译器,如C.C++.Java.D.Objective-C等 (2)gcc:特指GCC中的C语言编译器 (3)GCC与嵌入式:多数嵌入式操作系统都是基于GCC进行源码编译.如Linux.VxWorks.Android等. (4)实际开发中,内核开发一般用gcc,应用开发一般用gcc/g++/gdc 2. 交叉编译 (1)背景 ①嵌入式设备往往资源受限 ②不可能在嵌入式上直接对处

嵌入式 Linux C语言——C语言基础

嵌入式 Linux C语言--C语言基础 一.数据类型 1.基本数据类型 数据类型是创建变量的模型.变量名是连续存储空间的别名,程序中使用变量命名存储空间,通过变量可以使用存储空间.变量所占的内存大小取决于创建变量的数据类型. 2.有符号和无符号 有符号数中数据类型的最高位用于标识数据的符号,最高位为1表示为负数,最高位为0表示为正数. 计算机中有符号数通常使用补码表示,正数的补码为正数本身,负数的补码为负数的绝对值的各位取反后加1. 计算机中无符号数通常使用原码表示,无符号数默认为正数,没有符

嵌入式linux C++语言(四)——类与对象

嵌入式linux C++语言(四)--类与对象 类的设计和使用如下: #include <iostream>#include <stdlib.h>#include <stdio.h>#include <string.h>using namespace std;class Stack{public:    Stack(int size=1024);    ~Stack();    void init();    bool isEmpty();    bool

嵌入式linux C++语言(二)——C++对C语言基础语法的扩展

嵌入式linux C++语言(二)--C++对C语言基础语法的扩展 C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面. 一.类型增强 1.类型检查更严格 在C语言中: const int a = 100; int *p = &a; 在C++语言中: const int a = 100;//必须在定义的时候初始化 const int *p = &a; 在C++语言中不能隐式转换数据类型. error: invalid conversion

嵌入式 Linux C语言(八)——存储类型、作用域、生命周期、链接属性

嵌入式 Linux C语言(八)--存储类型.作用域.生命周期.链接属性 一.存储类型 C语言中,每个变量和函数都有两个属性:数据类型和数据的存储类型. 变量的存储类型是指存储变量值的内存类型.变量的存储类型决定变量何时创建.何时销毁以及它的值将保持多久.计算机中有三个地方可以用于存储变量:普通内存,运行时堆和栈,硬件寄存器.变量的存储类型取决于声明变量的位置. C语言存储类别说明符: 说明符 用    法 auto 只在代码块内变量声明中被允许, 表示变量具有本地生存期 extern 出现在顶

嵌入式Linux裸机开发(五)——SDRAM初始化

嵌入式Linux裸机开发(五)--SDRAM初始化 一.SDRAM初始化流程 S5PV210有两个独立的DRAM控制器,一个最大支持512MB,一个最大支持1024MB,但两个控制器必须支持相同类型的内存. 根据三星S5PV210文档可知,DDR2类型内存的初始化流程如下: 1.提供稳压电源给内存控制器和内存芯片,内存控制器必须保持CLE在低电平,此时就会提供稳压电源.注:当CKE引脚为低电平时,XDDR2SEL应该处于高电平 2.根据时钟频率正确配置PhyControl0.ctrl_start

嵌入式Linux C语言(六)——内存字节对齐

嵌入式Linux C语言(六)--内存字节对齐 一.内存字节对齐简介 1.内存字节对齐 计算机中内存空间都是按照字节划分的,从理论上讲对任何类型的变量的访问可以从任何地址开始,但是在程序实际编译过程中,编译器会对数据类型在编译过程中进行优化对齐,编译器会将各种类型数据按照一定的规则在空间上排列,而不是顺序的排放,这就是内存字节对齐. 2.内存字节对齐原因 不同硬件平台对存储空间的处理是不同的.一些平台对某些特定类型的数据只能从某些特定地址开始存取.比如某些架构的CPU在访问一个没有进行对齐的变量