Linux编程基础
C++是一个功能强大而又应用广泛的计算机语言,就应用领域而言,几乎无所不在,因为有操作系统的地方就会有C++的存在,热门程序而言,也就仅次于C和Java.而C与C++又有天然的血缘关系。如果是一个跨平台的项目,也就有必要到Linux下C++的编程有一个进行一个系统的学习和了解。本文主要记录在Linux平台下学习C++的一些总结和心得。希望与大家一起学习与成长!
在进行Linux下C++编程之前,先来了解几个重要的工具和概念:
GUN
在Linux下进行编程,GNU是你永远绕不开的一个概念,因为你处处都可以看到它,那它是一个什么东西呢?
GNU是GNU’s Not Unix的缩写,意思是GNU并不是Unix。那它是什么呢?其实它有多个含义:
GUN项目
1984年,史托曼(Richard Stallman)开始GNU项目,这个项目的目的是创建一个自由、开放的UNIX操作系统(Free Unix)。但是建立一个操作系统谈何容易啊!而且在当时的 GNU 是仅有史托曼一个人单打独斗的,这实在太麻烦,但又不能放弃这个计划。于是史托曼反其道而行之:“既然操作系统太复杂, 我就先写可以在 Unix 上面运行的小程序,这总可以了吧?”基于这个想法, 史托曼便开始参考Unix上的现有软件,并依据这些软件的作用开发出具有相同功能的软件,并将其开源。后来越来越多的人知道免费好用的GUN软件,并且与付费的专利的性能差不了多少,使用GUN软件的人就越来多,GNU项目也逐步打开知名度。
为了这个计划,他开始使用原本Unix上面跑的软件,并自行撰写功能与Unix原有专利软件相仿的软件。但不论是什么软件,都得要进行编译成为二进制档案(binary file)后才能够执行,因此他便开始撰写C语言的编译器,那就是现在相当有名的GNU C(gcc)!这个点相当的重要!这是因为C语言编译器版本众多,但都是专利软件,如果他写的C编译器够棒,效能够佳,那么将会大大的让GNU计划出现在众人眼前!
GUN自由软件协议
GPL协议指的是通用公共许可证(General Public License, GPL)。由于GNU项目开发的软件都是开放源代码的自由软件,这就有可能被他人盗用并对它进行注册而成为专利软件。为解决这个问题,1985年史托曼与律师草拟了有名的通用公共许可协议,你可以在GNU软件源码的每一个文件(如.h、.cpp)开头看到这个协议的声明,
GCC
GCC是Linux最常用的编译器,基本语法格式为:$ gcc [options][filenames]
options参数有很多种,现列一些常用的参数如下:
-c,只编译,不链接成为可执行文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件。
-o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out。
-g,产生符号调试工具(gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项。
-O,对程序进行优化编译、链接,采用这个选项,整个源代码会在编译、链接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是编译、链接的速度就相应地要慢一些。
-O2,比-O更好的优化编译、链接,当然整个编译、链接过程会更慢。
-Wall,开启编译器几乎所有常用的警告,有助于检测程序存在的问题
下面用一些例子来说明如何简单使用gcc命令
home目录下有一个hello.c 源文件,其内容如下:
#include <cstdio.h>int main(){ printf("hello,world\n"); return 0;}123456case1
$ gcc -c hello.c # 对hello.c只编译不链接1
生成一个hello.o 文件,其扩展名为.obj,是目标文件,无法执行。想要执行,必须再将其转换为可执行文件
$ gcc hello.o -o abc # 如果不加abc,则默认生成a.out可执行文件1
执行命令后,生成一个abc 文件,无扩展名,linux下的可执行文件,这时,再用./abc 即可执行,打印输出。当然,也可以将多个.o 文件一起链接,生成一个可执行文件。
case2
$ gcc hello.c -o abc1
直接生成了abc 可执行文件,说明同时完成了编译链接的工作。
$ gcc hello.c -O0$ gcc hello.c -O1$ gcc hello.c -O2 # 使用优化编译和链接,速度慢123
这个貌似无法添加输出的name参数,只能这样,默认输出a.out 文件,./a.out 一样能执行
case3
链接多个程序,假设/home 目录下有三个文件:hello.c, hello_fn.c和头文件hello.h,其内容分别如下:
// hello.hvoid hello (const char * name); // hello_fun.c#include <stdio.h> #include "hello.h" void hello (const char * name) { printf ("Hello, %s!\n", name);} // hello.c#include "hello.h" int main() { hello("world"); // hello函数在hello.h声明,在hello_fun.c中定义 return 0;}123456789101112131415161718下面编译源文件,使用如下命令:
$ gcc -Wall hello.c hello_fn.c -o newhello1
注意到头文件hello.h并未在命令行中指定,源文件中的的 #include "hello.h" 指示符使得编译器自动将其包含到合适的位置。执行后生成newhello 可执行文件。
gcc和g++
gcc既可以编译C程序,也可以编译C++程序(通过扩展名自动确认),但是gcc无法自动链接C++库函数,比如STL。因此,编译C++程序的任务还是交给g++比较好。
编译阶段是相同的,链接阶段g++默认链接c++库,gcc没有。
一般情况下用gcc编译c文件,用g++编译cpp文件。
也可以用gcc编译cpp文件,但后面需要加一个选项-l stdc++,作用是链接C++库。
gcc train.cpp -o exe -l stdc++ 和g++ train.cpp -o exe 算是等效的。
GDB
GDB是Linux下常用的调试工具,其主要功能有如下4点:
启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。
可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
当程序被停住时,可以检查此时你的程序中所发生的事。
你可以改变你的程序,将一个BUG产生的影响修正从而测试其他BUG。
基本用法
GDB主要调试的是C/C++程序。要调试C/C++的程序,首先在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数即可,如下所示:
$ gcc -g hello.c -o hello$ g++ -g hello.cpp -o hello12
编译成功后,可以使用gdb hello 启动GDB调试程序,然后使用一些命令调试程序:
l 列出程序内容
break 16在16行设置断点
break func在func函数入口设置断点
info break可以查看断点信息
r执行程序,
n是单步运行
c继续运行程序
p sum打印变量sum的值
bt可以查看函数堆栈
finish可以退出函数
until num 跳到指定行num,结束循环
q退出gdb
更多GDB调试技巧参考100个gdb小技巧
Makefile
Makefile文件中描述了整个工程所有文件的编译顺序、编译规则(哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译),可以使用make命令来解析这些规则。
C/C++源程序变为可执行程序需要两步,首先是编译(compile),将源文件转化为obj文件或o文件,第二步是链接(link) ,将大量的obj文件合成为可执行文件。
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。
链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(o文件或是obj文件)来链接我们的应用程序。
基本规则
目标 : 需要的条件 (注意冒号两边有空格) 命令 (注意前面用tab键开头)12
总结就是:目标文件依赖于条件,生成则使用相应的命令。如果需要的条件的文件比目标更新的话,就会执行生成命令来更新目标。
case1
如果一个工程有3个头文件,和8个C文件,最朴素的Makefile应该是下面的这个样子的。
output : main.o kbd.o command.o display.o / insert.o search.o files.o utils.o gcc -o edit main.o kbd.o command.o display.o / insert.o search.o files.o utils.o main.o : main.c defs.h gcc -c main.c kbd.o : kbd.c defs.h command.h gcc -c kbd.c command.o : command.c defs.h command.h gcc -c command.c display.o : display.c defs.h buffer.h gcc -c display.c insert.o : insert.c defs.h buffer.h gcc -c insert.c search.o : search.c defs.h buffer.h gcc -c search.c files.o : files.c defs.h buffer.h command.h gcc -c files.c utils.o : utils.c defs.h gcc -c utils.c clean : rm output main.o kbd.o command.o display.o / insert.o search.o files.o utils.o123456789101112131415161718192021222324
很明显,output是最终想得到的可执行文件(Linux系统下可执行文件一般不给扩展名),那具体的工作流程是:
make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
如果edit文件不存在,或是edit所依赖的后面的o文件的文件修改时间要比edit这个文件新,那么就会执行后面所定义的命令来生成edit这个文件。
如果edit所依赖的o文件也不存在,那么make会在当前文件中找目标为o文件的依赖性,如果找到则再根据那一个规则生成o文件。(这有点像一个堆栈的过程)
当然c文件和h文件是存在的,于是make会生成o文件,然后再用o文件生成make的终极任务,也就是执行文件edit了。
case2
上述例程中,重复率比较高,可以试着使用变量,节省代码量。
objects = main.o kbd.o command.o display.o / insert.o search.o files.o utils.o output : $(objects) gcc -o edit $(objects) main.o : main.c defs.h gcc -c main.c kbd.o : kbd.c defs.h command.h gcc -c kbd.c command.o : command.c defs.h command.h gcc -c command.c display.o : display.c defs.h buffer.h gcc -c display.c insert.o : insert.c defs.h buffer.h gcc -c insert.c search.o : search.c defs.h buffer.h gcc -c search.c files.o : files.c defs.h buffer.h command.h gcc -c files.c utils.o : utils.c defs.h gcc -c utils.c clean : rm output $(objects)1234567891011121314151617181920212223
此例中,使用object变量代指一系列的o文件,此时修改其中的o文件,只用改定义的那一处,output行不用改。
case3
make命令可以自动推导,比如
utils.o : utils.c defs.h gcc -c utils.c12
完全可以写成utils.o : defs.h ,因为make只要看到utils.o,就知道是utils.c生成的,而且还知道使用了gcc -c utils.c 命令,但是无法推导utils.c用了哪些头文件,所以要把用到的头文件写出来。于是,更为精简的Makefile如下:
objects = main.o kbd.o command.o display.o / insert.o search.o files.o utils.ogcc = cc # linux下cc和gcc是同一个东西 output : $(objects) cc -o edit $(objects) main.o : defs.h kbd.o : defs.h command.h command.o : defs.h command.h display.o : defs.h buffer.h insert.o : defs.h buffer.h search.o : defs.h buffer.h files.o : defs.h buffer.h command.h utils.o : defs.h clean : rm output $(objects)1234567891011121314151617
上述功能只能适用于简单的程序,且所有文件都放在在同一目录下。
更多Makefile知识可参考:跟我一起写Makefile