1. Xcode 创建 C++ 项目
Xcode (版本 4.6.3)默认支持创建 C++ 项目,步骤很简单:
打开 Xcode,新建一个项目;
在 OS X 中的 Application 中选择 Command Line Tool;
然后,在接下来的项目设置中,将 Type 设置为 C++;
如果你不需要用到 ARC 的话,取消勾选(C++ 里用不到);
这样,就在 Xcode 里创建了一个基本的 C++ 项目,Run :
需要注意的是, Xcode 默认的 C++ 编译器是 Apple LLVM Compiler(4.6.3 的版本为 4.2)也就是 Clang,默认的 C++ 标准库 为 libc++(Clang 提供的标准库),目前已经支持 C++ 11 (先前的 C++0x)标准。Xcode 同时也提供 LLVM GCC 编译器,其附带的 C++ 标准库为 libstdc++(GNU C++ 标准库)。在引入一些第三方库的时候,需要注意这些选择,如 OpenCV,可能就需要将标准库设置为 libstdc++。
IDE 由于集成了如文档、自动补全、可视化的环境配置等特性,很方便用于项目开发,但如果不用 IDE ,那怎样直接使用命令行编译 C++ 项目?很简单,直接使用编译器。
2. C++ 编译过程
在介绍编译器之前,先简单地说一下 C++ 的编译过程,以便理解编译器的工作。
编译(compiling)并不意味着只创建仅仅一个可执行文件。创建一个可执行文件是一个多级过程,其中最重要的过程是预处理(preprocessing),编译(compliation)和链接(linking)。从源代码文件到一个可执行文件的整个过程,最好的说法是 build(中文翻译的话,有叫生成,有叫编译链接,也有叫构建)。compiling 仅仅是 build 过程的一部分,但你经常会碰到许多人把 compile 指代整个过程。通常情况下,你不需要为这几个过程运行单独的命令,编译器自己会调用,如预处理器。
2.1 预处理
build 过程的第一步就是编译器运行 C 预处理器,目的是对代码文件进行文本上的处理。它会处理头文件包含指令(#include),条件编译指令(#ifdef……#endif)和宏(#define),这些指令叫做预处理指令,都以井字符 # 开头。编译器本身是绝对看不到这些预处理指令的。
比如:
- #include <iostream>
这句代码会告诉预处理指令,要把 iostream 的文件内容抓去到当前文件,你每包含一个头文件,它就会把这个头文件的内容粘贴到这个文件中,然后把 #include 指令移除。
- #define MY_NAME "Alex"
宏就是一个被其它内容(可能比较复杂)替换掉的字符串内容,此时预处理器会把下面的代码:
- cout << "Hello" << MY_NAME << endl;
展开成:
- cout << "Hello" << "Alex" << endl;
由于预处理器在编译器之前处理代码,它也可以用来移除代码——有时,你会要在代码里执行某些测试代码。你可以告诉预处理器,如果定义了某个宏,则包含某些代码。然后,如果你想执行这个代码,就定义这个宏,否则就移除掉这个宏的定义。
#include <iostream>
#define DEBUG
using namespace std;
int main()
{
int x;
int y;
cout << "Enter value for x: ";
cin >> x;
cout << "Enter value for y: ";
cin >> y;
x *= y;
#ifdef DEBUG
cout << "x: " << x << '\n' << "y: "<< y;
#endif
}
如果你不想执行变量的打印,那么只需简单注释掉 #define DEBUG 就行。
同样地,你也可以用 #ifndef 来改变条件——如果没有定义……这个方法通常用在引入多个头文件的时候。
2.2 编译
编译意味着把一个源文件(.cpp)转变成一个对象文件(object,.o 或 .obj)。
一个对象文件会把你程序里的每一个函数,封装成一个计算机处理器能理解的形式——机器指令(machine language instructions)。每一个源文件都是单独编译过的,即对象文件包含的机器代码都是编译过的源代码。比如,你有三个源文件,经过编译,生成了三个对象文件,每一个对象文件都包含了各自对应的机器代码。
但你还不能运行它们,这时候,就需要链接器了。
2.3 链接
链接(Linking),是把一堆对象文件和库(有时也可能仅仅是一个对象文件,但也需要链接)创建成一个单独的可执行文件(比如 .exe 或 .dll)。
链接器通过一种适当的格式创建一个可执行的文件,并传递每个独立的对象文件内容到一个可执行的结果。链接器也处理含有对象文件源代码之外的其它函数的引用,比如 C++ 标准库里的函数。当你调用了一个 C++ 标准库的函数,如 cout << “Hi”,你就在使用一个自己代码中没有定义的函数,它被定义在一个相关的对象文件中,但这是由编译器提供的,并不属于你。在编译时,编译器知道这个函数是有效的,因为你引出了 iostream 头文件,但由于这个函数不是 cpp 文件的一部分,编译器就会在调用树(call tree)留下一个存根(stub),链接器会遍历对象文件,针对每一个存根,它会找到正确的函数地址,然后从已链接过的其它对象文件中,用正确的地址替换掉对应的存根。
这个过程有时也叫做修正(fixup)。当你把你的程序分离成多个源文件时,你就会利用链接器来修正所有在源文件中调用过的函数。如果链接器找不到这个函数的位置,它就会生成一个 undefined function error,即便代码被编译器通过了,也不意味着代码是正确的。链接器是首先以全局的视角来探测这种错误的。
3. GCC 和 LLVM
Mac 下,如果你安装了 Xcode ,那么你就拥有了 LLVM 和 GCC 两大编译工具。
3.1 LLVM
LLVM,原来叫做 「Low Level Virtual Machine」,该项目的领导者和最初作者是 Chris Lattner 和 Vikram Adve,在 2000年的开始于 伊利诺伊大学厄巴纳-香槟分校(University of Illinois at Urbana-Champaign),2005年,Apple 雇佣了 Chris Lattner,成立了一个团队专注于 LLVM 系统在 Apple 开发系统上的各种使用。LLVM 目前是 Apple 的 Mac OSX 和 iOS 的开发工具的重要部分。
LLVM 项目是一个综合项目(umbrella project),它包括了一系列开发工具相关的技术,如:
编译器,Clang,LLVM 原生的 C/C++/Objective-C 编译器,旨在提供一个快速的编译器
调试器,LLDB,在 LLVM 和 Clang 的基础上构建的一个调试器
JIT 系统,VMKit, Java 和 .NET 虚拟机的 LLVM 技术实现
优化器,DragonEgg,集成了 LLVM 的优化器和搭配 GCC 解析工具的代码产生器
……
3.2 GCC
GCC,全称为 GNU Complier Collection,GCC 是 GNU Project 的关键组成部分,是由*软件之父 Richard Stallman 在 1983 年9月27日于麻省理工大学发起的,旨在给一切计算机用户提供*、可控的计算环境,用户可以*的运行、分享、学习以及修改软件,即*软件。
GCC 最初叫做 GNU C Compiler ,只支持 C 语言的编译,1.0 之后开始支持 C++,再随后支持了 Objective-C,Objective-C++,Fortran,Java,Ada,Go等其他语言。
至于选择哪个作为首选,则看具体情况了。
GCC 历史悠久,支持较为广泛,且目前许多开源项目都是直接使用 GCC 作为编译器的。当然,Clang 同为开源项目,可以跨平台使用,而 Clang 相对 GCC 的优势: 编译速度快:在某些平台上,Clang 的编译速度显著的快过 GCC。 占用内存小:Clang 生成的 AST 所占用的内存是 GCC 的五分之一左右。 模块化设计:Clang 采用基于库的模块化设计,易于 IDE 集成及其他用途的重用。 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告。 设计清晰简单,容易理解,易于扩展增强。与代码基础古老的 GCC 相比,学习曲线平缓。
Clang 下编译一个 C++ 代码:
GCC 下编译:
两者用法类似。gcc 和 clang 有类似的编译选项命令,通过这些命令我们可以做许多事情。比如:
GCC 和 Clang 通过相应的编译选项,可以看到 build 过程某些步骤:
-E, 可以只执行预处理阶段,如:clang++ -E HelloWorld.cpp结果会打印出预处理后的代码,HelloWorld 只有 7 行,预处理过后却有几万行。
-S, 执行到编译阶段:clang++ -S HelloWorld.cpp这个阶段,会生成一个对应名称的 .s 文件,里面包含了所谓的机器指令——汇编代码,HelloWorld.cpp 生成了 86 行汇编代码
-c,执行到编译后的阶段,生成对应名称的 .o 对象文件
关于这两个工具的选项,使用 man gcc 和 man clang 可以看到更多详细的介绍。