Windows中我们常用vs来编译编写好的C和C++代码,vs把编辑器,编译器和调试器等工具都集成在这一款工具中。linux下,通常我们使用vim编辑器,然后用gcc和g++编译、链接生成可执行程序。
我们很难说 C++ 拥有独立的编译器,例如 Windows 下的微软编译器(cl.exe)、Linux 下的 GCC 编译器、Mac 下的 Clang 编译器(已经是 Xcode 默认编译器,雄心勃勃,立志超越 GCC),它们都同时支持C语言和 C++,统称为 C/C++ 编译器。对于C语言代码,它们按照C语言的方式来编译;对于 C++ 代码,就按照 C++ 的方式编译。
1、我们先看一下C++源代码到可执行文件过程:
1)预处理:
经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。
- 处理#define宏指令,进行宏展开、替换;
- 处理#if... 条件编译指令,将不必要的代码过滤;
- 处理#include预编译指令,包含头文件;
- 删除注释;
- 添加行号,为了调试;
2)编译:
- 语法分析;
- 词法分析;
- 生成相应的汇编代码;
3)汇编:
将汇编代码转换成相应的机器语言,即:目标文件;
4)链接:
通过调用链接器来链接程序运行需要的一大堆目标文件,以及所依赖的其他库文件,最后形成可执行文件。
2、gcc和g++命令:
熟悉C++的人应该都知道,C++是C语言的超集,编写C/C++代码的时候,有人用gcc,也有人用g++,我们先来看看gcc和g++是否都能编译C++和C代码:
1)对于C文件:
gcc和g++所做的事情确实是一样的,g++在编译C文件时调用了gcc。
2)对于cpp文件:
对于下面的test.cpp:
#include <iostream>
using namespace std;
int main()
{
cout<<"hello c++"<<endl;
return 0;
}
预处理、汇编gcc和g++都是可以的;但是编译、链接gcc会报错,原因是代码中#include <iostream>是C++的标准库,g++使用C++的标准库将目标文件和 libstdc++ 库中的函数链接得到可执行文件,而gcc使用的是C的标准库,所以会报错。知道了这些,通过遵循源码的命名规范并指定对应库的名字,用 gcc 来编译链接 C++ 程序是可行的,如下例所示:gcc命令编译、连接:
gcc test.cpp -lstdc++ -o helloworld
通过选项 -l(小写L) 指定C++的动态库名,根据默认规则:前缀 lib 和后缀 .a 拼接成libstdc++.a。
总结:
gcc和g++的区别主要是在对cpp文件的编译和链接过程中,因为cpp和c文件中库文件的命名方式不同,所以gcc无法直接编译、连接C++程序。那为什么g++既可以编译C又可以编译C++呢,这是因为g++在内部做了处理,默认编译C++程序,但如果遇到C程序,它会直接调用gcc去编译。
3、用g++ 进行编译步骤详解:
1)预处理 -E (源文件->预处理文件)
g++ -E hello.cpp -o hello.i
-o 选项指定生成的文件,这一步若不指定,则会在终端输出预编译后的文件,后面几步的-o 也是同理,就是指生成指定文件。
2)编译 -S (大写,预处理文件->汇编语言文件)
g++ -S hello.ii 或者 g++ -S hello.cpp
默认生成hello.s
3)汇编 -c (汇编语言文件->可重定向的目标文件)
g++ -c hello.s 或者 g++ -c hello.cpp
默认生成 hello.o 目标文件,为二进制形式
4)链接 多个.o文件->可执行文件
比如有1.cpp,2.cpp
g++ -c 2.cpp //生成2.o
g++ -c 1.cpp //生成1.o
g++ 1.o 2.o -o hello.out //直接1.o 跟上2.o(空格隔开),-o指定为hello,默认为a.out
或者
g++ 1.cpp 2.cpp -o hello.out
g++ *.o -o hello.out
g++ *.cpp -o hello.out
4、g++命令:
4.1)编译、连接带有头文件、多个源文件的程序:
1)当我们只编译一个main.cpp文件时,直接输入:
g++ main.cpp -o main
2)多个源文件
c/c++都是分别编译的,所以多个源文件可以分别编译,然后再一起连接。例如:
g++ common.cpp main.cpp -o main
3)当我们需要编译一个头文件和一个源文件如:common.h和main.cpp文件时:
3.1)假设common.h与main.cpp在同一文件夹下:
main.cpp中使用#include "common.cpp"引入,那么可以直接编译:
g++ main.cpp -I ./ -o main 或 g++ main.cpp -o main
3.2)假设common.h在/home/user/include文件夹下:
A、main.cpp使用绝对路径或者相对路径引入(#include "/home/user/include/common.h"),然后使用下面命令编译:
g++ main.cpp -o main
B、main.cpp中使用#include "common.cpp"引入,在直接编译的时候用-I 指明路径(绝对路径或者相对路径):
g++ main.cpp -I /home/user/include/ -o main
总结:可以通过g++ -I参数指定头文件位置,也可以在源文件中指定头文件路径。
注:使用g++命令编译链接时,后面不用跟头文件,因为头文件会在编译时直接展开在源文件中。
4.2)带有第三方库
1)编译链接的时候需要使用如下参数指定:
- -L:指定库文件路径;
- -l:指定库文件名字(根据默认的规则:前缀lib,后缀.a/so);
- -I:(大小i)指定头文件(include)路径;
2)运行的时候:
- 需要配置LIBRARY_PATH、LD_LIBRARY_PATH环境变量,
- 或者在/etc/ld.so.conf文件中指定库文件路径(或者将库文件放到默认的库文件路径/lib、/usr/lib...),然后执行ldconfig刷新/etc/ld.so.cache;
4.3)调试
想要调试,我们需要生成具有调试信息可执行文件。在编译时要加一个编译器参数(-g)来添加调试信息。然后在使用gdb工具调试。
4.4)其他gcc/g++参数选项
- -shared :指定生成动态链接库。
- -fPIC :产生位置无关的代码,当产生共享库的时候(动态库),应该创建位置无关的代码,这会让共享库使用任意的地址而不是固定的地址。
- -static :指定生成静态链接库。
- -L:指定要连接的库所在的目录。
- -l:指定链接时需要的库名(动态/静态),编译器查找连接库时有隐含的命名规则:即在给出的名字前面加上lib,后面加上.a/.so来确定库的名称。
- -I:(大写i)指定头文件的文件夹。
- -Wall :生成所有警告信息。
- -ggdb :此选项将尽可能的生成gdb的可以使用的调试信息。
- -g :编译器在编译的时候产生调试信息。
- -o:指定执行文件的名字;
- -E:预处理、-S:汇编、-c:编译成目标代码(.o);