在Ubuntu下搭建C/C++编程环境
在Ubuntu下搭建C/C++编程环境,综合起来说就是vim+gcc+gdb
。其中vim用于文本编辑,gcc用于程序编译,gdb用于代码调试。
要安装编程环境,在Terminal中执行sudo apt-get install build-essential
。
桌面系统的快捷键
如果不用桌面系统,可以关闭。
打开Terminal的快捷键是Ctrl+Alt+T
,使用快捷键Ctrl+Super+Up
最大化,Alt+F4
关闭。
使用快捷键Alt+Tab
切换窗口。
长按Super键(默认为Windows键),可以看到关于桌面的所有快捷键。
Terminal的一些基本命令
一般最常用的是cd, ls, mkdir, rmdir, cp, rm, mv, clear, pwd, shutdown.
一般使用时只需记住常用命令,不清楚的时候用man查询。如需查看更多命令可以阅读《The Linux Command Line》(《Linux命令行大全》)。
编辑
不去管vim与Emacs的世纪之争,vim真的是很强大的编辑器,摆脱对图形界面的依赖,效率会有很大提高。vim有无穷尽的插件,善加选择利用,有很多针对编程的快捷功能是vs也提供不了的。
vim的学习曲线虽然有些陡峭,但还是很值得的。
网上有很多学习Vim的资源,推荐几本书:《Vim用户手册中文版7.3》《A Byte of Vim》《Practical Vim》,还有这个视频教程(需要翻墙)。
使用vim,一般只需动用肌肉“记住”一些常用操作(如下表),其余功能用的时候查询即可。
vim入门
vim入门主要是熟练使用基本命令。最直接的学习资源是在Terminal里输入vimtutor
,会有一个差不多30分钟的入门教程。
或者学习上文提到的书籍和视频,对常见命令均有讲解。
推荐直接从官方文档或是书籍入手,比在网上搜来搜去只鳞片爪有效率的多。四处搜索别人的学习经验,出发点是少走弯路,结果反而欲速则不达,还不如沉下心好好看完一本书。
有几张不同角度的cheat sheet,方便查阅。作者见文末参考链接。
在vim中,获得帮助的命令是:help
。
如果不知道自己想查什么,可以运行:help user-manual
查看整个用户手册的目录。
如果大概知道想使用的功能而不知道具体命令,可以使用:helpgrep
在整个文档中搜索相关内容。比如想了解如何查看词首,可以运行:helpgrep beginning of a word
。可以使用:cnext
和:cprev
在搜索结果间跳转。
更多关于帮助的帮助,参看《A Byte of Vim》中Vim en:Help这一节。
vim自定义配置
基本操作熟练之后,多半都会开始安插件折腾自定义配置。
自定义配置与插件包括vimrc、global plugin、filetype plugin、syntax highlighting plugin、compiler plugin。
一般将简单配置记录在vimrc文件里,高级功能使用插件来实现。
在Linux系统中,vimrc文件地址是$HOME/.vimrc
。vim安装时自带的插件在$VIMRUNTIME/plugin/
目录下。自定义插件放在$HOME/.vim/plugin/
的相应子目录下,且需在vimrc文件里设置自动加载此插件。
详细的配置与插件使用及编写请参看《A Byte of Vim》中Vim en:Plugins这一节。
偷了个懒,在github上发现一个很棒的配置方案spf13-vim,就直接安上了。
spf13-vim是运行在vim层之上,为方便编程,对vimrc和plugin都做了很多特殊优化配置的一个工具。
spf13-vim。安装很容易,根据readme上的指示进行即可。
安装成功后,可以打开~/.vimrc
看到spf13-vim的预置配置。创建~/.vimrc.local
和~/.gvimrc.local
进行自定义配置。
spf13-vim已经预置了很多插件,关于这些插件的启动和使用可以看spf13-vim的介绍和这些插件自己的github页面。此处唯一提醒的一点是<leader>
键在spf13-vim这里是逗号,
。
我目前也在摸索中,有心得会随时更新。这是我的操作速查表。
在Quora上看到有些人并不推荐使用spf13等工具,认为会引诱你学习这个工具的配置方式,而不是真正学习vim的配置文件,使用自定义的针对自己需求的配置文件才最符合vim精神。不过对于初学者来说,我觉得spf13-vim还是很有价值的,毕竟时间这么宝贵,折腾是很累人的。
编译运行
gcc
GCC(GNU Compiler Collection)是一组编译工具的总称,支持多平台、多语言源文件到可执行文件的编译与生成。其中也包括gcc(C编译器)和g++(C++编译器)。
推荐书籍《An Introduction to GCC》。
在GCC内部寻找帮助,使用gcc --help
,如果想看gcc选项的完整列表使用gcc -v --help 2>&1 | more
。
最简单的应用示例:一个hello.cpp文件。一下语句就是编译与运行。
1 | g++ hello.cpp hello |
gcc常用命令
基本语法格式如下。
对于C:gcc [options] [filenames]
对于C++:g++ [options] [filenames]
上述命令行按编译选项(options)指定的操作对给定的文件(filenames)进行编译处理。
选项主要列表如下。
选项 | 选项描述 |
---|---|
-c | 只对文件进行编译和汇编,但不进行连接,生成目标文件".o" |
-S | 只对文件进行编译,但不汇编和连接 |
-E | 只对文件进行预处理,但不编译汇编和连接 |
-g | 在可执行程序中包含标准调试信息 |
-o file1 [file2] | 将文件file1编译成可执行文件file2 |
-v | 打印出编译器内部编译各过程的命令行信息和编译器的版本 |
-I dir | 在头文件的搜索路径列表中添加dir目录 |
-L dir | 在库文件的搜索路径列表中添加dir目录 |
-static | 强制链接静态库 |
-lNAME | 连接名为libNAME的库文件 |
-Wall -W | 开启GCC最常用的警告,GCC的warning一般格式为file:line-number:message |
-pedantic | 要求严格符合ANSI标准 |
-Wconversion | 开启隐式类型转换警告 |
-Wshadow | 开启同名变量函数警告 |
-Wcast-qual | 开启对特性移除的cast的警告,如const |
-O(-O1) | 对编译出的代码进行优化 |
-O2 | 进行比-O高一级的优化 |
-O3 | 产生更高级别的优化 |
-Os | 产生最小的可执行文件 |
-pg | 开启性能测试,记录每个函数的调用次数与时长 |
-ftest-coverage | 记录每一行被执行的次数 |
-fprofile-arcs | 记录每个分支语句执行的频率 |
注意选项的大小写。只在最后发行版时再使用优化。即使在最后发行版也应该加上-g选项。
几种情境
以c++为例。
编译单个文件为可执行文件:
g++ -Wall -W hello.cpp -o hello
编译多个文件为可执行文件:
g++ -Wall -W main.cpp hello_fun.cpp -o newhello
编译单个文件为可执行文件,连接静态库static library:
系统默认库文件在目录
/usr/lib
和/lib
,还会自动搜索/usr/local/lib/
和/usr/lib/
。
相应的,系统默认头文件在目录,会自动搜索/usr/local/include/
和/usr/include/
。
显式指定库目录与文件,g++ -Wall -W calc.cpp /usr/lib/libm.a -o calc
或更好的写法g++ -Wall -W -static calc.cpp -lm -o calc
,连接系统自动搜索库目录里的库文件。注意g++优先使用shared library。如果找到同名.so就不会用同名.a。所以如果需要强制使用.a文件的话,应使用-static
。
如果不在自动搜索的库目录,两种方法。一般使用命令行这一种。
- 使用命令行
-I
添加搜索头文件的目录和-L
添加搜索库文件的目录,例如g++ -Wall -W -static -I/code/test/include -I/code/another/include -I. -L/code/test/lib -L/code/another/lib -L. calc.cpp -ltest
。.表示当前目录。- 或使用环境变量
C_INCLUDE_PATH
©或CPLUS_INCLUDE_PATH
(C++)和LIBRARY_PATH
。用如下语句添加搜索路径,之后就可以使用g++ -Wall -W -static calc.cpp -ltest
了。
1 | CPLUS_INCLUDE_PATH = /code/test/include:/code/another/include:.:$CPLUS_INCLUDE_PATH |
编译单个文件为可执行文件,连接共享库shared library:
如果只是用了系统默认库,
g++ -Wall -W calc.cpp -lm -o calc
除了前文系统自动搜索库文件目录之外,如果要添加其他共享库目录,必须在环境变量LD_LIBRARY_PATH
中添加路径。
1 | LD_LIBRARY_PATH = /code/test/lib:/code/another/lib:.:$LD_LIBRARY_PATH |
之后可以使用
g++ -Wall -W calc.cpp /usr/lib/libm.so -o calc
或g++ -Wall -W -I/code/test/include -I/code/another/include -I. -L/code/test/lib -L/code/another/lib -L. calc.cpp -ltest
。
使用ldd calc
可以查看该可执行文件依赖哪些.so。
编译多个文件为静态库文件.a:
将多个.o文件集合为一个静态库文件.a。其中cr表示"create and replace"。
1 | g++ -Wall -c hello_fn.cpp |
可以查看一个.a文件里包含哪些.o。使用
ar t libhello.a
。
g++ x.cpp y.cpp z.cpp -fPIC -shared -o libtest.so
。其中-fPIC表示编译为位置独立的代码。
预处理
可以用gcc选项定义宏,-DNAME会定义一个名为NAME的宏。如g++ -Wall -DTEST dtest.cpp
,定义了名为TEST的宏。定义的宏会对代码产生影响。
也可以为宏定义值,-DNAME=VALUE。如g++ -Wall -DNUM=100 dtestval.cpp
,g++ -Wall -DNUM="2+2" dtestval.cpp
,g++ -Wall -DMESSAGE="\"Hello,World!\"" dteststr.cpp
。在代码中把宏用括号括起来是好习惯。
性能
开启-pg选项生成可执行文件后。首先正常运行一次可执行文件./hello
,然后运行gprof hello
查看数据。
开启-fprofile-arcs -ftest-coverage选项生成可执行文件后。首先正常运行一次可执行文件./hello
,然后运行gcov hello.cpp
查看数据。未被执行的语句会在.gcov文件中标记上-,可以通过执行grep '-' *.gcov
查找未被执行的语句。
makefile文件
对于较大的工程,如果还像前文一样写命令行就太痛苦了。而使用makefile可以管理整个工程的编译规则,之后用一个make命令就可自动编译,相对方便很多。
推荐书籍《跟我一起写Makefile》和官方文档。内容看起来还是很晦涩…(・-・*)还好现在有了自动化工具,用来自动生成的工具也可以用工具自动生成了。
最基础的用法
makefile基本规则是:
target1 target2 target3: prerequisite1 prerequisite2
command1
command2
其中target是目标文件。prerequisites是要生成target所需文件或目标。command是make需要执行的命令。规则表示了一个文件的依赖关系,即target依赖于prerequisites,生成规则为command。如果target不存在或prerequisites中至少一个文件比target新的话,command定义的命令就会执行。这就是makefile中最核心的内容。
make工作的流程是:
当我们输入make命令之后,
- 读入所有的makefile文件。
- 读入被include包括的其他makefile文件。
- 初始化文件中的变量。
- 推导隐式规则,分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
makefile文件名应为Makefile
或makefile
。
小示例
我为自己刷题的一个小项目写了一个将几个目录下所有的源文件一起编译的一个makefile。在这里看工程的目录结构。
1 | EXEC = MyLeetCode |
网上看到一个Generic Makefile for C/C++ Program。
使用CMake自动生成makefile
当处理较大型的项目时,手动书写makefile就比较痛苦,这时用来用来自动化自动化工具makefile的自动化工具就是CMake。不过天下哪有那么便宜的事,它也是要写自己的CMakeLists.txt的。
推荐书籍《CMake实践》《Mastering CMake》和官网帮助。
简介
CMake是一个跨平台的自动化建构系统,它是用一个名为CMakeLists.txt的文件来描述构建过程,可以产生标准的构建文件,如Unix的makefile或Windows Visual Studio的projects/workspaces。
文件CMakeLists.txt需要手工编写,也可以通过编写脚本进行半自动的生成。
在Linux平台下使用CMake生成makefile并编译的流程如下:
- 安装CMake。在Ubuntu上安装cmake很简单
$sudo apt-get install cmake
。如果想要其Qt图形界面另需安装sudo apt-get install cmake-qt-gui
。一般不需要,在Ubuntu系统上用ccmake就可以了。 - 编写CMakeLists.txt。
- 运行CMake。用
cd
将当前目录设为生成目标目录,执行命令ccmake srcdir
(文字界面)或cmake -i
(交互命令行),如果想使用Qt图形界面使用cmake-gui
。 - Makefile已经生成。使用make命令进行编译。
- 如果想清理工程。使用
make clean
。
简单语法
注释:#
命令语法:COMMAND(参数1 参数2 ...)
字符串列表:A;B;C
或A B C
。分号或空格分隔的值。
变量(字符串或字符串列表):
set(Foo a b c)
设置变量Foo。
command(${Foo})
等价于command(a b c)
。
command("${Foo}")
等价于command("a b c")
。
command("/${Foo}")
转义,和a b c无关联。
流控制结构:
IF()...ELSE()/ELSEIF()...ENDIF()
WHILE()...ENDWHILE()
FOREACH()...ENDFOREACH()
正则表达式:
常用命令总结
命令 | 意义 |
---|---|
INCLUDE_DIRECTORIES( “dir1” “dir2” … ) | 头文件路径,相当于编译器参数 -Idir1 -Idir2 |
AUX_SOURCE_DIRECTORY( “sourcedir” variable) | 收集目录中的文件名并赋值给变量 |
ADD_EXECUTABLE | 可执行程序目标 |
ADD_LIBRARY | 库目标 |
ADD_CUSTOM_TARGET | 自定义目标 |
ADD_DEPENDENCIES( target1 t2 t3 ) | 目标target1依赖于t2 t3 |
ADD_DEFINITIONS( “-Wall -ansi”) | 本意是供设置 -D… /D… 等编译预处理需要的宏定义参数,对比 REMOVE_DEFINITIONS() |
TARGET_LINK_LIBRARIES( target-name lib1 lib2 …) | 设置单个目标需要链接的库 |
LINK_LIBRARIES( lib1 lib2 …) | 设置所有目标需要链接的库 |
SET_TARGET_PROPERTIES( … ) | 设置目标的属性 OUTPUT_NAME, VERSION, … |
MESSAGE(…) | 这个指令用于向终端输出用户定义的信息 |
INSTALL( FILES “f1” “f2”DESTINATION . ) | DESTINATION 相对于 ${CMAKE_INSTALL_PREFIX} |
SET( VAR value [CACHE TYPE DOCSTRING [FORCE]]) | 定义与修改变量 |
LIST( APPEND/INSERT/LENGTH/GET/REMOVE_ITEM/REMOVE_AT/SORT …) | 列表操作 |
STRING( TOUPPER/TOLOWER/LENGTH/SUBSTRING/REPLACE/REGEX …) | 字符串操作 |
SEPARATE_ARGUMENTS( VAR ) | 转换空格分隔的字符串到列表 |
FILE( WRITE/READ/APPEND/GLOB/GLOB_RECURSE/REMOVE/MAKE_DIRECTORY …) | 文件操作 |
FIND_FILE | 注意 CMAKE_INCLUDE_PATH |
FIND_PATH | 注意 CMAKE_INCLUDE_PATH |
FIND_LIBRARY | 注意 CMAKE_LIBRARY_PATH |
FIND_PROGRAM | |
FIND_PACKAGE | 注意 CMAKE_MODULE_PATH |
EXEC_PROGRAM( bin [work_dir] ARGS <…> [OUTPUT_VARIABLE var] [RETURN_VALUE var] ) | 执行外部程序 |
OPTION( OPTION_VAR “description” [initial value] ) |
变量
工程路径
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
表示工程顶层目录。
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
表示生成目标目录。
CMAKE_CURRENT_SOURCE_DIR
表示当前处理的CMakeLists.txt所在的目录。
CMAKE_CURRRENT_BINARY_DIR
表示当前处理的CMakeLists.txt的目标目录。
CMAKE_CURRENT_LIST_FILE
输出调用这个变量的CMakeLists.txt的完整路径。
Debug和Release模式的构建
在CMakeList.txt文件中使用
SET(CMAKE_BUILD_TYPE Debug)
。
或命令行参数cmake DCMAKE_BUILD_TYPE=Release
。
编译器参数
CMAKE_C_FLAGS
CMAKE_CXX_FLAGS
也可以通过指令ADD_DEFINITIONS()
添加。
包含路径
CMAKE_INCLUDE_PATH
配合FIND_FILE()
以及FIND_PATH()
使用。如果头文件没有存放在常规路径(/usr/include, /usr/local/include等),则可以通过这些变量就行弥补。如果不使用FIND_FILE
和FIND_PATH
的话,CMAKE_INCLUDE_PATH
没有任何作用。
CMAKE_LIBRARY_PATH
配合配合FIND_LIBRARY()
使用。否则没有任何作用。
CMAKE_MODULE_PATH
CMake为上百个软件包提供了查找器(finder):FindXXXX.cmake。当使用非CMake自带的finder时,需要指定finder的路径,这就是CMAKE_MODULE_PATH
,配合FIND_PACKAGE()
使用。
编写CMakeLists.txt的示例
对于如下的目录结构。
1 | +example |
多文件夹编译(手动)
将所有子文件夹中的源文件包含进来,然后生成。
在顶文件夹中example的CMakeLists.txt中:
1 | cmake_minimum_required(VERSION 2.6) |
在src目录的CMakeLists.txt中:
1 | ## list the source files for this directory |
在thirdparty目录的CMakeLists.txt中:
1 | ## list the source files for this directory |
多文件夹编译(自动)
和上一种方式一样,只不过是自动包含。
在顶文件夹中example的CMakeLists.txt中:
1 | cmake_minimum_required(VERSION 2.6) |
另一种多文件夹编译(自动)
也是将所有子文件夹中符合后缀的文件自动包含进来,然后生成。
在顶文件夹example中的CMakeLists.txt中:
1 | cmake_minimum_required(VERSION 2.6) |
多文件夹使用静态库
将thirdparty中的文件编译为一个静态库,由src中的另一个源文件在生成可执行文件时调用。
在顶文件夹中example的CMakeLists.txt中:
1 | cmake_minimum_required(VERSION 2.6) |
在src目录的CMakeLists.txt中:
1 | #include_directories(${PROJECT_SOURCE_DIR}/thirdparty) |
在thirdparty目录的CMakeLists.txt中:
1 | set(LIB_SRC hello.cpp) |
多文件夹使用动态库
将thirdparty中的文件编译为一个动态库,由src中的另一个源文件在生成可执行文件时调用。
与之前静态库的区别是add_library时的参数,和兼顾平台的部分。
thirdparty目录中的hello.h文件添加WIN32平台的导入导出设置:
1 |
|
在thirdparty目录的CMakeLists.txt中:
1 | set(LIB_SRC hello.cpp) |
查找并使用其他程序库
为VS生成debug和release版
使用
CMAKE_CXX_FLAGS_DEBUG
和CMAKE_CXX_FLAGS_RELEASE
。
以上文“多文件夹编译(自动)”为例,顶文件夹中example的CMakeLists.txt修改为:
1 | cmake_minimum_required(VERSION 2.6) |
在用ccmake生成时,图形界面会出现CMAKE_BUILD_TYPE
的选项。根据不同选择会生成不同结果。
调试
gdb
gdb是一个用来调试C和C++程序的功能强大的调试器,能在程序运行时观察程序的内部结构和内存使用情况。
gdb主要提供以下功能:
- 监视程序中变量的值的变化。
- 设置断点,使程序在指定的代码行上暂停执行,便于观察。
- 单步执行代码。
- 分析崩溃程序产生的core文件。
推荐书籍《Debugging with GDB》[在线][下载pdf]。通过在gdb下输入help
或在命令行上输入gdb h
查看关于gdb选项说明的简单列表。键入help后跟命令的分类名。可以获得该类命令的详细清单。搜索和word相关的命令可用apropos word
。
为使gdb能正常工作,必须在程序编译时包含调试信息。即-g
选项。前文有讲解。
简单的调试步骤示例
- 载入test可执行文件
gdb test --silent
。 - 运行
run
。 - 查看程序出错的地方
where
。 - 查看出错函数附近的代码
list
。 - 打开堆栈
backtrace
。 - 单步调节
next
或step
。 - 查看可疑表达式值
print var
。 - 在可疑行打断点
break 8
。 - 重新运行会在断点处停止。用
set variable
修改变量值。 - 继续运行
continue
。看结果是否正确。 - 退出gdb
quit
。
常用命令介绍
Valgrind
Valgrind是一个工具集,其中最著名的就是可以检查内存泄露的Memcheck。
在ubuntu上安装Valgrind的命令sudo apt-get install valgrind
。
与gdb的要求相同,程序编译时需要有-g参数。
对于平时用myprog arg1 arg2
启动的可执行文件,使用valgrind --leak-check=yes myprog arg1 arg2
就可以开启内存检查,如果有错误会输出。
Valgrind命令的一般格式为valgrind [valgrind-options] your-prog [your-prog-options]
。
--tool=<toolname>
可以选择当前运行的工具,默认即为memcheck。
--help
显示帮助。
[1] http://www.viemu.com/
[2] http://bbs.zol.com.cn/diybbs/d34450_19858.html
[3] http://4gamers.cn/blog/2014/06/19/make-your-vim-weapon/
[4] http://derekwyatt.org/vim/tutorials/
[5] http://www.phecda.org
[6] http://blog.vgod.tw/tag/mit/?variant=zh-cn
[7] http://vim.spf13.com/#vimrc
[8] http://ram.kossboss.com/spf13c/
[9] A Byte of Vim
[10] http://michael.peopleofhonoronly.com/vim/
[11] Linux环境编程
[12] Linux C编程一站式学习
[13] Linux环境下C编程指南
[14] An Introduction to GCC
[15] 跟我一起写Makefile
[16] Mastering CMake
[17] http://www.ibm.com/developerworks/cn/linux/l-cn-cmake/
[18] http://blog.sina.com.cn/s/blog_9ce5a1b501015avz.html
[19] http://blog.csdn.net/dbzhang800/article/details/6314073
[20] http://blog.csdn.net/dbzhang800/article/details/6329068
[21] 用GDB调试程序
[22] GDB调试命令手册
[23] Debugging with GDB
[24] http://www.vingel.com/tools/reference/gdb-commands-list.png
[25] https://wiki.ubuntu.com/Valgrind
[26] http://valgrind.org/docs/manual/quick-start.html
[27] http://valgrind.org/docs/manual/manual.html