第四五周作业
作业目的
**一.用gcc生成静态库和动态库以及静态库.a与.so库文件的生成与使用
二.改编第一次作业的程序代码创建静态库和动态库并生成可执行文件程序,记录文件的大小,并与之前做对比
三.说明gcc编译工具集中各软件的用途,了解ELF文件格式,汇编语言格式
四.OpenCV
一.用gcc生成静态库和动态库。
在windows下,一般可以通过文件的后缀名来识别文件的类型。在Linux下大致上也是可以的。但是要明确的一点是,在linux下,文件的后缀与文件的类型是没有必然的联系的。这只是约定俗称的习惯罢了。
在linux 下进行C/C++开发,一般都是使用的gcc编译器,所以本文的讲解以gcc为主。
.o文件,即目标文件。一般通过.c或者.cpp文件编译而来,相当于VC编译出来的obj文件
.so文件,shared object 共享库(对象),相当于windows下的dll。
.a文件,archive 归档包,即静态库。其实质是多个.o文件打包的结果,相当于VC下的.lib文件
.la文件,libtool archive 文件,是libtool自动生成的共享库文件。
在linux中用gcc命令实现静态库和动态库的创建
1.1先创建一个作业目录来存放文件
mkdir test1
cd test1
1.2创建程序 hello.h、hello.c 和 main.c的文件
1.3用gedit编译文件
文件hello.h:
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
文件hello.c:
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
文件main.c:
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
其中
hello.h为该函数库的头文件。
main.c为测试库文件的主程序, 在主程序中调用了公用函数 hello。
1.4将 hello.c 编译成.o 文件
输入以下命令得到hello.o文件
gcc -c hello.c
运行IS命令查看是否生成
1.5由.o 文件创建静态库
ar -crv libmyhello.a hello.o //创建名为libmyhello.a的静态库文件
运行 ls 命令看看是否生成了 hello.o 文件(由运行结果可知已经生成了hello.o目标文件)
1.6在程序中使用静态库
方法一:执行命令
gcc -o hello main1.c -L. –lmyhello
方法二:
gcc main1.c libmyhello.a -o hello
方法三:
先生成 main.o:
gcc -c main.c
再生成可执行文件:
gcc -o hello main.o libmyhello.a
然后运行./hello
./hello
删除静态库文件试试公用函数hello是否真的连接到目标文件hello中
rm libmyhello.a
./hello
结果如下
程序照常运行,静态库中的公用函数已经连接到目标文件中了。
1.7由.o 文件创建动态库文件
用gcc来创建动态库文件,输入以下命令来得到动态库文件。
gcc -shared -fPIC -o libmyhello.so hello.o
用IS命令查看
1.8在程序使用动态库
输入以下命令来运行动态库
gcc main.c libmyhello.so -o hello
运行结果
二.改编第一次作业的程序代码创建静态库和动态库并生成可执行文件程序,记录文件的大小,并与之前做对比
2.1代码的创建修改
main1代码:
#include<stdio.h>
#include"sub1.c"
#include"sub2.c"
void main()
{
int a=4,b=2;
float c;
float d;
c=x2x(a,b);
d=x2y(a,b);
printf("%f\n",c);
printf("%f\n",d);
}
metanoia@ubuntu:~$ touch sub2.c //创建sub2.c
metanoia@ubuntu:~$ gedit sub2.c
#include<stdio.h>
float x2y (int a,int b)
{
float d;
d=a*b;
return d;
}
2.2编译生成.o文件及创建静态库和动态库可执行程序
编译生成.o文件
此时已经生成
由.o 文件创建动态库文件
metanoia@ubuntu:~/testttt$ gcc -shared -fPIC -o libmain1.so sub1.o sub2.o //生成动态库文件libmain1.so
metanoia@ubuntu:~/testttt$ ls //查看是否生成动态库文件
生成
在程序中使用动态库
gcc -shared -fPIC -o libsub.so sub1.o sub2.o //生成动态库
gcc -o main2 main1.c libsub.so //生成可执行文件
sudo cp libsub.so /usr/lib //把 so 文件拷贝到对应路径
./main2 //运行文件
运行该程序文件
2.3比较文件大小
查看文件大小
对比静态库和动态库,动态库文件比静态库文件要大得多,生成的可执行文件相差不大。
三.说明gcc编译工具集中各软件的用途,了解ELF文件格式,汇编语言格式
1.准备
先创建一 个工作目录 test0,然后
用文本编辑器生成一个 C 语言编写的简单 Hello.c 程序为示例,其源代码如下所示:
#include <stdio.h>
//此程序很简单,仅仅打印一个 Hello World 的字符串。
int main(void)
{
printf("Hello World! \n");
return 0;
}
2.预处理
预处理的过程主要包括以下过程:
(1) 将所有的#define 删除,并且展开所有的宏定义,并且处理所有的条件预编
译指令,比如#if #ifdef #elif #else #endif 等。
(2) 处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置。
(3) 删除所有注释“//”和“/* */”。
(4) 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号。
(5) 保留所有的#pragma 编译器指令,后续编译过程需要使用它们。
使用 gcc 进行预处理的命令如下:
$ gcc -E hello.c -o hello.i
3.编译
编译过程就是对预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。
使用 gcc 进行编译的命令如下:
$ gcc -S hello.i -o hello.s
4.汇编
汇编过程调用对汇编代码进行处理,生成处理器能识别的指令,保存在后缀为.o的目标文件中。由于每一个汇编语句几乎都对应一条处理器指令,因此,汇编相对于编译过程比较简单,通过调用 Binutils 中的汇编器 as 根据编指令和处理器指令的对照表一一翻译即可。当程序由多个源代码文件构成时,每个文件都要先完成汇编工作,生成.o 目标文件后,才能进入下一步的链接工作。注意:目标文件已经是最终程序的某一部
分了,但是在链接之前还不能执行。
使用 gcc 进行汇编的命令如下:
$ gcc -c hello.s -o hello.o
5.链接
链接也分为静态链接和动态链接,其要点如下:
(1) 静态链接是指在编译阶段直接把静态库加入到可执行文件中去,这样可执行文件会比较大。链接器将函数的代码从其所在地(不同的目标文件或静态链接库中)拷贝到最终的可执行程序中。为创建可执行文件,链接器必须要完成的主要任务是:符号解析(把目标文件中符号的定义和引用联系起来)和重定位(把符号定义和内存地址对应起来然后修改所有对符号的引用)。
(2) 动态链接则是指链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。
1.在 Linux 系统中,gcc 编 译链 接时的动态库搜索路径顺序通常为:首先从 gcc 命令的参数-L 指定的路径寻找 ;再从环境变量LIBRARY_PATH 指 定的路径寻址;再从默认路径/lib、/usr/lib、 /usr/local/lib 寻找 。
2.在 Linux 系统中,执 行二 进制 文件 时的 动态 库搜 索路 径的 顺序 通常 为:首 先搜 索编 译目标 代码 时指 定的 动态 库搜 索路 径;再从环境变量 LD_LIBRARY_PATH 指定的路径寻址;再从配置文件/etc/ld.so.conf 中指定动态库搜索路径 ;再从默认路径/lib、/usr/lib寻找。
3.在 Linux 系统 中, 可以用 ldd命令查看一个可执行程序依赖的共享库。 由于链接动态库和静态库的路径可能有重合,所以如果在路径中有同名的静态库文件和动态库文件,比如 libtest.a 和 libtest.so,gcc 链接时默认优先选择动态库,会链接libtest.so,如果要让 gcc 选择链接 libtest.a 则可以指定 gcc 选项-static,该选项会强制使用静态库进行链接。以 Hello World 为例:
如果使用命令“gcc hello.c -o hello”则会使用动态库进行链接,生成的
ELF 可执行文件的大小(使用 Binutils 的 size 命令查看)和链接的动态库
(使用 Binutils 的 ldd 命令查看)如下所示:
$ gcc hello.c -o hello
$ size hello //使用 size 查看大小
$ ldd hello //可以看出该可执行文件链接了很多其他动态库,主要是 Linux的glibc动态库
如 果 使 用 命 令 “ gcc -static hello.c -o hello”则 会 使 用 静 态 库 进 行 链 接 ,生成的 ELF 可执行文件的大小(使用 Binutils 的 size 命令查看)和链接的
动态库(使用 Binutils 的 ldd 命令查看)如下所示:
$ gcc -static hello.c -o hello
$ size hello //使用 size 查看大小
$ ldd hello
6.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 hello
7.反汇编 ELF
由于 ELF 文件无法被当做普通文本文件打开,如果希望直接查看一个 ELF 文件包含的指令和数据,需要使用反汇编的方法。
使用 objdump -D 对其进行反汇编如下:
$ objdump -D hello
四.opencv
1.编写一个打开图片进行特效显示的代码
#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;
}
输入命令执行:
gcc test1.cpp -o test1 `pkg-config --cflags --libs opencv` //保存编译
./test1 //运行
运行结果:
2.编写一个打开摄像头显示处理视频的程序代码
代码:
/*********************************************************************
打开电脑摄像头,空格控制视频录制,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;
}
执行命令:
g++ test2.cpp -o test2 `pkg-config --cflags --libs opencv`
./test
结果如下: