编译器安装

  • 安装GCC、GDB、CMake
sudo apt install build-essential gdb
sudo apt install cmake
  • 验证安装成功
gcc --version      # C语言的编译器
g++ --version      # C++语言的编译器
gdb --version      # 调试器
cmake --version

gcc、g++、make、cmake

gcc和g++

gcc与g++的概念
  • GCC是linux下最常用的C语言编译器,能够编译多种语言
  • gcc是GCC中的C编译器
  • g++是GCC中的C++编译器
gcc和g++主要区别
  • 对于 .c和.cpp文件,gcc分别当做c和cpp文件编译(c和cpp的语法强度是不一样的); g++则统一当做cpp文件编译
  • 使用g++编译文件时,g++会自动链接标准库STL,而gcc不会自动链接STL,在用gcc编译c++文件时,为了能够使用STL,需要加参数 –lstdc++ ,但这并不代表 gcc –lstdc++ 和 g++等价
  • gcc在编译C文件时,可使用的预定义宏是比较少的

make和cmake

  • 当只有一个文件去编译的时候,可以使用g++编译,但是当有很多文件时,使用g++命令编译工作量巨大,因此可以使用makefile文件,针对拥护指定的命令编译和连接。(就是活makefile包括了gcc命令)
  • 简单的工程完全可以手写makefile文件,但是工程巨大的时候,就提出了cmake工具。就是用户自己写CMakeList.txt文件,使用cmake工具自动生成makefile文件,再使用make工具,对工程进行编译。
  • cmake的优点是可以跨平台使用,就是会生成对应平台支持的makefile文件

gcc编译器

编译流程

1.预处理(源文件 -> .i)
  • 预处理器都干了什么
  • 预处理阶段不会做任何语法检查,因为预处理器不会,而且预处理命令不属于C/C++的语句,(这也是宏定义的时候不加分号的原因),语法检查是编译器做的事情
  • 将所有的#define宏定义展开,就是代码中的宏定义用具体指定的值替代
  • 处理所有的条件预编译指令,如#if #ifdef #elif #else等等
  • #include(预编译指令)包含的文件插入到预编译指令的位置
  • 删除所有注释
  • 添加行号和标示,用于在编译调试过程中使用
  • 预处理命令
  • 宏定义
  • 预处理#include时,为了防止文件被重复引用,有两种方式:#ifndef#pragma
  • #ifndef是防止一块代码被重复引用,在C中经常使用,尤其是在头文件的开头,防止重复引用
  • #pragme once是C++的特性(现在新的GCC也支持),可以在头文件的开头写明,这样不会出现文件被引用多次的情况,防止引发宏名碰撞出现一些奇怪的问题。
  • #pragma后面可以接的不仅仅是once还可以有其他的供编译器识别
  • #ifndef的对象是一块代码,#pragma的对象是一个文件
  • 预先编译命令

写法

解释

#include

尖括号:编译器会在系统路径下查找头文件

双引号:编译器现在当前目录下查找头文件,然后去系统路径查找

#define

无参宏定义:#define 宏名 字符串

有参宏定义:#define 宏名(参数表) 字符串

#ifdef #else #endif #ifndef

条件编译,只会处理起作用的部分代码

#if #elif

#elif相当于else if 语句,注意是#elif,而不是#elseif

  • 与宏定义相似的处理
  • 内联函数(inline)
  • 普通的函数调用的时候需要保留现场、执行、返回主调函数、恢复现场等等操作,是非常消耗时间的操作。而内联函数,就是将一些很简单的函数内嵌到调用他的程序代码中,是一种以空间换时间的方法
  • 内联函数的要求较严格,必须是很简单的函数,所以,就算我们定义了函数是内联函数,编译器也并不一定会将其设置成内联函数,编译器会自动的将一些简单的函数定义为内联函数
  • 内联函数和宏定义的区别在于对参数的处理,宏定义直接替换,而内联函数终究是函数,会进行运算求值,再把结果返回给形参
  • typedef
  • typedef就是给数据类型一个新的名字,跟宏定义也有些相似,但是需要注意一些本质区别
#define PIN1 int *           //将int * 宏定义为PIN1
typedef (int *) PIN2         //将int * 取个新名字PIN2
//使用的区别
PIN1 a,b;                    
//预处理之后是 int *a,b   直接替换,变成了一个指针,一个整型
PIN2 a,b;
//使用typedef之后,就是两个指针
//typedef可以对结构体进行重命名
typedef struct abc{
int age;
char a;
}linklist;
//linklist就是结构体的别名,完全替代
  • 预处理生成文件
  • 生成.i文件,如果打开此文件会发现有很多的代码,这是由于将#include插入到文件中了
# -E 选项是对此文件仅进行预处理
g++ -E test.cpp -o test.i
2.编译(.i -> .s)
  • 将代码翻译成汇编语言
  • 生成.s文件,如果打开此文件会发现里面全部都是汇编语言
# -S 选项是将代码文件翻译成汇编文件(默认就是.s文件)
g++ -S test.i -o test.s
3.汇编(.s -> .o)
  • 将汇编语言转换成机器可以执行的指令,就是按照对照表将汇编一一翻译
  • 生成.o文件,如果打开此文件会发现里面全是乱码,因为都是机器码
# -c 选项将汇编代码翻译成机器码(默认是.o文件)
g++ -c test.s -o test.o
4.链接(.o -> bin文件)
  • 通过使用链接器ld来链接程序运行所需要的目标文件,以及所依赖的其他库文件
  • 链接的主要内容是:将各个模块之间的相互应用处理好,能够正确的衔接
  • 链接的主要过程是:地址和空间分配,重定位等
  • 链接分为静态链接和动态链接:
  • 静态链接:在编译阶段直接把静态库加入到可执行文件中,这样使得可执行文件比较大
  • 动态链接:在链接阶段只加入一些描述信息,在程序执行时再从系统中把相应的动态库加载到内存中
  • 生成bin文件,就是可执行文件
# -o 生成指定的文件
g++ test.o -o test

g++重要编译参数

-g 调试信息
# -g 选项是使得文件生成调试信息,表示这个程序可以被调试器调试

# 产生带有调试信息的可执行文件test
g++ -g test.cpp
-O[n] 优化代码
# 所谓优化,例如省略掉代码中从未使用过的变量、直接将常量表达式用结果值代替等等,这些操作会缩减目标文件所包含的代码量,提高最终生成的可执行文件的运行效率

# -O[n] n代表优化等级
# -O 同时减小代码的长度和执行时间,其效果等价于-O1
# -O0 表示不做优化
# -O1 为默认优化
# -O2 除了完成-O1的优化之外,还进行一些额外的调整工作,如指令调整等
# -O3 包括循环展开和其他一些与处理特性相关的优化工作
# 一般就是用-O2进行优化代码

# 优化源代码,并输出可执行文件
g++ -O2 test.cpp
-l和-L 指定库
# -l参数(小写)就是用来指定程序要链接的库,-l参数紧接着就是库名
# 在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接
# 链接glog库
g++ -lglog test.cpp

# 如果库文件没放在上面三个目录里,需要使用-L参数(大写)指定库文件所在目录
# -L参数跟着的是库文件所在的目录名
# 链接mytest库(动态库),libmytest.so在指定目录下
g++ -L/home -lmytest test.cpp
-I 指定头文件搜索目录
# -I 选项指定包含的头文件的位置
# /usr/include目录一般是不用指定的,gcc知道去那里找,但是如果头文件在其他目录下我们就要用-I参数指定了,比如头文件放在/myinclude目录里,那编译命令行就要加上-I/myinclude 参数了
# -I参数可以用相对路径
g++ -I/myinclude test.cpp
-Wall 打开警告信息
# 打印出gcc的警告信息
g++ -Wall test.cpp
-w 关闭警告信息
# 关闭所有警告信息
g++ -w test.cpp
-std=c++11 编译标准
# 使用c++11标准进行编译,如果程序中有新特性,就需要添加这项
g++ -std=c++11 test.cpp
-o 指定输出
# 指定输出的文件名,后面跟着的就是输出的文件
g++ test.cpp -o test
-D 定义宏
# -Dname 定义宏name,在编译的时候使用定义宏
# 常用场景:
# -DDEBUG定义DEBUG宏,那么在程序中如果有#ifdef DEBUG的代码,就会执行,如下面例子:

int main(){
	#ifdef DEBUG
		printf("DEBUG LOG\n");
	#endif
		printf("in\n");
}
# 如果使用了g++ -DDEBUG main.cpp 那么就会指定第7行

g++编译实战

.# 目录结构
├── include
│   └── swap.h
├── main.cpp
└── src
    └── swap.cpp
直接编译
  • 最简单的编译
g++ main.cpp src/swap.cpp -Iinclude
# 直接指定两个输入的源文件和指定头文件所在目录
./a.out
  • 添加参数编译
g++ main.cpp src/swap.cpp -Iinclude -std=c++11 -O2 -Wall -o b.out
# 添加一堆参数
./b.out
使用库文件编译
  • 使用静态库链接,生成可执行文件
# 先在其他的源文件下生成自己的静态库文件.a,然后在main源文件下链接
cd src
# 汇编,生成.o文件
g++ swap.cpp -c -I../include
# 生成静态库文件.a 库文件都是lib*.a,其中的*指的就是库名
# 下面就是将swap.o转换成静态库文件libswap.a
ar rs libswap.a swap.o

cd ..
# 链接,生成可执行文件
g++ main.cpp -Iinclude -Lsrc -lswap -o static_main
# -Iinclude 指定头文件搜索目录
# -Lsrc 指定库文件的目录
# -lswap 指定库的名字,就是libswap.a中的swap

# 运行可执行文件
./static_main
  • 使用动态库链接,生成可执行文件
# 先在其他的源文件下生成自己的动态库文件.so,然后在main源文件下链接
cd src
# 汇编,生成.so文件
g++ Swap.cpp -I../include -fPIC -shared -o libSwap.so
# -fPIC 生成位置无关代码,就是代码没有绝对跳转,都是相对跳转,一般生成动态库都是需要这个的
# -shared 用于生成动态库
# 上面命令等价于以下两条命令
## gcc Swap.cpp -I../include -c -fPIC
## gcc -shared -o libSwap.so Swap.o

cd ..
# 链接,生成可执行文件
g++ main.cpp -Iinclude -Lsrc -lswap -o share_main

# 运行可执行文件,需要指定动态库的路径
LD_LIBRARY_PATH=src ./share_main

GDB调试(未更新)

这部分以后再说,因为目前很少会去使用单纯的GDB调试,都是使用Clion或者其他的编辑器中的GDB调试功能。

CMake

  • CMake是一个跨平台的安装编译工具,可以用简单的语句来描述所有平台的安装(编译过程)
  • CMakeLists.txt可以自动产生可执行文件需要的Makefile文件等工具

语法特性

  • 基本语法格式:指令(参数1 参数2 …)
  • 参数使用括号括起
  • 参数之间使用空格或者分号分开
  • 指令是大小写无关的,但是参数和变量是大小写相关的
# 设置变量,大小写是不一样的
set(HELLO hello.cpp)
# 下面两行是一样的效果,因为指令是大小写无关的
add_executable(hello main.cpp hello.cpp)
ADD_EXECUTABLE(hello main.cpp ${HELLO})
  • 变量使用${}方式取值(这点跟shell脚本差不多),但是在IF控制语句中需要直接使用变量名

重要指令

cmake_minimum_required
  • 指定CMake的最小版本要求(一般是文件的第一个指令)
  • 语法:cmake_minimum_required(VERSION versionNumber [FATAL_ERROR])
# CMake最小版本要求2.8.3,语法后面的[]是如果不符合版本要求输出什么
cmake_minimum_required(VERSION 2.8.3)
project
  • 定义工程的名称,也可以指定工程支持的语言
  • 语法:project(projectname [CXX] [C] [Java])
# 指定工程名为 HELLOWORLD
project(HELLOWORLD)
set
  • 显式的定义变量
  • 语法:set(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
# 定义SRC变量,其值为sayhello.cpp hello.cpp
set(SRC sayhello.cpp hello.cpp)
message
  • 调试手段,相当于echo/printf,主要用于查看cmake文件的语法错误
# 打印cmake中的变量值use_test
message("use_test: ${use_test}")
add_definitions
  • 添加宏定义(相当于g++编译器中的-D参数)
# 一般使用在if这种条件编译中
# 在代码中,会运行定义了TEST_DEBUG部分的代码
if (TEST_DEBUG) 
	add_definitions(-DTEST_DEBUG)
endif(TEST_DEBUG)
list
# 命令很多,仅列举一个
set( mylist )
list( APPEND mylist a )
list( APPEND mylist b )
# 向变量mylist添加数据
find_package
  • 查找系统中的库文件所在位置
  • 语法:find_package(LAPACK REQUIRED)
# find_package一般用于opencv等库的查找
find_package(OpenCV 3.0 REQUIRED)
# 然后cmake就会把找到的LAPACK库的路径送给已经定义好的变量中
# 头文件路径给${LAPACK_INCLUDE_DIRS}
# 库文件路径给${LAPACK_LIBRARIES}
include_directories
  • 向工程添加多个特定的头文件搜索路径(相当于g++编译器中的-i参数)
  • 语法:include_directories([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
# 将/usr/include/myincludefolder 和 ./include 添加到头文件搜索路径
include_directories(/usr/include/myincludefolder ./include)
link_directories
  • 向工程中添加多个特定的库文件搜索路径(相当于g++编译器中的-L参数)
  • 语法:link_directories(dir1 dir2 ...)
# 将/usr/lib/mylibfolder 和 ./lib 添加到库文件搜索路径
link_directories(/usr/lib/mylibfolder ./lib)
add_library
  • 生成库文件
  • 语法:add_library(libname [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] source1 source2 ... sourceN)
# 通过变量 SRC 生成 libhello.so 共享库
add_library(hello SHARED ${SRC})
add_compile_options
  • 添加编译参数,就之前g++命令中那些参数都可以添加进来
  • 语法:add_compile_options(<option> ...)
# 添加编译参数 -Wall -std=c++11 -O2
add_compile_options(-Wall -std=c++11 -O2)
add_executable
  • 生成可执行文件
  • 语法:add_executable(exename source1 source2 ...)
# 编译main.cpp生成可执行文件main
add_executable(main main.cpp)
target_link_libraries
  • 为target添加需要链接的共享库(相当于g++编译器中的-l参数)
  • 语法:target_link_libraries(target library1<debug | optimized> library2...)
# 将hello动态库文件链接到可执行文件main
target_link_libraries(main hello)
add_subdirectory
  • 向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置(就是说,当子文件中有CMakeLists.txt文件的时候,需要使用这个指令指定其位置)
  • 语法:add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# 添加src子目录,src中需有一个CMakeLists.txt
add_subdirectory(src)
aux_source_directory
  • 发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表
  • 语法:aux_source_directory(dir VARIABLE)
# 定义SRC变量,其值为当前目录下所有的源代码文件
aux_source_directory(. SRC)
# 编译SRC变量所代表的源代码文件,生成main可执行文件
add_executable(main ${SRC})

常用变量

  • PROJECT_NAME -> 定义的工程名称
  • CMAKE_C_FLAGS -> gcc编译选项
  • CMAKE_CXX_FLAGS -> g++编译选项
# 在CMAKE_CXX_FLAGS编译选项后追加-std=c++11
set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
  • CMAKE_BUILD_TYPE -> 编译类型(debug和release)
# 设定编译类型为debug,调试时需要选择debug
set(CMAKE_BUILD_TYPE Debug)
# 设定编译类型为release,发布时需要选择release
# 设置成release就是使用-O3,并关闭Debug模式
set(CMAKE_BUILD_TYPE Release)
  • CMAKE_SOURCE_DIR -> 工程顶层目录
  • PROJECT_SOURCE_DIR -> 工程顶层目录
  • _SOURCE_DIR -> 工程顶层目录(上面这三个是一样的)