一、什么是库
我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
二、使用gcc生成静态库
1.编辑生成例子程序hello.h、hello.c和main.c
创建目录保存本次练习文件
mkdir text1
cd text1
使用vim、nano或者gedit等文本编辑器编辑生成需要的三个文件:hello.h、hello.c和main.c。
这里使用vim编辑器
vim hello.h
vim hello.c
vim main.c
代码如下:
#ifndef HELLO_H
#define HELLO_H
void hellow(const char *name);
#endif //HELLO_H!
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
2.将hello.c编译成.o文件,并使用ls命令查看是否生成hello.o文件
gcc -c hello.c
3.由.o文件创建静态库
静态库文件名的命名规范是以 lib 为前缀,紧接着跟静态库名,扩展名为. a 。例如:我们将创建的静态库名为 myhelo ,则静态库文件名就是 libmyhello.a 。创建静态库用 ar 命令。
ar -crv libmyhello.a hello.o
4.在程序中使用静态库
方法一:
gcc -o hello main.c -L. -lmyhello
方法二:
gcc main.c libmyhello.a -o hello
方法三:
先生成 main.o,在生成可执行文件
gcc -c main.o
gcc -o hello main.o libmyhello.a
运行结果如下:
删除静态库看公用函数hello是否真的连接到目标文件hello中
rm libmyhello
程序运行成功,静态库中的公用函数成功连接到目标文件。
三、创建动态库
1.生成动态库
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀 lib ,但其文件扩展名为.so 。例如:我们将创建的动态库名为 myhello ,则动态库就是 libmyhello.so 。用 gcc 来创建动态库。
gcc -shared -fPIC -o libmyhello.so hello.o
结果如下:
2.使用动态库
gcc -o hello main.c -L. -lmyhello
或者
gcc main.c libmyhello.so -o hello
将libmyhello.so复制到目录 /usr/lib 中
以上方法在复制时发生错误,故采用root身份
四、实例
1)编写一个主程序文件main.c 和两个子程序文件 sub1.c,sub2.c,要求:子程序sub1.c 包含一个算术运算函数 float x2x(int a,int b),此函数功能为对两个输入整型参数做某个运算,将结果做浮点数返回;再扩展写一个x2y函数(功能自定);main函数代码将调用x2x和x2y ;返回结果printf出来。
2)将这3个函数分别写成单独的3个 .c文件,并用gcc分别编译为3个.o 目标文件;将x2x、x2y目标文件用 ar工具生成1个 .a 静态库文件, 然后用 gcc将 main函数的目标文件与此静态库文件进行链接,生成最终的可执行程序,记录文件的大小。
3)将x2x、x2y目标文件用 ar工具生成1个 .so 动态库文件, 然后用 gcc将 main函数的目标文件与此动态库文件进行链接,生成最终的可执行程序,记录文件的大小,并与之前做对比。
1.编写文件sub1.c、sub2.c、main.c、sub.h
利用vim编辑器编写文件,以下代码顺序为sub1.c、sub2.c、main.c、sub.h
2.编译.o文件
使用命令与运行结果如下:
3.生成并使用静态库
生成静态库
然后使用静态库得到运行结果,并用 ll 命令查看记录库文件大小
4.生成并使用动态库
用 ll 命令查看库文件大小可以发现静态库生成的文件比动态库小。
五、gcc的常用命令
GCC 的意思是 GNU C Compiler 。经过了多年的发展,GCC 支持 C语言、 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器套件的意思,虽然缩写一样,但功能上区别很大。
1.编写代码
创建.c文件并编写代码
vim test.c
对这个程序进行编译
gcc test.c -o test
实质上,上述编译过程是分为四个阶段进行的,即预处理(也称预编详, Preprocessing )、编详( compilation )、汇编( Assembly )和连按( Linking )。
2.编译过程
2.1预处理
gcc -E test.c -o test.i 或 gcc -E test.c
可以输出 test.i文件中存放着 test . c 经预处理之后的代码。打开 test.i文件,看一看,就明白了。后面那条指令,是直接在命令行窗口中输出预处理后的代码.gcc 的 -E 选项,可以让编译器在预处理后停止,并输出预处理结果。在本例中,预处理结果就是将 stdio.h 文件中的内容插入到 test.c中了。
2.2编译为汇编代码
gcc -S test.i -o test.s
预处理之后,可直接对生成的 test.i 文件编译, gcc 的 -S 选项,表示在程序编译期间,在生成汇编代码后停止,-o 输出汇编代码文件。
2.3汇编
gcc -c test.s -o test.o
对于上一步生成的汇编代码文件 test.s,gas汇编器负责将其编译为目标文件。
2.4连接
gcc test.o -o test
gcc 连接器是 gas 提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。
对于上一步中生成的 test.o,将其与C标准输入输出库进行连接,最终生成程序 test。
3.多个程序文件的的编译
通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用 gcc 能够很好地管理这些编译单元。
假设有一个由 test1.c 和 test2.c 两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 test,可以使用下面这条命令:
gcc test1.c test2.c -o test
如果同时处理的文件不止一个,gcc仍然会按照预处理、编译和链接的过程依次进行。上面这个命令大致相当于依次执行下面三个命令:
gcc -c test1.c -o test1.o
gcc -c test2.c -otest2.o
gcc test1.o test2.o -o test
4.检错
gcc -pedantic illcode.c -o illcode
-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它仅仅只能用来帮助。-pedantic 选项能够帮助程序员发现一些不符合ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。
gcc -Wall illcode.c -o illcode
GCC 给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。所以,在编译程序时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:
gcc -Werror test.c -o test
六、ELF文件
可执行与可链接格式 (Executable and Linkable Format,ELF),常被称为 ELF格式,是一种用于可执行文件、目标代码、共享库和核心转储(core dump)的标准文件格式,一般用于类Unix系统,比如Linux,Macox等。ELF 格式灵活性高、可扩展,并且跨平台。比如它支持不同的字节序和地址范围,所以它不会不兼容某一特别的 CPU 或指令架构。这也使得 ELF 格式能够被运行于众多不同平台的各种操作系统所广泛采纳。
ELF文件一般由三种类型的文件:
- 目标文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。比如编译的中间产物.o文件;
- 可执行文件:一个可执行文件;
- 动态库:文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。比如linux下的.so文件。
1.ELF文件的的段
ELF文件格式如下所示,位于 ELF Header 和 Section Header Table 之间的都是段(Section)。一个典型的 ELF 文件包含下面几个段:
- .text:已编译程序的指令代码段。
- .rodata:ro 代表 read only,即只读数据(譬如常数 const)。
- .data:已初始化的 C 程序全局变量和静态局部变量。
- .bss:未初始化的 C 程序全局变量和静态局部变量。
- .debug:调试符号表,调试器用此段的信息帮助调试。
使用readelf -S可查看其各个section的信息
readelf -S test
2.反汇编ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包含的指令和数据,需要使用反汇编的方法。
objdump -D test
将其反汇编并且将其C语言源代码混合显示出来
gcc -o test -g test.c
objdump -S test
七、用opencv打开图片及视频
1.opencv的安装
2.用opencv打开图片
建立code文件夹存放代码,然后打开文件夹创建test1.cpp文件
在test1.cpp文件里复制下面代码
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
CvPoint center;
double scale = -3;
IplImage* image = cvLoadImage("lena.jpg");
argc == 2? cvLoadImage(argv[1]) : 0;
cvShowImage("Image", image);
if (!image) return -1; center = cvPoint(image->width / 2, image->height / 2);
for (int i = 0;i<image->height;i++)
for (int j = 0;j<image->width;j++) {
double dx = (double)(j - center.x) / center.x;
double dy = (double)(i - center.y) / center.y;
double weight = exp((dx*dx + dy*dy)*scale);
uchar* ptr = &CV_IMAGE_ELEM(image, uchar, i, j * 3);
ptr[0] = cvRound(ptr[0] * weight);
ptr[1] = cvRound(ptr[1] * weight);
ptr[2] = cvRound(ptr[2] * weight);
}
Mat src;Mat dst;
src = cvarrToMat(image);
cv::imwrite("test.png", src);
cvNamedWindow("test",1); imshow("test", src);
cvWaitKey();
return 0;
}
编译此文件
g++ test1.cpp -o test1 `pkg-config --cflags --libs opencv `
使用pkg-config时,选项–cflags 它是用来指定程序在编译时所需要头文件所在的目录,选项 --libs则是指定程序在链接时所需要的动态链接库的目录。
在该文件夹下准备一张图片命名为lena.jpg,执行 ./test1 命令得到运行结果。
3.用opencv播放视频
3.1使虚拟机获取摄像头权限
3.2播放视频
创建test2.cpp文件,写入以下代码,保存后并编译
#include <opencv2/opencv.hpp>
using namespace cv;
int main()
{
//读取视频
VideoCapture capture("park.mp4");
//循环显示每一帧
while(1){
Mat frame;//定义一个Mat变量,用于存储每一帧的图像
capture >> frame;//读取当前帧
if(frame.empty())//播放完毕,退出
break;
imshow("读取视频帧",frame);//显示当前帧
waitKey(30);//掩饰30ms
}
system("pause");
return 0;
}
VideoCapture capture(0),后面的参数设置为 0 ,则从摄像头读取视频并循环显示每一帧;如果设置为一个视频的文件名,如:park.mp4 ,则会将视频读取并循环显示每一帧。
4.打开摄像头录制视频
创建test3.cpp文件,复制以下代码后保存文件,然后进行编译
gedit test3.cpp
g++ test3.cpp -o test3 `pkg-config --cflags --libs opencv `
./test3
/*********************************************************************
打开电脑摄像头,空格控制视频录制,ESC退出并保存视频RecordVideo.avi
*********************************************************************/
#include<iostream>
#include <opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
int main()
{
//打开电脑摄像头
VideoCapture cap(0);
if (!cap.isOpened())
{
cout << "error" << endl;
waitKey(0);
return 0;
}
//获得cap的分辨率
int w = static_cast<int>(cap.get(CV_CAP_PROP_FRAME_WIDTH));
int h = static_cast<int>(cap.get(CV_CAP_PROP_FRAME_HEIGHT));
Size videoSize(w, h);
VideoWriter writer("RecordVideo.avi", CV_FOURCC('M', 'J', 'P', 'G'), 25, videoSize);
Mat frame;
int key;//记录键盘按键
char startOrStop = 1;//0 开始录制视频; 1 结束录制视频
char flag = 0;//正在录制标志 0-不在录制; 1-正在录制
while (1)
{
cap >> frame;
key = waitKey(100);
if (key == 32)//按下空格开始录制、暂停录制 可以来回切换
{
startOrStop = 1 - startOrStop;
if (startOrStop == 0)
{
flag = 1;
}
}
if (key == 27)//按下ESC退出整个程序,保存视频文件到磁盘
{
break;
}
if (startOrStop == 0 && flag==1)
{
writer << frame;
cout << "recording" << endl;
}
else if (startOrStop == 1)
{
flag = 0;
cout << "end recording" << endl;
}
imshow("picture", frame);
}
cap.release();
writer.release();
destroyAllWindows();
return 0;
}
八、总结
这次作业练习了.o文件生成并使用静态库和动态库,然后再一次熟悉了gcc的常用命令,了解了ELF文件格式,熟悉了opencv打开图片、播放视频和获取摄像头的操作。这次作业很耗时,也很考验耐心!
参考资料
https://blog.csdn.net/weixin_46129506/article/details/120646081
https://blog.csdn.net/qq_43279579/article/details/109026927