Linux学习之路4
本文介绍了Linux开发中的核心工具和技术。主要内容包括:1)静态库与动态库的制作与使用,对比了两者的特点和适用场景;2)makefile的编写方法,展示如何实现自动化编译;3)main函数参数的使用和环境变量操作;4)gdb调试工具的使用,包括安装、常用命令、调试core文件和正在运行的程序。文章通过具体示例演示了各种技术的实际应用,为Linux开发提供了实用的技术参考。重点强调了动态库的优势和
目录
1、静态库和动态库
在实际开发中,我们把通用的函数和类分文件编写,称之为库。在其它的程序中,可以使用库中的函数和类。
一般来说,通用的函数和类不提供源代码文件(安全性、商业机密),而是编译成二进制文件。
库的二进制文件有两种:静态库和动态库。
一、静态库
1)制作静态库
g++ -c -o lib库名.a 源代码文件清单
2)使用静态库
不规范的做法:
g++ 选项 源代码文件名清单 静态库文件名
规范的做法:
g++ 选项 源代码文件名清单 -l库名 -L库文件所在的目录名
3)静态库的概念
程序在编译时会把库文件的二进制代码链接到目标程序中,这种方式称为静态链接.
如果多个程序中用到了同一静态库中的函数或类,就会存在多份拷贝.
4)静态库的特点
- 静态库的链接是在编译时期完成的,执行的时候代码加载速度快.
- 目标程序的可执行文件比较大,浪费空间.
- 程序的更新和发布不方便,如果某一个静态库更新了,所有使用它的程序都需要重新编译.
二、动态库
1)制作动态库
g++ -fPIC -shared -o lib库名.so 源代码文件清单
2)使用动态库
不规范的做法:
g++ 选项 源代码文件名清单 动态库文件名
规范的做法:
g++ 选项 源代码文件名清单 -l库名 -L库文件所在的目录名
运行可执行程序的时候,需要提前设置
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:要追加的文件所在目录的完整路径
echo $LD_LIBRARY_PATH #查看是否成功追加
环境变量。
3)动态库的概念
程序在编译时不会把库文件的二进制代码链接到目标程序中,而是在运行时候才被载入.
如果多个进程中用到了同一动态库中的函数或类,那么在内存中只有一份,避免了空间浪费问题.
4)动态库的特点
- 程序在运行的过程中,需要用到动态库的时候才把动态库的二进制代码载入内存。
- 可以实现进程之间的代码共享,因此动态库也称为共享库。
- 程序升级比较简单,不需要重新编译程序,只需要更新动态库就行了。
三、优先使用动态库
如果动态库和静态库同时存在,编译器将优先使用动态.
2、makefile
在实际开发中,项目的源代码文件比较多,按类型、功能、模块分别存放在不同的目录和文件中,哪些文件需要先编译,那些文件后编译,那些文件需要重新编译,还有更多更复杂的操作。
make是一个强大的实用工具,用于管理项目的编译和链接。make需要一个编译规则文件makefile,可实现自动化编译。
一.测试代码1(makefile文件)
INCLUDEDIR=-I/home/wucz/tools -I/home/wucz/api
LIBDIR=-L/home/wucz/tools -L/home/wucz/api
all:demo01 demo02 demo03
demo01:demo01.cpp
g++ -o demo01 demo01.cpp $(INCLUDEDIR) $(LIBDIR) -lpublic -lmyapi
cp demo01 /tmp/.
demo02:demo02.cpp
g++ -o demo02 demo02.cpp $(INCLUDEDIR) $(LIBDIR) -lpublic -lmyapi
demo03:demo03.cpp
g++ -o demo03 demo03.cpp $(INCLUDEDIR) $(LIBDIR) -lpublic -lmyapi
clean:
rm -f demo01 demo02 demo03
测试代码2:
# 指定编译的目标文件是libpublic.a和libpublic.so
all:libpublic.a \
libpublic.so
# 编译libpublic.a需要依赖public.h和public.cpp
# 如果被依赖文件内容发生了变化,将重新编译libpublic.a
libpublic.a:public.h public.cpp
g++ -c -o libpublic.a public.cpp
libpublic.so:public.h public.cpp
g++ -fPIC -shared -o libpublic.so public.cpp
# clean用于清理编译目标文件,仅在make clean才会执行。
clean:
rm -f libpublic.a libpublic.so
3、main函数的参数
一、main函数的参数
main函数有三个参数,argc、argv和envp,它的标准写法如下:
int main(int argc,char *argv[],char *envp[])
{
return 0;
}
argc 存放了程序参数的个数,包括程序本身。
argv 字符串的数组,存放了每个参数的值,包括程序本身。
envp 字符串的数组,存放了环境变量,数组的最后一个元素是空。
在程序中,如果不关心main()函数的参数,可以省略不写。
二、操作环境变量
1)设置环境变量
int setenv(const char *name, const char *value, int overwrite);
name 环境变量名。
value 环境变量的值。
overwrite 0-如果环境不存在,增加新的环境变量,如果环境变量已存在,不替换其值;非0-如果环境不存在,增加新的环境变量,如果环境变量已存在,替换其值。
返回值:0-成功;-1-失败(失败的情况极少见)。
注意:此函数设置的环境变量只对本进程有效,不会影响shell的环境变量。如果在运行程序时执行了setenv()函数,进程终止后再次运行该程序,上次的设置是无效的。
2)获取环境变量的值
char *getenv(const char *name);
三、示例
#include <iostream>
using namespace std;
int main(int argc,char *argv[],char *envp[])
{
if (argc!=4)
{
cout << "表白神器程序的使用方法:./demo 追求者姓名 被追求者姓名 表白内容\n";
return -1;
}
cout << argv[1] << "开始向" << argv[2] << "表白。\n";
cout << argv[3] << endl;
cout << argv[1] << "表白完成。\n";
return 0;
cout << "一共有" << argc << "个参数。\n";
// 显示全部的参数。
for (int ii=0;ii<argc;ii++)
{
cout << "第" << ii << "个参数:" << argv[ii] << endl;
}
// 显示全部的环境变量。
for (int ii=0;envp[ii]!=0;ii++) // 环境变量数组最后一个元素是0。
{
cout << envp[ii] << endl;
}
// 设置环境变量AA。
setenv("AA","aaaa",0);
// 显示环境变量AA的值。
cout << "AA=" << getenv("AA") << endl;
return 0;
}
4、gdb的常用命令
如果程序有问题,不要问别人为什么会这样,而是立即动手调试。
一、安装gdb
sudo apt update
sudo apt install -y gdb
二、gdb常用命令
如果希望程序可调试,编译时需要加-g选项,并且,不能使用-O的优化选项。
gdb 目标程序:
|
命令 |
简写 |
命令说明 |
|
set args |
设置程序运行的参数。 例如:./demo 张三 西施 我是一只傻傻鸟 设置参数的方法是: set args 张三 西施 我是一只傻傻鸟 |
|
|
break |
b |
设置断点,b 20 表示在第20行设置断点,可以设置多个断点。 |
|
run |
r |
开始运行程序, 程序运行到断点的位置会停下来,如果没有遇到断点,程序一直运行下去。 |
|
next |
n |
执行当前行语句,如果该语句为函数调用,不会进入函数内部。 VS的F10 |
|
step |
s |
执行当前行语句,如果该语句为函数调用,则进入函数内部。VS的F11 注意了,如果函数是库函数或第三方提供的函数,用s也是进不去的,因为没有源代码,如果是自定义的函数,只要有源码就可以进去。 |
|
|
p |
显示变量或表达式的值,如果p后面是表达式,会执行这个表达式。 |
|
continue |
c |
继续运行程序,遇到下一个断点停止,如果没有遇到断点,程序将一直运行。 VS的F5 |
|
set var |
设置变量的值。 假设程序中定义了两个变量: int ii; char name[21]; set var ii=10 把ii的值设置为10; set var name="西施"。 |
|
|
quit |
q |
退出gdb。 |
注意:在gdb中,用上下光标键可以选择执行过的gdb命令。
三、示例
#include <iostream>
using namespace std;
void show(const char *name1,const char *name2,const char *message)
{
cout << name1 << "开始表白。\n";
cout << name2 << ":" << message << endl;
}
int main(int argc,char *argv[],char *envp[])
{
if (argc!=4)
{
cout << "表白神器程序的使用方法:./demo 追求者姓名 被追求者姓名 表白内容\n"; return -1;
}
cout << "表白前的准备工作一。\n";
cout << "表白前的准备工作二。\n";
cout << "表白前的准备工作三。\n";
cout << "表白前的准备工作四。\n";
cout << "表白前的准备工作五。\n";
show(argv[1],argv[2],argv[3]);
cout << "表白完成。\n";
for (int ii=0;ii<10;ii++)
{
string str="这是第"+to_string(ii)+"个武装直升机";
cout << str << endl;
}
return 0;
}
5、gdb调试core文件
如果程序在运行的过程中发生了内存泄漏,会被内核强行终止,提示“段错误(吐核/核心已转储)”,内存的状态将保存在core文件中,方便程序员进一步分析。
Linux缺省不会生成core文件,需要修改系统参数。
调试core文件的步骤如下:
1)用ulimit -a查看当前用户的资源限制参数;
2)用ulimit -c unlimited把core file size改为unlimited;(此为临时改法,下次连接时不生效)
3)运行程序,产生core文件;
4)运行gdb 程序名 core文件名;
5)在gdb中,用bt查看函数调用栈。
代码示例:
#include <cstring>
#include <iostream>
using namespace std;
void xc91(const int bh,const string xm)
{
char *ptr=nullptr;
*ptr=78;
//strcpy(ptr,xm.c_str());
}
int main(){
xc91(69,"库里库里库里库里");
return 0;
}
6、gdb调试正在运行中的程序
gdb 程序名 -p 进程编号
新建一个连接或终端对当前运行的程序调试
被调试的程序会暂停运行,并由调试的那一端控制,调试退出后继续运行.
#进程编号获取
ps -ef |grep 进程名
#展示结果的第二列即为进程ID(编号)
代码示例:
#include <unistd.h>
#include <iostream>
using namespace std;
void xc66(const int bh,const string xm)
{
for (int ii=0;ii<10000;ii++)
{
sleep(1);
cout << "ii=" << ii << endl;
}
}
void aa(const int no,const string name)
{
bb(3,"黑塔");
}
int main()
{
aa(8,"火花");
return 0;
}
在gdb中,用bt查看函数调用栈。
更多推荐



所有评论(0)