一,
先来一个大家常常遇到的问题解答:Visual studio 2012 Debug版本转化到Release版本,编译不成功是因为Release中的设置,链接库和Debug不相同。需要手动修改成为一样的才可以。系统并不会直接给你转换成为相同配置。
二,安装gsl 库连接和makefile 的撰写,自己成功在linux下面成功编译通过
/*感谢来自清华大学的陈俊杰同学的修改贡献*/ new version
(I)先来在linux下安装GSL
Linux下 root下面安装gsl
GNU Scientific Library Installation Mannual
------------------------------------------------------------------
OS : Ubuntu 16.04-64 bit
Hardware : Dell Precision T3620 Working Station
******************************************************************
(1) Download GSL library file from : https://www.gnu.org/software/gsl/
(2) Unzip the file : tar -zxvf $(File Name)
(3) Enter unzipped file location, install GSL : ./configure --prefix=$Your Installation Directory
(4) Same location : make
(5) Same location : make install (or sudo make install)
(6) Add path in ~/.bashrc :
export PATH=$PATH:$Your Installation Directory/bin
export C_INCLUDE_PATH=$C_INCLUDE_PATH:$Your Installation Directory/include
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$Your Installation Directory/lib
export GSL_ID=$Your Installation Directory/include
For a source file "src.cpp" and target excutable file "target.out", typical compiling command for g++ compiler is
-I : identify the directory of include file
-lm : using the library "libm.so" or "libm.a" in GSL_LD
-lgsl : using the library "gsl" in GSL_LD
-lgslcblas : using the library "gslcblas" in GSL_LD
Linux下 非root下面安装gsl-1.16
1. 首先在http://ftp.club.cc.cmu.edu/pub/gnu/gsl/下载gsl-1.16.tar.gz
2. tar -zxvf gsl-1.9.tar.gz 解压在/home/dengmin下
3. ./configure --prefix=YOUR_DIRECTORY
比如我习惯安装在如下目录/home/dengmin/gsl1.16
4.make
5.make install
6.使用vi编辑器,编辑用户根目录下配置文件。
vi ~/.bashrc
在文件末尾加上下面的代码:
#GSL
export PATH=$PATH:YOUR_DIRECTORY/bin
export C_INCLUDE_PATH=$C_INCLUDE_PATH:YOUR_DIRECTORY/include
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:YOUR_DIRECTORY/lib
export GSL_LD=YOUR_DIRECTORY/lib
这里YOUR_DIRECTORY就是/home/dengmin/gsl1.16这个路径。
保存,退出
其中最后一个是为了方便链接使用。
7.source ~/.bashrc
8.使用GSL
编辑一个如下的测试程序:
#include "gsl/gsl_math.h"
int main(void)
{
double x=5.0;
double y=gsl_sf_bessel_J0(x);
printf("J0(%g)=%.18e\n", x, y);
return 0;
}
编译文件:
gcc -o al.o -c test.c
链接文件:
gcc -o al.out al.o -lgsl -lgslcblas -L$GSL_LD -lm
或者直接编译链接:
gcc -o al.out test.c -lgsl -lgslcblas -L$GSL_LD -lm
静态链接:
gcc -o al.out al.o -lgsl -lgslcblas -L$GSL_LD -static -lm(这个我没有做测试)
然后在/home/dengmin目录下就会产生一个al.out文件,执行./al.out
就回得到如下输入:
J0(5)=-1.3030001840000000000e+9
这样就成功了。。。。
//------------------------------------------------------------------old version
(II)在windows 下安装:
1.先装 mingw-w64-install.exe
2.再装 MSYS-1.0.1.exe 会有需要设定安装mingw的安装位置(http://www.mingw.org/wiki/msys)
(如果安装msys2-x86_64-20150512,则要修改
fsab 修改为:d:/mingw64 /mingw
)
fstab.sample 文件中是:
#fstab.sample
#This is a sample file for /etc/fstab.
#Currently /etc/fstab is only read during dll initialization.
#I will eventually watch the directory for changes and reread the file.
#The line format is simple in that you give the Win32 path, followed by one or
#more space or tab delimiter, followed by the mount point. Mount points in
#typical UNIX environments must be a physical name on a drive before it can
#actually be used as a mount point. In this implementation the "must exist"
#requirement isn't enforced, however, it will be an aide to such programs as
#find and readline's tab completion if it does exist.
#You can use a # as the first character on the line as a comment indicator.
#Blank lines are ignored.
#Win32_Path Mount_Point
c:/mingw /mingw
c:/ActiveState/perl /perl
3.按如下要求继续安装
(5)进入gsl目录,执行命令:-
./configure-
make-
make install-
(6)完成编译之后,在MSYS安装目录下的-
local/bin有编译了的gsl动态库文件libgsl-0.dll libgslcblas-0.dll-
local/lib有编译好了的静态库文件libgsl.a libgsl.dll.a libgslcblas.a libgslcblas.dll.a-
local/include有编程需要的gsl头文件-
(7)此时你会发现,编译的结果缺少lib文件,现在我们来生成lib文件-
cd /usr/local/bin-
pexports libgsl-0.dll >libgsl-0.def-
pexports libgslcblas-0.dll >libgslcblas-0.def-
这时候就生产了两个def文件-
在VC6上使用GSL(我没有测试过)(搞不懂lib命令的可以参考我前一篇安装FFTW的安装中关于lib命令的使用)
(8)使用VC6或以上版本的lib命令,生成lib文件,具体如下:-
lib /machine:i386 /def:libgsl-0.def-
lib /machine:i386 /def:libgslcblas-0.def-
(9)动态链接库拷贝到系统的Windows文件夹下面;-
将gsl头文件(local/include里面的gsl目录)拷贝到include文件夹下面;-
lib文件和*.a文件(local/lib下面)放到Lib文件夹下面,
编译器tools-options-directories添加Lib路径和Include路径-
最后一步就是在VC6的工程设置中的object/library modules里面添加以下内容:-
libgslcblas-0.lib libgsl-0.lib-
至此,整个gsl的移植工作就完成了。-
在Codeblock上面使用(测试通过)
Codeblock 修改
1. Linker
D:\office\CodeBlocks\build\MinGW32\lib\gcc\mingw32\4.6.2\libgomp.a
D:\office\CodeBlocks\build\MinGW32\lib\libpthread.a
D:\office\CodeBlocks\build\gsl\lib\libgsl.a
D:\office\CodeBlocks\build\gsl\lib\libgslcblas.a
Search directories
complier
D:\office\CodeBlocks\build\gsl\include
$(#cb)\build\mingw32\include
D:\office\CodeBlocks\build\User_include
$(#math)
C:\Users\Administrator\Desktop\H_re_imag_prop\base
linker
$(#cb)\build\mingw32\lib
$(#cb)\build\mingw32\lib\gcc\mingw32\4.6.2
D:\office\CodeBlocks\build\gsl\lib
整个gsl 放到如下文件夹里面:
D:\office\CodeBlocks\build\gsl
注意
1.自己编译的gsl是64位的,所以
在ColdBlock里面要先用64位的编译器
Toolchain executables
compliler's installation directory
D:\office\CodeBlocks\build\MinGW64
注意: 如果是使用Visual stduio , 可以按照如下实现,我也在我的VS 2010上面可以运行了
1. first setup the gsl-1.4-setup.exe
C:\Program Files (x86)\GSL
including
doc
include
lib
uninstall
...
(该文件夹中的GSL直接拷入D:\soft setup\Visual studio 2012\VC\路径中就可以了,注意GSL中包含了WinGsl.h头文件)
2 open vc++2010
项目:
属性, VC++ 目录
包含目录:
C:\Program Files %28x86%29\GSL\include
库目录:
C:\Program Files %28x86%29\GSL\lib
或者:
D:\soft setup\Visual studio 2012\VC\GSL\include
D:\soft setup\Visual studio 2012\VC\GSL\lib
也一样
链接器:
输入
配置 DeBug: win32
附加依赖项
gsl.lib
gslcblas.lib
配置 Release: win32
gsl.lib
gslcblas.lib
头文件
采用:
#include <WinGsl.h>
当然也可以采用<gsl/gsl_sf_lngamma.h>
范列:
#include "stdafx.h"
#include <iostream>
#include <WinGsl.h>
using namespace std;
int main()
{
std::cout << gsl_sf_gamma_inc( 1.5, 0.5 ) << std::endl;
std::cout << gsl_sf_gamma_inc_Q( 1.5, 0.5 ) << std::endl;
std::cout << gsl_sf_gamma_inc_P( 1.5, 0.5 ) << std::endl;
std::cin.get();
return 0;
}
问题:
若出现
gsl_const.h
#include <gsl/gsl_const_num.h>
#include <gsl/gsl_const_cgsm.h>
#include <gsl/gsl_const_mksa.h>
中下面两项为如下:
gsl/gsl_const_cgs.h
gsl/gsl_const_mks.h
则手动修改为
gsl/gsl_const_cgsm.h
gsl/gsl_const_mksa.h
大功告成
我笔记本上是64位的
但是win32都是可以运行的。
---------------------------------------------------------------在linux 和windows下使用GSL库--------------------------------------------
库链接、makefile示范例子:
代码例子在后面附着,直接在命令行中编译,有两种方法:
1. G++ gtest.cpp –o gtes.out `gsl-config–libs --cflags`
2. G++ gtest.cpp –o -lgsl –lgslcblas–L$GSL_LD %这个库lgslcblas很重要,注意不要改为-lcbals 会错的。
这里的变量GSL_LD=-L/home/dengmin/gsl1.16/lib需要提前在/.bashrc中间修改的
~/.bashrc改写为:
export PATH=$PATH:/home/dengmin/gsl1.16/bin
exportC_INCLUDE_PATH=$C_INCLUDE_PATH:/home/dengmin/gsl1.16/in clude
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dengmin/gsl1.16/ lib
exportGSL_LD=/home/dengmin/gsl1.16/lib
#add C++ PATH
exportCPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/home/dengmin/gs l1.16/include
3. Makefile 可以这样写:
GSL_LD=-L/home/dengmin/gsl1.16/lib
testmake_deng.out:gtest.o
g++ -o $@ $^ -lgsl-lgslcblas $(GSL_LD)
gtest.o:gtest.cpp
gcc -c gtest.ogtest.cpp
#这里改为g++都是可以的,但是上面的改为gcc是不行的,因为它无法链接c++库
clean:
rm *.o
.PHONY:clean
附录是程序的例子:
Gtest.cpp 示范
#include <gsl/gsl_sf_bessel.h>
#include <stdio.h>
#include <iostream>
#include <queue>
using namespace std;
int main (void)
{
queue<int> q;
double x = 5.0;
cout<<"x:I am the king: "<<endl;
for (int i =0; i< 10; i++)
q.push(i+1);
while(!q.empty())
{
cout<<q.front()<<endl;
q.pop();
}
double y =gsl_sf_bessel_J0 (x);
printf ("J0(%g)= %.18e\n", x, y);
return 0;
}
---------------------------------------------------------------Makefile撰写的相关知识点-----------------------------------------------------------------------
一. 基本但是非常重要的makefile 知识点:
1. Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是:
$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。
-c 只编译不链接
-c和-o都是gcc编译器的可选参数
-c表示只编译(compile)源文件但不链接,会把.c或.cc的c源程序编译成目标文件,一般是.o文件.
-o用于指定输出(out)文件名.不用-o的话,一般会在当前文件夹下生成默认的a.out文件作为可执行程序.
例如
gcc -c test.c将生成test.o的目标文件
gcc -o app test.c将生成可执行程序app
gcc -c a.c -o a.o表示把源文件a.c编译成指定文件名a.o的中间目标文件(其实在这里,你把-o a.o省掉,效果是一样的,因为中间文件默认与源文件同名,只是后缀变化).
2. linux下文件的类型是不依赖于其后缀名的,但一般来讲:
.o,是目标文件,相当于windows中的.obj文件
.so 为共享库,是shared object,用于动态连接的,和dll差不多
.a为静态库,是好多个.o合在一起,用于静态连接
3. 在Linux里设置环境变量的方法(export PATH)
4. 关于头文件写什么的两个知识点:
1),头文件中可 以写内联函数(inline)的定义。因为inline函数是需要编译器在遇到它的地方根据它的定义把它内联展开的,而并非是普通函数那样可以先声明再链 接的(内联函数不会链接),所以编译器就需要在编译时看到内联函数的完整定义才行。如果内联函数像普通函数一样只能定义一次的话,这事儿就难办了。因为在 一个文件中还好,我可以把内联函数的定义写在最开始,这样可以保证后面使用的时候都可以见到定义;但是,如果我在其他的文件中还使用到了这个函数那怎么办 呢?这几乎没什么太好的解决办法,因此C++规定,内联函数可以在程序中定义多次,只要内联函数在一个.cpp文件中只出现一次,并且在所有的.cpp文 件中,这个内联函数的定义是一样的,就能通过编译。那么显然,把内联函数的定义放进一个头文件中是非常明智的做法。
2),头文件中可以写类(class)的定义。因为在程序中创建一个类的对象时,编译器只有在这个类的定义完全可见的情况下,才能知道这个类的对象应该如何布局,所以,关于类的 定义的要求,跟内联函数是基本一样的。所以把类的定义放进头文件,在使用到这个类的.cpp文件中去包含这个头文件,是一个很好的做法。在这里,值得一提 的是,类的定义中包含着数据成员和函数成员。数据成员是要等到具体的对象被创建时才会被定义(分配空间),但函数成员却是需要在一开始就被定义的,这也就 是我们通常所说的类的实现。一般,我们的做法是,把类的定义放在头文件中,而把函数成员的实现代码放在一个.cpp文件中。这是可以的,也是很好的办法。 不过,还有另一种办法。那就是直接把函数成员的实现代码也写进类定义里面。在C++的类中,如果函数成员在类的定义体中被定义,那么编译器会视这个函数为 内联的。因此,把函数成员的定义写进类定义体,一起放进头文件中,是合法的。注意一下,如果把函数成员的定义写在类定义的头文件中,而没有写进类定义中, 这是不合法的,因为这个函数成员此时就不是内联的了。一旦头文件被两个或两个以上的.cpp文件包含,这个函数成员就被重定义了。
3),#include <> : 直接到系统指定的某些目录中去找某些头文件。
预处理器按以下顺序搜索包含文件:
1. 在包含 #include 语句的文件所在的同一目录中。
2. 在当前打开的包含文件的目录中,采用与打开它们的顺序相反的顺序。 搜索从父包含文件的目录中开始进行,然后继续向上到任何祖父包含文件的目录。
3. 跟随每个 /I 编译器选项指定的路径。
跟随INCLUDE 环境变量指定的路径。
#include “” : 先到源文件所在文件夹去找,然后再到系统指定的某些目录中去找某些头文件。
预处理器按以下顺序搜索包含文件:
1. 跟随每个 /I 编译器选项指定的路径。
通过命令行进行编译时,跟随 INCLUDE 环境变量指定的路径。
二. Linux下环境变量设置的三种方法(来源于网络)。
如想将一个路径加入到$PATH中,可以像下面这样做:
1、控制台中设置,不赞成这种方式,因为他只对当前的shell 起作用,换一个shell设置就无效了:
$PATH="$PATH":/NEW_PATH (关闭shell Path会还原为原来的path)
2、修改 /etc/profile 文件,如果你的计算机仅仅作为开发使用时推存使用这种方法,因为所有用户的shell都有权使用这个环境变量,可能会给系统带来安全性问题。这里是针对所有的用户的,所有的shell
在/etc/profile的最下面添加: export PATH="$PATH:/NEW_PATH"
vi /etc/profile
注:修改文件后要想马上生效还要运行# source /etc/profile不然只能在下次重进此用户时生效。
3、修改bashrc文件,这种方法更为安全,它可以把使用这些环境变量的权限控制到用户级别,这里是针对某一特定的用户,如果你需要给某个用户权限使用这些环境变量,你只需要修改其个人用户主目录下的
在下面添加:
Export PATH="$PATH:/NEW_PATH"
vi /home/guok/.bash.profile
注:修改文件后要想马上生效还要运行$ source /home/guok/.bash_profile不然只能在下次重进此用户时生效。
1、直接用export命令:
#export PATH=$PATH:/自己需要添加的命令,然后echo $PATH查看。命令 “PATH=$PATH:路径”可以把这个路径加入环境变量,但是退出这个命令行就失效了。要想永久生效,需要把这行添加到环境变量文件里。有两个文件可选:“/etc/profile”和用户主目录下的“.bash_profile”,“/etc/profile”对系统里所有用户都有效,用户主目录下的“.bash_profile”只对这个用户有效。
注意:
1、/etc/profile里面修改PATH是不合理的,因为这个变量可能会被其他的脚本给修 改了。所以最好你在~/.bashrc里面写。
2、export PATH=$PATH:/tools/bin
注销以后重新登陆就可以。
如果你在某一个终端里面写了上面这句话
那只是在这个终端里面修改了PATH,而关闭了这个终端,PATH还是原来的PATH.
例子:
noxim里的makefile.defs让手工修改环境变量的部分如下:
SYSTEMC = /opt/systemc-2.3.0
INCDIR = -I. -I.. -I../src -I$(SYSTEMC)/include
LIBDIR = -L. -L..-L../src -L$(SYSTEMC)/lib-$(TARGET_ARCH)其中TARGET_ARCH之前已经定义过了, -I. -I.. -I../ -L. -L.. -L..怎么设置?假设systemc路径为/home/owner/systemc/
INCDIR配置的是gcc编译时的头文件搜索路径,-I后跟路径名,一个点指当前目录,两个点指当前目录的上层目录。你的INCDIR写法的意思就是编译时在当前目录,当前目录的上一层目录,上层目录下的src目录,/home/owner/systemc/include,这几个路径下搜索头文件。
LIBDIR配置的事gcc链接时查找动态库的搜索路径,-L跟路径名,也就是在当前目录,上层目录,上层目录下的src目录,/home/owner/systemc/lib-$(TARGET_ARCH),这几个路径搜索动态库。
另外一部分理解:
-l: 是加入某个函数库(Library)的意思
所以-lm表示使用libm.so(或者 libm.a)这个函数库的意思。至于那个-L后面接的路径呢?这表示我要的函数库libm.so请到/lib或/usr/lib里面搜索。
由于linux默认是将函数库放置在/lib与/usr/lib当中,所以你没有写-L/lib与-L/usr/lib也没有关系的,不过,万一哪天你使用的函数库并非放置在这两个目录下,那么-L/path就很重要了,否则会找不到函数库。
除了连接的函数库之外,你或许已经发现了一个奇怪的地方,那就是在程序中外面如果加入了一个其它地方路径的头文件,那么程序在编译的时候肯定需要找到这个头文件,然后把内容读入这个主程序中,一般情况标准的头文件都放到/usr/include/中,其它路径中的头文件,你需要把它包含进来。例如sin.c这个文件中写了#include<stdio.h>那么你在makefile中可以如下写:
Gcc sin.c –lm –I/usr/include
/etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行.
并从/etc/profile.d目录的配置文件中搜集shell的设置.
/etc/bashrc:为每一个运行bash shell的用户执行此文件.当bash shell被打开时,该文件被读取.
~/.bash_profile:每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件.
~/.bashrc:该文件包含专用于你的bash shell的bash信息,当登录时以及每次打开新的shell时,该
该文件被读取.
~/.bash_logout:当每次退出系统(退出bashshell)时,执行该文件.
这几个文件的区别:
对所有用户有效在/etc/profile增加以下内容。只对当前用户有效在Home目录下的
.bashrc或.bash_profile里增加下面的内容:
(注意:等号前面不要加空格,否则可能出现 command not found)
#在PATH中找到可执行文件程序的路径。
export PATH =$PATH:$HOME/bin
#gcc找到头文件的路径
C_INCLUDE_PATH=/usr/include/libxml2:/MyLib
export C_INCLUDE_PATH
#g++找到头文件的路径
CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/include/libxml2:/MyLib
export CPLUS_INCLUDE_PATH
#找到动态链接库的路径
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/MyLib
export LD_LIBRARY_PATH
#找到静态库的路径
LIBRARY_PATH=$LIBRARY_PATH:/MyLib
export LIBRARY_PATH
编译程序,在库中寻找的顺序:
1.头文件
gcc 在编译时怎么去寻找所需要的头文件:
※所以header file的搜寻会从-I开始
※然后找gcc的环境变量C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH
※再找内定目录
/usr/include
/usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.2/include
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../include/g++-3
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../i386-linux/include
库文件不过如果装gcc的时候,是有给定的prefix的话,那么就是
/usr/include
prefix/include
prefix/xxx-xxx-xxx-gnulibc/include
prefix/lib/gcc-lib/xxxx-xxx-xxx-gnulibc/2.8.1/include
2.库文件
cos()等函式库的选项要多加-lm
编译的时候:
※gcc会去找-L
※再找gcc的环境变量LIBRARY_PATH
※再找内定目录 /lib/usr/lib /usr/local/lib这是当初compilegcc时写在程式内的
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三. 其它杂散的编译资料
如果感兴趣,请大家可以再读读,这里大都来自网络中,转载其它人的文章。
Linux系统下各种环境变量都通过修改/etc/profile文件来实现。由于是系统文件,修改此文件需要root权限。因此实现以下功能都需要用户拥有root权限。
另:不要轻易修改profile文件中的现有内容。应在保证原有内容不变的前提下,在文件的最后插入新的一行。
1.路径变量PATH
当用户在某工作目录执行一个命令或者可执行程序时,若当前路径下不存在该程序,则系统将从PATH路径列表中查找指定的应用程序。还记得Java命令,ping命令么?这些应用程序所在的文件夹都包含在了PATH路径中。
修改方法:在profile文件末尾插入新的一行,写入 exportPATH={YourPath}:$PATH
解释一下:这里{YourPath}表示你要加入到PATH中的一个目录。例如,你要将/home/jack/apps/目录加入到PATH中,则这样写: exportPATH=/home/jack/apps/:$PATH
等号后边的表达式中,多个路径以英文冒号分隔。最后一定要加入$PATH。因为这表示在原有PATH环境变量的基础上追加了若干个目录。Linux系统中获取环境变量的方法即美元符号$+变量名。当你设置好后保存该文件。等等,这并不会立即生效。这种设置是全局的。无论当前系统中有多少个用户,都会在环境变量PATH中增加该条记录。因此系统必须重新启动之后才能真正应用上。好了,重新启动后,在终端中输入 echo $PATH 看看新加入到路径是否已经显示出来了。这个例子只是个初级扫盲,接下来所描述的内容将不会这么罗嗦。
2.添加库默认加载路径LD_LIBRARY_PATH
export LD_LIBRARY_PATH={YourPath}:$LD_LIBRARY_PATH
加入此环境变量的目的是允许系统从用户自定义的目录中加载库(有点像Windows中的dll)。这样如果你有一个软件,需要依赖很多库,但又不想破坏系统原有的纯净,则可将所有库放入一个文件夹内,然后将该文件夹添加至LD_LIBRARY_PATH环境变量。
3.添加gcc默认的include目录C_INCLUDE_PATH
export C_INCLUDE_PATH={YourPath}:$C_INCLUDE_PATH
如果你使用gcc来编译你的C程序,你又安装了某个第三方的C库。你可以将库中的头文件放到一个目录中,然后将该目录作为gcc默认的查找头文件目录。这样你在编译的时候就可以少加入一个“-I”参数,编译器会自动从原有目录和你设置的目录中查找引用的头文件。
Ubuntu 10.04 中常用的INCLUDE目录为
/usr/include
/usr/include/c++/4.4
/usr/include/c++/4.4/backward
/usr/include/c++/4.4/i686-linux-gnu
/usr/lib/gcc/i686-linux-gnu/4.4.5/include
/usr/lib/gcc/i686-linux-gnu/4.4/include-fixed
/usr/local/include
4.添加g++默认的include目录CPLUS_INCLUDE_PATH
export CPLUS_INCLUDE_PATH={YourPath}:$CPLUS_INCLUDE_PATH
意义同上面的那一条相同,只不过是针对g++编译器的。
后面再接触到新的内容时,我会及时在文章里添加。也欢迎朋友们提供。谢谢了。
动态链接库*.so的编译与使用- -
动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助。
1、动态库的编译
下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so。
so_test.h:
#include <stdio.h>
#include <stdlib.h>
void test_a();
void test_b();
void test_c();
test_a.c:
#include "so_test.h"
void test_a()
{
printf("this is in test_a.../n");
}
test_b.c:
#include "so_test.h"
void test_b()
{
printf("this is in test_b.../n");
}
test_c.c:
#include "so_test.h"
void test_c()
{
printf("this is in test_c.../n");
}
将这几个文件编译成一个动态库:libtest.so
$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so
2、动态库的链接
在1、中,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c。
test.c:
#include "so_test.h"
int main()
{
test_a();
test_b();
test_c();
return 0;
}
l 将test.c与动态库libtest.so链接生成执行文件test:
$ gcc test.c -L. -ltest -o test
l 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了
$ ldd test
l 执行test,可以看到它是如何调用动态库中的函数的。
3、编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
l -L.:表示要连接的库在当前目录中
l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用/sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
4、注意
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录通过“-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
1、生成静态库 生成静态库使用ar工具,其实ar是archive的意思
$ar cqs libhello.a hello.o
2、生成动态库 用gcc来完成,由于可能存在多个版本,因此通常指定版本号:
$gcc -shared -o libhello.so.1.0 hello.o
3、库文件是如何命名的,有没有什么规范:
在 linux 下,库文件一般放在/usr/lib和/lib下,
静态库的名字一般为libxxxx.a,其中 xxxx 是该lib的名称;
动态库的名字一般为libxxxx.so.major.minor,xxxx 是该lib的名称,major是主版本号,minor是副版本号
4、可执行程序在执行的时候如何定位共享库(动态库)文件 :
当系统加载可执行代码(即库文件)的时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器(dynamic linker/loader)
对于 elf 格式的可执行程序,是由 ld-linux.so* 来完成的,它先后搜索 elf 文件的 DT_RPATH 段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache 文件列表— /lib/,/usr/lib 目录找到库文件后将其载入内存
如: export LD_LIBRARY_PATH=’pwd’
将当前文件目录添加为共享目录
5、使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库:
ldd 命令可以查看一个可执行程序依赖的共享库,
例如 # ldd /bin/lnlibc.so.6
=> /lib/libc.so.6(0×40021000)/lib/ld-linux.so.2
=> /lib/ld-linux.so.2 (0×40000000)
可以看到 ln 命令依赖于 libc 库和ld-linux 库
6、使用nm工具,查看静态库和动态库中有那些函数名(T类表示函数是当前库中定义的,U类表示函数是被调用的,在其它库中定义的,W类是当前库中定义,被其它库中的函数覆盖)。:
有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的涉及到的所有符号,这里的库既可以是静态的也可以是动态的。
nm列出的符号有很多, 常见的有三种::
· 一种是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
· 一种是在库中定义的函数,用T表示,这是最常见的;
· 另外一种是所 谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
例如,假设开发者希望知道上文提到的hello库中是否引用了 printf():
$nm libhello.so | grep printf
发现printf是U类符号,说明printf被引用,但是并没有在库中定义。
由此可以推断,要正常使用hello库,必须有其它库支持,使用ldd工具查看hello依赖于哪些库:
$ldd hello libc.so.6=>/lib/libc.so.6(0x400la000)/lib/ld-linux.so.2=>/lib/ld-linux.so.2 (0x40000000)
从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on
7、使用ar工具,可以生成静态库,同时可以查看静态库中包含那些.o文件,即有那些源文件构成。
可以使用 ar -t libname.a 来查看一个静态库由那些.o文件构成。
可以使用 ar q libname.a xxx1.o xxx2.o xxx3.o ... xxxn.o 生成静态库
Linux下进行程序设计时,关于库的使用:
一、gcc/g++命令中关于库的参数:
-shared: 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
-fPIC:表示编译为位置独立(地址无关)的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时,是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L:指定链接库的路径,-L.表示要连接的库在当前目录中
-ltest:指定链接库的名称为test,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,
不过如果没有root权限,那么只能采用修改LD_LIBRARY_PATH环境变量的方法了。
调用动态库的时候,有几个问题会经常碰到:
1、有时,明明已经将库的头文件所在目录通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
二、静态库链接时搜索路径的顺序:
1.ld会去找gcc/g++命令中的参数-L;
2. 再找gcc的环境变量LIBRARY_PATH,它指定程序静态链接库文件搜索路径;
exportLIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib
3. 再找默认库目录 /lib /usr/lib /usr/local/lib,这是当初compilegcc时写在程序内的。
三、动态链接时、执行时搜索路径顺序:
1. 编译目标代码时指定的动态库搜索路径;
2. 环境变量LD_LIBRARY_PATH指定动态库搜索路径,它指定程序动态链接库文件搜索路径;
exportLD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4. 默认的动态库搜索路径/lib;
5. 默认的动态库搜索路径/usr/lib。
四、静态库和动态链接库同时存在的问题:
当一个库同时存在静态库和动态库时,比如libmysqlclient.a和libmysqlclient.so同时存在时:
在Linux下,动态库和静态库同事存在时,gcc/g++的链接程序,默认链接的动态库。
注意:
查看一个命令的参数: 例如, ls –help
描述一个命令的参数:例如, man ls
4. .Makefile的编写
假设我们有下面这样的一个程序,源代码如下:
/* main.c */
#include "mytool1.h"
#include "mytool2.h"
int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}
/* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif
/* mytool1.c */
#include "mytool1.h"
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s\n",print_str);
}
/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif
/* mytool2.c */
#include "mytool2.h"
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s\n",print_str);
}
当然由于这个程序是很短的我们可以这样来编译
gcc -c main.c
gcc -c mytool1.c
gcc -c mytool2.c
gcc -o main main.o mytool1.o mytool2.o
5. 改写为简便的:
Makefile的文件是:
# 这是上面那个程序的Makefile文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
规则是:
target:components
TAB rule
第一行表示的是依赖关系。第二行是规则。
比如说我们上面的那个Makefile文件的第二行
main:main.o mytool1.o mytool2.o
表示我们的目标(target)main的依赖对象(components)是main.o mytool1.o mytool2.o 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令。就象我们的上面那个Makefile第三行所说的一样要执行 gcc -o main main.o mytool1.o mytool2.o 注意规则一行中的TAB表示那里是一个TAB键
Makefile有三个非常有用的变量。分别是$@,$^,$<代表的意义分别是:
$@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件。
如果我们使用上面三个变量,那么我们可以简化我们的Makefile文件为:
# 这是简化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
经过简化后我们的Makefile是简单了一点,不过人们有时候还想简单一点。这里我们学习一个Makefile的缺省规则
.c.o:
gcc -c $<
这个规则表示所有的 .o文件都是依赖与相应的.c文件的。例如mytool.o依赖于mytool.c这样Makefile还可以变为:
# 这是再一次简化后的Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
.c.o:
gcc -c $<
好了,我们的Makefile 也差不多了,如果想知道更多的关于Makefile规则可以查看相应的文档。
3.程序库的链接
试着编译下面这个程序
/* temp.c */
#include
int main(int argc,char **argv)
{
double value;
printf("Value:%f\n",value);
}
这个程序相当简单,但是当我们用 gcc -o temp temp.c 编译时会出现下面所示的错误。
/tmp/cc33Kydu.o: In function `main':
/tmp/cc33Kydu.o(.text+0xe): undefined reference to`log'
collect2: ld returned 1 exit status
出现这个错误是因为编译器找不到log的具体实现。虽然我们包括了正确的头文件,但是我们在编译的时候还是要连接确定的库。在Linux下,为了使用数学 函数,我们必须和数学库连接,为此我们要加入 -lm 选项。 gcc -o temp temp.c -lm这样才能够正确的编译。也许有人要问,前面我们用printf函数的时候怎么没有连接库呢?是这样的,对于一些常用的函数的实现,gcc编译器会自 动去连接一些常用库,这样我们就没有必要自己去指定了。有时候我们在编译程序的时候还要指定库的路径,这个时候我们要用到编译器的 -L选项指定路径。比如说我们有一个库在 /home/hoyt/mylib下,这样我们编译的时候还要加上 -L/home/hoyt/mylib。对于一些标准库来说,我们没有必要指出路径。只要它们在起缺省库的路径下就可以了。系统的缺省库的路径/lib /usr/lib /usr/local/lib 在这三个路径下面的库,我们可以不指定路径。
还有一个问题,有时候我们使用了某个函数,但是我们不知道库的名字,这个时候怎么办呢?很抱歉,对于这个问题我也不知道答案,我只有一个傻办法。首先,我 到标准库路径下面去找看看有没有和我用的函数相关的库,我就这样找到了线程(thread)函数的库文件(libpthread.a)。 当然,如果找不到,只有一个笨方法。比如我要找sin这个函数所在的库。 就只好用 nm -o/lib/*.so|grep sin>~/sin 命令,然后看~/sin文件,到那里面去找了。在sin文件当中,我会找到这样的一行libm-2.1.2.so:00009fa0 W sin 这样我就知道了sin在libm-2.1.2.so库里面,我用 -lm选项就可以了(去掉前面的lib和后面的版本标志,就剩下m了所以是 -lm)。 如果你知道怎么找,请赶快告诉我,我回非常感激的。谢谢!
4.程序的调试
我们编写的程序不太可能一次性就会成功的,在我们的程序当中,会出现许许多多我们想不到的错误,这个时候我们就要对我们的程序进行调试了。
最常用的调试软件是gdb.如果你想在图形界面下调试程序,那么你现在可以选择xxgdb.记得要在编译的时候加入 -g选项.关于gdb的使用可以看gdb的帮助文件。由于我没有用过这个软件,所以我也不能够说出如何使用。不过我不喜欢用gdb.跟踪一个程序是很烦的 事情,我一般用在程序当中输出中间变量的值来调试程序的。当然你可以选择自己的办法,没有必要去学别人的。现在有了许多IDE环境,里面已经自己带了调试 器了。你可以选择几个试一试找出自己喜欢的一个用。
5.头文件和系统求助
有时候我们只知道一个函数的大概形式,不记得确切的表达式,或者是不记得着函数在那个头文件进行了说明。这个时候我们可以求助系统。
比如说我们想知道fread这个函数的确切形式,我们只要执行 man fread 系统就会输出着函数的详细解释的。和这个函数所在的头文件说明了。 如果我们要write这个函数的说明,当我们执行man write时,输出的结果却不是我们所需要的。 因为我们要的是write这个函数的说明,可是出来的却是write这个命令的说明。为了得到write的函数说明我们要用 man 2 write. 2表示我们用的write这个函数是系统调用函数,还有一个我们常用的是3表示函数是C的库函数。
链接问题:
我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件。
如果我们用gcc去一个一个编译每一个源文件的话,效率会低很多,但是如果我们可以写一个Makefile,那么只需要执行一个make就OK了,这样大大提高了开发效率。
但是Makefile的语法规则众多,而且缺乏参考资料,对于初学者来说,写起来还是有一定的难度,往往令很多人望而生畏。
下面我们介绍一个比较通用而且简洁的Makefile,大家只要对它稍作修改就可以用在你们自己的工程里了。
现在假设我们有一个工程叫my_project,工程源码目录下面有app1.c,app2.c,app3.c以及main.c这五个源文件。我们现在需要编译出app1.o,app2.o,app3.o以及main.o,然后再把这些.o文件链接成为一个ELF格式的可执行程序叫做my_app。我们先看一个最简单的Makefile如何编写:
my_app : main.o, app1.o, app2.o, app3.o,app4.o
gcc –o my_app main.o app1.o, app2.o, app3.o, app4.o
main.o : main.c
gcc –c main.c
app1.o : app1.c
gcc –c app1.c
app2.o : app2.c
gcc –c app2.c
app3.o : app3.c
gcc –c app3.c
clean :
rm main.o app1.o, app2.o, app3.o, app4.o
这是一个傻瓜式的Makefile,不灵活,而且不具备可复制性,想象一个如果我们的工程下面有50个源文件,那岂不是要一个一个写出来。
我们的目标是写一个Makefile,只要稍作修改就可以在各个工程之间通用。
下面这个Makefile就可以满足这个要求:
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c = .o)
CC = gcc
INCLUDES = -I/×××
LIBS = -L/×××
CCFLAGS = -g -Wall -O0
my_app : $(OBJS)
$(CC) $^ -o $@ $(INCLUDES) $(LIBS)
%.o : %.c
$(CC) -c $< $(CCFLAGS)
clean:
rm *.o
.PHONY:clean
大家看这个Makefile和前一个比起来是不是简洁很多,当然理解起来不如上一个那么直观。
实际上编写 Makefile就是为了提高我们的工作效率,而不是增加我们的工作量。
因此Makefile为我们提供了很多强大的功能,比如定义变量,使用通配符等等。只要合理利用,就可以达到事半功倍的效果。
下面我们一条一条分析这个Makefile:
SRCS = $(wildcard *.c)
这条语句定义了一个变量SRCS,它的值就是当前面目录下面所有的以.c结尾的源文件。
OBJS = $(SRCS:.c = .o)
这里变量OBJS的值就是将SRCS里面所有.c文件编译出的.o目标文件
CC = gcc
变量CC代表我们要使用的编译器
INCLUDES = -I/×××
LIBS = -L/×××
这里指定除了编译器默认的头文件和库文件的路径之外需要额外引用的头文件路径以及库的路径(×××表示路径)。
CCFLAGS = -g -Wall -O0
CCFLAGS变量存放的是编译选项
my_app : $(OBJS)
$(CC) $^ -o $@ $(INCLUDES) $(LIBS)
my_app依赖于所有的.o文件,$^代表$(OBJS),$@代表my_app
%.o : %.c
$(CC) -c $< $(CCFLAGS)
将所有的.c源代码编译成.o目标文件,这样写是不是很省事?
clean:
rm *.o
在执行make clean之后删除所有编译过程中生成的.o文件。
.PHONY:clean
每次执行make clean时都要执行rm *.o命令
这个Makefile就具备灵活的通用性,我们只要对它稍作修改就可以用在自己的工程里面。当然Makefile还有很多强大的功能,需要我们进一步学习。