以前没系统学过linux下C++的一些基本开发方法,所以抽空在网易云课堂上听了个课程简单学习一下基础,本文记录一下听课中的笔记。
课程前几章介绍开发环境、linux基础操作、配置SAMBA共享目录啥的就不进行记录了,毕竟听这个课是为了补一下linux下如何进行C++编程的基础知识。
最简单的两个命令记录一下:
g++ -c main.cpp -o main.o
:表示将main.cpp编译成main.o中间文件。
g++ main.o -o helloworld
:表示将main.o链接成可执行文件helloworld。
也可直接:
g++ main.cpp -o helloworld
第五章 makefile
makefile的基本规则在另一篇笔记里记录了,这里只记录几个新的点。
增量编译
增量编译其实在记录makefile的那篇笔记里也提过,就是当某个文件改变时,只增量编译受影响的文件,而不是把整个项目重新编译。
增量编译是靠文件时间来判断的,查看文件时间可以用:ls -ls --full-time
所谓的比较时间,规则如下:
- 如果target不存在,则执行make,否则转2;
- 如果dependencies的时间比target时间新,则执行make,否则转3;
- 输出已经是最新的了,不执行规则。
优化makefile
这个教程里的优化写的比较简单,但也可以学习:
- 使用变量、通配符
1 | %.o:%.cpp |
- 自动罗列*.o文件
1 | CXX_SOURCES=$(wildcard*.cpp) |
- 头文件依赖
使用编译选项-MMD,比如g++ -c -MMD main.cpp -o main.o
则会在生成main.o的同时生成main.d文件,该文件里的内容就是所需要的依赖,然后如下操作:
1 | DEP_FILES=$(patsubst%.o,%.d,$(CXX_OBJECTS)) |
- 利用foreach函数进行对子目录的支持
该章节最终的makefile如下,能支持简单使用了:
1 |
|
GDB
在compile这一步添加选项-g,如果没有用-g选项生成的可执行文件虽然也可以gdb运行,但是调试过程中不会显示调试信息,没什么用。
g++ -g main.cpp -o hello
b:添加断点
r:从头开始运行
n:下一步
c:程序继续运行直到下一处断点
p:显示表达式的值
info break:显示断点信息
del break n:删除编号n的断点
q:退出gdb
disp:监视变量的值
x:显示内存:x/16xb n,看16个单元,x表示16进制看,b表示每个单元多少字节,b是1个字节,h是2个字节,w是4个字节,g是8个字节。
bt:显示堆栈。
段错误
在Linux下,程序中如果进行了不正确的指针操作(如空指针访问、野指针访问),那么程序会崩溃,发生段错误(Segment Fault)。
很容易复现的话就可以直接用GDB来调试。
当错误不容易复现的时候,可以使用内存转储的手段。
内存转储步骤如下:
- ulimit -c unlimited
- 运行程序,程序发生段错误退出时会将信息转储到core.*文件中
- 用gdb来查看段错误的代码位置
gdb exe core.*
。
一个程序生成时用了-g选项的话,生成的可执行文件会更大一些,因为包含了可调式信息。
如果要判断一个程序是否包含可调式信息,可以用objdump -h helloworld
来判断,有debug就是有调试信息。
适用场合
- 单元测试
- 段错误的定位
不适用场合
- 大型程序
- 多线程
动态库和静态库
生成动态库
编译:g++ -c -fPIC example.cpp -o example.o
链接:g++ -shared example.o -o libexample.so
PIC:position independent code位置无关代码
动态库命名规范:libxxx.so
可以使用nm命令查看库中的符号。
使用动态库
包含头文件,调用里面的函数即可。
编译:g++ -c main.cpp -o main.o
链接:g++ main.o -o helloworld -L. -leample
-L.:指定库文件的位置
-leample:使用libexample.so这个动态库。
操作系统默认从标准位置寻找相应的库:/lib /usr/lib /usr/local/lib
如果没有就会去LD_LIBRARY_PATH环境变量里找。
所以要想使用自己的库,得做好上述设置。
查看依赖的库
readelf -d helloworld
通常目录结构是:
1 | libxxx/ |
常用CXXFLAGS表示C++编译选项,LDFLAGS表示链接选项。
静态库
命名标准:libxxx.a
第一步,编译得到.o文件
第二步,打包ar -rcs libxxx.a file1.o file2.o...
静态库本质就是将.o文件打个包而已,因此可以像.o文件一样使用。
当静态库和动态库同时存在的时候,会优先选择动态库进行链接,若要使用静调库,泽科使用全路径方式指定静态库。
c++兼容c的库
以前就学过,用external “C”即可,但比较好的做法是在.h文件中就写好兼容C和C++的版本。
1 |
|
动态库的手工加载
使用dl库中的函数
#include<unistd.h>
#include<dlfcn.h>
具体再看教程吧,感觉用的不多,主要用dlopen、dlsym和dlclose方法。
Linux文件读写
简单来说有两种方式:
- ANSI C,使用stdio.h里的函数fopen、fclose、fwrite、fread。
- Linux API,open、close、write、read
推荐使用第一种吧,能被各平台支持。
使用第一种的时候,文件路径使用/,换行符Linux和windows下不同,linux是\n,windows是\n\r。
普通情况下均推荐使用ANSIC来操作文件。
仅当该文件表示一个外部设备时,才用LinuxAPI来操作。
线程
在Linux下,使用pthread库来创建和操作线程,pthread是Linux系统自带的一个库。
1 | pthread_create(&handle,//指代一个线程对象,pthreat_t类型的实例 |
线程主函数退出时,线程自然终止。
pthread_join(handle,NULL):等待B线程自然退出,在B线程退出后回收B线程的系统资源。
pthread_cancel:用于终止取消一个正在运行的线程,不推荐使用。
互斥体
pthread_mutex_t hMutex;
pthread_mutex_init(&hMutext,NULL);
pthread_mutext_destroy(&hMutext);
pthread_mutext_lock(&hMutex);//不能获取锁的时候会阻塞,但trylock不能获取锁的时候就会直接返回。
pthread_mutext_unlock(&hMutex);
信号量Semaphore
1 |
|
进程相关
何为进程?
当helloworld文件被运行后,在操作系统内部创建一个进程对象。
查看进程:ps。
top:实时查看进程。
kill强制杀死进程。
前台进程和后台进程
关闭终端时会关闭前台进程。
以后台进程运行,在命令行后面加一个&符号。
前后台进程切换:ctrl+z暂停任务,bg将任务送到后台继续任务,fg将任务拿到前台运行。
进程间通信
IPC:inter-process communicating。
四种方式:管道、消息队列、共享内存、网络套接字。
管道:pipe,是Linux操作系统提供的一个消息传递机制。
- 系统中创建一个文件,其文件类型为管道
mkfifo message
- 进程A打开该文件写入数据
- 进程B打开该文件读取数据
open是阻塞的,read也是阻塞的,管道是单向的,和普通文件有区别。
系统调用
查看可用的系统调用man syscalls
如果找不到对应的系统调用,可以在代码里直接调用命令行,用system().
int ret = system("rm -rf *.txt");
该函数会阻塞。
也可以用popen():调用某个命令行,并获取其标准输出。
该函数是linux特有的,windows下对应的叫_popen/_pclose
1 | FILE* fp = popen("ifconfig","r"); |
跨平台
尽量使用标准函数/类型/语法
可以用条件编译。