|
Andy Niu Help
1.0.0.0
|
变量 | |
| 链接静态库只链接调用的方法 | |
| 编译的头文件和库文件路径 | |
| 编译告警 | |
| 编译报错的解决思路 | |
| PHONY的作用 | |
| 编译示例 | |
| Linux静态链接库 | |
| Linux动态链接库 | |
| Linux动态加载库 | |
| makefile | |
| 编译间接依赖的库 | |
| 静态库与动态库的链接 | |
| 编译链接的有关问题 | |
| 方法地址找错 | |
详细描述
变量说明
| Linux动态加载库 |
1、动态加载库,解决什么问题?
静态链接库和动态链接库是在编程时,直接调用对应的方法。链接时已经完成了,静态链接直接放在后面,动态链接使用-l链接。
而动态加载库在编程时,需要使用dlopen等函数来获取库里面方法的定义,然后进行调用。
对于没有提供头文件的动态库,只能dlopen等函数来调用。
2、测试代码:
test.cpp
#include <string>
std::string GetName()
{
return std::string("Andy");
}
main.cpp
#include <string>
#include <dlfcn.h>
using namespace std;
typedef string (*Func) ();
int main(int argc, char* argv[])
{
void* pHandle = dlopen("./libtest.so", RTLD_NOW);
void* error = dlerror();
printf("Error[%s]\n", error);
Func pFunc = (Func)dlsym(pHandle,"GetName");
error = dlerror();
printf("Error[%s]\n", error);
string name = pFunc();
printf("name[%s]\n",name.c_str());
return 0;
}
3、生成动态库so文件
[root@localhost dll2]# g++ -fPIC -shared -o libtest.so test.cpp
[root@localhost dll2]# ll
total 16
-rwxr-xr-x 1 root root 5158 Jan 31 22:38 libtest.so
-rw-r--r-- 1 root root 401 Jan 31 22:37 main.cpp
-rw-r--r-- 1 root root 73 Jan 31 22:33 test.cpp
4、生成可执行文件
[root@localhost dll2]# g++ -o main main.cpp -ldl
[root@localhost dll2]# ll
total 24
-rwxr-xr-x 1 root root 5158 Jan 31 22:38 libtest.so
-rwxr-xr-x 1 root root 6222 Jan 31 22:38 main
-rw-r--r-- 1 root root 401 Jan 31 22:37 main.cpp
-rw-r--r-- 1 root root 73 Jan 31 22:33 test.cpp
-ldl就是 -l dl,表示动态加载dll
5、运行
[root@localhost dll2]# ./main
Error[(null)]
Error[./libtest.so: undefined symbol: GetName]
Segmentation fault (core dumped)
报错没有找到定义的符号,明明有这个方法,为什么没有找到?
这是因为G++编译的时候,进行了名称重整,生成libtest.so的时候,GetName进行了名称重整。
可能变成了__string_GetName,所以dlsym(pHandle,"GetName"); 找不到符号。
6、怎么解决?
需要抑制名称重整,修改如下:
#include <string>
extern "C"
{
std::string GetName()
{
return std::string("Andy");
}
};
重新执行一遍,运行如下:
[root@localhost dll2]# g++ -fPIC -shared -o libtest.so test.cpp
[root@localhost dll2]# g++ -o main main.cpp -ldl
[root@localhost dll2]# ./main
Error[(null)]
Error[(null)]
name[Andy]
7、上面的做法存在问题,因为C编译器并不认识extern "C",因此需要预编译,如下:
#include <string>
#ifdef __cplusplus
extern "C"
{
#endif
std::string GetName()
{
return std::string("Andy");
}
#ifdef __cplusplus
};
#endif
8、静态链接、动态链接和动态加载的比较:
a、对于静态库,实现发生改变,依赖它的exe(或者dll)文件必须重新静态链接,把新的代码实现链接到exe文件中,才会有效。
b、对于动态库的链接,实现发生改变,依赖它的exe(或者dll)文件不需要重新链接。
如果接口发生变化,必须重新构建,否则报错,如下:
./main: symbol lookup error: ./main: undefined symbol: _Z7GetNamev
c、对于动态库的加载,和动态库的链接是同样道理。二者的区别是:动态链接是在程序运行之前,链接确定下来。
但是有些场景下,需要在程序运行过程中,按照需要加载进来。
相比较动态链接,动态加载的优点是:延迟和按照需要进行。
| Linux动态链接库 |
1、测试代码:
test.h
#include <string>
std::string GetName();
test.cpp
#include "test.h"
std::string GetName()
{
return std::string("Andy");
}
main.cpp
#include "test.h"
using namespace std;
int main(int argc, char* argv[])
{
string name = GetName();
printf("name[%s]\n",name.c_str());
return 0;
}
2、生成动态库so文件
[root@localhost dll]# g++ -fPIC -shared -o libtest.so test.cpp
[root@localhost dll]# ll
total 20
-rwxr-xr-x 1 root root 5158 Jan 31 20:58 libtest.so
-rw-r--r-- 1 root root 149 Jan 31 20:46 main.cpp
-rw-r--r-- 1 root root 74 Jan 31 20:43 test.cpp
-rw-r--r-- 1 root root 41 Jan 31 20:55 test.h
3、生成可执行文件
[root@localhost dll]# g++ -o main -ltest -L./ main.cpp
[root@localhost dll]# ll
total 28
-rwxr-xr-x 1 root root 5158 Jan 31 20:58 libtest.so
-rwxr-xr-x 1 root root 5992 Jan 31 20:59 main
-rw-r--r-- 1 root root 149 Jan 31 20:46 main.cpp
-rw-r--r-- 1 root root 74 Jan 31 20:43 test.cpp
-rw-r--r-- 1 root root 41 Jan 31 20:55 test.h
4、运行
[root@localhost dll]# export LD_LIBRARY_PATH=$(pwd)
[root@localhost dll]# ldd main
linux-gate.so.1 => (0x00402000)
libtest.so => /home/niu/dll/libtest.so (0x00e91000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x021da000)
libm.so.6 => /lib/libm.so.6 (0x00817000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x07cee000)
libc.so.6 => /lib/libc.so.6 (0x006c9000)
/lib/ld-linux.so.2 (0x006ab000)
[root@localhost dll]# ./main
name[Andy]
| Linux静态链接库 |
1、测试代码:
test.h
#include <string>
std::string GetName();
test.cpp
#include "test.h"
std::string GetName()
{
return std::string("Andy");
}
main.cpp
#include "test.h"
#include <stdio.h>
using namespace std;
int main(int argc, char* argv[])
{
string name = GetName();
printf("name[%s]\n",name.c_str());
return 0;
}
2、生成静态库
[root@localhost lib]# ll
total 12
-rw-r--r-- 1 root root 149 Jan 31 21:27 main.cpp
-rw-r--r-- 1 root root 73 Jan 31 21:27 test.cpp
-rw-r--r-- 1 root root 41 Jan 31 21:27 test.h
[root@localhost lib]# g++ -c test.cpp
[root@localhost lib]# ll
total 16
-rw-r--r-- 1 root root 149 Jan 31 21:27 main.cpp
-rw-r--r-- 1 root root 73 Jan 31 21:27 test.cpp
-rw-r--r-- 1 root root 41 Jan 31 21:27 test.h
-rw-r--r-- 1 root root 1384 Jan 31 21:30 test.o
[root@localhost lib]# ar -rc libtest.a test.o
[root@localhost lib]# ll
total 20
-rw-r--r-- 1 root root 1532 Jan 31 21:30 libtest.a
-rw-r--r-- 1 root root 149 Jan 31 21:27 main.cpp
-rw-r--r-- 1 root root 73 Jan 31 21:27 test.cpp
-rw-r--r-- 1 root root 41 Jan 31 21:27 test.h
-rw-r--r-- 1 root root 1384 Jan 31 21:30 test.o
3、生成可执行文件
[root@localhost lib]# g++ -o main main.cpp libtest.a
[root@localhost lib]# ll
total 28
-rw-r--r-- 1 root root 1532 Jan 31 21:30 libtest.a
-rwxr-xr-x 1 root root 6317 Jan 31 21:31 main
-rw-r--r-- 1 root root 149 Jan 31 21:27 main.cpp
-rw-r--r-- 1 root root 73 Jan 31 21:27 test.cpp
-rw-r--r-- 1 root root 41 Jan 31 21:27 test.h
-rw-r--r-- 1 root root 1384 Jan 31 21:30 test.o
4、特别注意:链接静态库,上面使用的方式是,后面直接添加静态库文件。
也可以使用 -l的方式链接静态库,如下:
g++ -o main main.cpp -L./ -ltest
也就是说,【g++ -o main main.cpp -L./ -ltest】与【g++ -o main main.cpp libtest.a】是等价的。
但是,有些特殊情况,需要明确标识是静态库还是动态库。
使用-Wl,-Bstatic或者-Wl,-Bdynamic
-Wl.option 此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选项,然后传递给会连接程序.
5、运行
[root@localhost lib]# ldd main
linux-gate.so.1 => (0x00320000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x021da000)
libm.so.6 => /lib/libm.so.6 (0x00817000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x07cee000)
libc.so.6 => /lib/libc.so.6 (0x006c9000)
/lib/ld-linux.so.2 (0x006ab000)
[root@localhost lib]# ./main
name[Andy]
| makefile |
1、有以下三个文件:main.cpp, student.h, student.cpp
2、makefile文件如下:
app:main.o student.o
g++ -o app main.o student.o
main.o:main.cpp student.h
g++ -c main.cpp
student.o:student.cpp student.h
g++ -c student.cpp
clean:
rm -fr app *.o
3、app是目标,依赖main.o student.o,要生成目标,执行的命令是 g++ -o app main.o student.o
main.o是同样道理。
4、make xxx 执行对应的命令,
make app 执行 g++ -o app main.o student.o
make main.o 执行 g++ -c main.cpp
make 默认执行 make app
5、目标main.o 和 student.o可以去掉,makefile在生成app的时候,发现依赖的对象不存在,会自动生成依赖的对象。
6、make会自动在当前目录下面,查找makefile文件,如果存在多个makefile,可以指定使用哪一个,如下:
make -f makefile2
7、打印makefile中的变量
makefile与shell脚本不一样,具体方法如下:
all: desc $(DLIBTARGET)
$(CP) $(DLIBTARGET) $(TARGET_BASEPATH)/lib_linux/ibp_sdk/imds_sdk
desc:
echo aaaaaaaaaaa
@echo $(TARGET_BASEPATH)
echo bbbbbbbbbbb
8、在makefile中调用shell脚本,如下:
all: desc
echo '1111111'
echo 22222222
desc:
hh=kkkkkk;\
echo $$hh
ddddd;\
ret=$$?;if [ $$ret -ne 0 ]; then echo "OK"; fi
echo aaaaaaaaaaa
@echo $(TARGET_BASEPATH)
echo bbbbbbbbbbb
在makefile中使用shell脚本,有两点特别注意:
a、一行必须是一个完整的shell语句,可以使用反斜杠折行
b、为了区分makefile中的变量,引用变量使用 $$var
9、如果你想要看到make构建的详细过程,可以使用make VERBOSE=1或者VERBOSE=1 make命令来进行构建。
| PHONY的作用 |
1、避免语义冲突,告诉make不是创建目标文件,而是执行一些命令,也就是说,伪目标。
考虑,下面的情况,makefile内容如下:
clean:
rm -fr *.o
执行:
niuzibin@ubuntu:~/work/test1/phony$ ll
total 12
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:09 ./
drwxrwxr-x 3 niuzibin niuzibin 4096 Apr 8 23:07 ../
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:08 1.o
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:08 2.o
-rw-rw-r-- 1 niuzibin niuzibin 19 Apr 8 23:07 makefile
niuzibin@ubuntu:~/work/test1/phony$ more makefile
clean:
rm -fr *.o
niuzibin@ubuntu:~/work/test1/phony$ make clean
rm -fr *.o
niuzibin@ubuntu:~/work/test1/phony$ ll
total 12
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:10 ./
drwxrwxr-x 3 niuzibin niuzibin 4096 Apr 8 23:07 ../
-rw-rw-r-- 1 niuzibin niuzibin 19 Apr 8 23:07 makefile
假设当前目录有个clean文件,如下:
niuzibin@ubuntu:~/work/test1/phony$ touch clean
niuzibin@ubuntu:~/work/test1/phony$ ll
total 12
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:12 ./
drwxrwxr-x 3 niuzibin niuzibin 4096 Apr 8 23:07 ../
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:12 1.o
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:12 2.o
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:12 clean
-rw-rw-r-- 1 niuzibin niuzibin 19 Apr 8 23:07 makefile
niuzibin@ubuntu:~/work/test1/phony$ make clean
make: `clean' is up to date.
这个时候的make clean,make读取到makefile中的clean,检查当前目录是否有目标clean,已经有目标clean了,就不再执行。
这不是我们所期望的,怎么解决这个问题?
告诉make,这个clean是个伪目标,强制执行下面的命令。如下:
niuzibin@ubuntu:~/work/test1/phony$ ll
total 12
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:23 ./
drwxrwxr-x 3 niuzibin niuzibin 4096 Apr 8 23:07 ../
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:23 1.o
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:23 2.o
-rw-rw-r-- 1 niuzibin niuzibin 0 Apr 8 23:12 clean
-rw-rw-r-- 1 niuzibin niuzibin 32 Apr 8 23:23 makefile
niuzibin@ubuntu:~/work/test1/phony$ more makefile
.PHONY:clean
clean:
rm -fr *.o
niuzibin@ubuntu:~/work/test1/phony$ make clean
rm -fr *.o
2、解决间接依赖的问题。
考虑下面的情况,当前包含了四个目录test、add、sub、include 和一个顶层目录makefile文件。
test、add、sub三个目录分别包含了三个源程序test.c、add.c、sub.c和三个子目录makefile。
niuzibin@ubuntu:~/work/test1/phony$ ll
total 28
drwxrwxr-x 6 niuzibin niuzibin 4096 Apr 8 23:42 ./
drwxrwxr-x 3 niuzibin niuzibin 4096 Apr 8 23:07 ../
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:42 add/
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:34 include/
-rw-rw-r-- 1 niuzibin niuzibin 249 Apr 8 23:37 makefile
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:42 sub/
drwxrwxr-x 2 niuzibin niuzibin 4096 Apr 8 23:42 test/
niuzibin@ubuntu:~/work/test1/phony$ more ./include/heads.h
#ifndef _HEAD_H_
#define _HEAD_H_
extern int add(int,int);
extern int sub(int,int);
#endif
niuzibin@ubuntu:~/work/test1/phony$ more ./add/add.c
#include "../include/heads.h"
int add(int a,int b)
{
return (a+b);
}
niuzibin@ubuntu:~/work/test1/phony$ more ./add/makefile
add.o :add.c ../include/heads.h
gcc -c -o $@ $<
.PHONY: clean
clean:
rm -f *.o
niuzibin@ubuntu:~/work/test1/phony$ more ./sub/sub.c
#include "../include/heads.h"
int sub(int a,int b)
{
return a-b;
}
niuzibin@ubuntu:~/work/test1/phony$ more ./sub/makefile
sub.o:sub.c ../include/heads.h
gcc -c -o $@ $<
.PHONY: clean
clean:
rm -f *.o
niuzibin@ubuntu:~/work/test1/phony$ more ./makefile
OBJS = ./add/add.o ./sub/sub.o ./test/test.o
program: $(OBJS)
gcc ./test/test.o ./add/add.o ./sub/sub.o -o program
$(OBJS):
make -C $(dir $@)
.PHONY: clean
clean:
make -C ./add clean
make -C ./sub clean
make -C ./test clean
rm -f program
运行,如下:
niuzibin@ubuntu:~/work/test1/phony$ make
make -C add/
make[1]: Entering directory `/home/niuzibin/work/test1/phony/add'
gcc -c -o add.o add.c
make[1]: Leaving directory `/home/niuzibin/work/test1/phony/add'
make -C sub/
make[1]: Entering directory `/home/niuzibin/work/test1/phony/sub'
gcc -c -o sub.o sub.c
make[1]: Leaving directory `/home/niuzibin/work/test1/phony/sub'
make -C test/
make[1]: Entering directory `/home/niuzibin/work/test1/phony/test'
gcc -c -o test.o test.c
make[1]: Leaving directory `/home/niuzibin/work/test1/phony/test'
gcc ./test/test.o ./add/add.o ./sub/sub.o -o program
注意:makefile的规则,是目标,依赖什么,怎么做,怎么做前面是tab键
现在修改 ./add/add.c实现,如下:
niuzibin@ubuntu:~/work/test1/phony$ vi ./add/add.c
#include "../include/heads.h"
int add(int a,int b)
{
return (a+b+100);
}
再次执行make,如下:
niuzibin@ubuntu:~/work/test1/phony$ make
make: `program' is up to date.
当代码实现修改了,我们期望重新构建,但是并没有重新构建。分析为什么?
构建program的时候,检查被依赖的项 add sub test是否更新了,这里只有源代码更新了,依赖的目标文件没有更新,导致program没有重新构建。
解决办法就是,告诉make,./add/add.o ./sub/sub.o ./test/test.o是伪目标,强制进入子目录执行make。如下:
OBJS = ./add/add.o ./sub/sub.o ./test/test.o
program: $(OBJS)
gcc ./test/test.o ./add/add.o ./sub/sub.o -o program
.PHONY: $(OBJS)
$(OBJS):
make -C $(dir $@)
.PHONY: clean
clean:
make -C ./add clean
make -C ./sub clean
make -C ./test clean
rm -f program
| 方法地址找错 |
1、两个动态库,含有相同的C接口方法名,在linux下,找方法地址可能会找错误,windows不会找错
| 编译告警 |
1、测试代码
niuzibin@ubuntu:~/work/flawfinder$ vi main.cpp
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
printf("hello wolrd\n");
int aa;
int bb = aa + 10;
char ch[50];
gets(ch);
scanf("%c",ch);
return 1;
}
2、输出所有的告警 -Wall,这里是-大写W 加上单词all
niuzibin@ubuntu:~/work/flawfinder$ g++ -Wall -o main main.cpp
main.cpp: In function ‘int main(int, char**)’:
main.cpp:9:2: warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(ch);
^
main.cpp:9:9: warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(ch);
^
main.cpp:7:6: warning: unused variable ‘bb’ [-Wunused-variable]
int bb = aa + 10;
^
main.cpp:7:16: warning: ‘aa’ may be used uninitialized in this function [-Wmaybe-uninitialized]
int bb = aa + 10;
^
/tmp/ccUdCIgt.o: In function `main':
main.cpp:(.text+0x39): warning: the `gets' function is dangerous and should not be used.
3、禁止所有的告警 -w,在linux中,大小写往往表示相反的意思,-W是开启告警,-w是关闭告警,如下:
niuzibin@ubuntu:~/work/flawfinder$ g++ -w -o main main.cpp
/tmp/ccVjBsPa.o: In function `main':
main.cpp:(.text+0x39): warning: the `gets' function is dangerous and should not be used.
注意:有些告警比较严重,即使带上-w选项,也不能禁止,如上。
4、每一种告警,默认值(是否输出是不一样的),如下:
niuzibin@ubuntu:~/work/flawfinder$ g++ -Wall -o main main.cpp
main.cpp: In function ‘int main(int, char**)’:
main.cpp:9:2: warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(ch);
^
main.cpp:9:9: warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(ch);
^
main.cpp:7:6: warning: unused variable ‘bb’ [-Wunused-variable]
int bb = aa + 10;
^
main.cpp:7:16: warning: ‘aa’ may be used uninitialized in this function [-Wmaybe-uninitialized]
int bb = aa + 10;
^
/tmp/ccic7wY5.o: In function `main':
main.cpp:(.text+0x39): warning: the `gets' function is dangerous and should not be used.
niuzibin@ubuntu:~/work/flawfinder$ g++ -o main main.cpp
main.cpp: In function ‘int main(int, char**)’:
main.cpp:9:2: warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(ch);
^
main.cpp:9:9: warning: ‘char* gets(char*)’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
gets(ch);
^
/tmp/ccLU9M12.o: In function `main':
main.cpp:(.text+0x39): warning: the `gets' function is dangerous and should not be used.
可以看到,在默认情况下,-Wdeprecated-declarations是输出的,而-Wunused-variable和-Wmaybe-uninitialized是不输出的。
5、如何输出默认不输出的?
指定-Wxxx强制输出,如下:
g++ -o main main.cpp -Wunused-variable
特别注意:一般情况,要输出全部的告警 -Wall,因为告警往往意味着潜在的风险。
| 编译报错的解决思路 |
1、cmake编译报错,如下:
/home/niuzibin/work/heming/cryptdb/src/util/onions.hh:40:35: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11 [enabled by default]
/home/niuzibin/work/heming/cryptdb/src/util/onions.hh:40:37: error: ‘SECLEVEL’ is not a class or namespace
2、我直接执行一把
g++ -o KeyManager.o -c KeyManager.cpp -I../include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include/ -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include/ -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql/ -I/home/niuzibin/work/heming/cryptdb/src/ -I/usr/include/openssl
同样的错误,如下:
/home/niuzibin/work/heming/cryptdb/src/util/onions.hh:40:35: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11 [enabled by default]
/home/niuzibin/work/heming/cryptdb/src/util/onions.hh:40:37: error: ‘SECLEVEL’ is not a class or namespace
3、加上-std=c++11,再来测试一把,如下:
g++ -std=c++11 -o KeyManager.o -c KeyManager.cpp -I../include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include/ -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include/ -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql/ -I/home/niuzibin/work/heming/cryptdb/src/ -I/usr/include/openssl
报错如下:
/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql/field.h:236:49: error: invalid use of incomplete type ‘struct TABLE’
my_ptrdiff_t l_offset= (my_ptrdiff_t) (table->s->default_values -
/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql/structs.h:29:8: error: forward declaration of ‘struct TABLE’
struct TABLE;
原因是:在前置声明的情况下,调用了方法。
4、那就要思考一个问题了,为什么直接在 heming/cryptdb中可以构建通过,把执行的构建命令拿出来,如下:
make VERBOSE=1
cd /home/niuzibin/work/heming/cryptdb/build/src/keymanager && /usr/bin/c++ -DDBUG_OFF -DEMBEDDED_LIBRARY -DHAVE_CONFIG_H -DKeyManager_EXPORTS -DMYSQL_BUILD_DIR=\"/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build\" -DMYSQL_SERVER -std=c++0x -fPIC -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/regex -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/sql -I/home/niuzibin/work/heming/cryptdb/src -I/usr/include/openssl -g -O0 -fno-strict-aliasing -fno-rtti -fwrapv -fPIC -Wall -Werror -Wpointer-arith -Wendif-labels -Wformat=2 -Wextra -Wmissing-noreturn -Wwrite-strings -Wno-unused-parameter -Wno-deprecated -Wmissing-declarations -Woverloaded-virtual -Wunreachable-code -D_GNU_SOURCE -o CMakeFiles/KeyManager.dir/KeyManager.cpp.o -c /home/niuzibin/work/heming/cryptdb/src/keymanager/KeyManager.cpp
5、修改一下,拿到本地执行一把,
/usr/bin/c++ -DDBUG_OFF -DEMBEDDED_LIBRARY -DHAVE_CONFIG_H -DKeyManager_EXPORTS -DMYSQL_BUILD_DIR=\"/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build\" -DMYSQL_SERVER -std=c++0x -fPIC -I../include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/regex -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/sql -I/home/niuzibin/work/heming/cryptdb/src -I/usr/include/openssl -g -O0 -fno-strict-aliasing -fno-rtti -fwrapv -fPIC -Wall -Werror -Wpointer-arith -Wendif-labels -Wformat=2 -Wextra -Wmissing-noreturn -Wwrite-strings -Wno-unused-parameter -Wno-deprecated -Wmissing-declarations -Woverloaded-virtual -Wunreachable-code -D_GNU_SOURCE -o KeyManager.cpp.o -c KeyManager.cpp
可以执行成功
6、那么对照一下,和上面的构建命令有啥区别?
一点一点向正确的靠近。
错误的
g++ -std=c++11 -o KeyManager.o -c KeyManager.cpp -I../include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include/ -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include/ -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql/ -I/home/niuzibin/work/heming/cryptdb/src/ -I/usr/include/openssl
正确的
/usr/bin/c++ -DDBUG_OFF -DEMBEDDED_LIBRARY -DHAVE_CONFIG_H -DKeyManager_EXPORTS -DMYSQL_BUILD_DIR=\"/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build\" -DMYSQL_SERVER -std=c++0x -fPIC -I../include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/regex -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/sql -I/home/niuzibin/work/heming/cryptdb/src -I/usr/include/openssl -g -O0 -fno-strict-aliasing -fno-rtti -fwrapv -fPIC -Wall -Werror -Wpointer-arith -Wendif-labels -Wformat=2 -Wextra -Wmissing-noreturn -Wwrite-strings -Wno-unused-parameter -Wno-deprecated -Wmissing-declarations -Woverloaded-virtual -Wunreachable-code -D_GNU_SOURCE -o KeyManager.cpp.o -c KeyManager.cpp
7、最终找到错误的地方,需要 添加头文件的搜索路径,添加宏-DMYSQL_SERVER 以及 变异选项 -Wno-deprecated
g++ -std=c++11 -o KeyManager.o -c KeyManager.cpp -DMYSQL_SERVER -Wno-deprecated -I../include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/include -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/sql -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/regex -I/home/niuzibin/work/heming/cryptdb/external_libs/mysql-src/build/sql -I/home/niuzibin/work/heming/cryptdb/src -I/usr/include/openssl
8、事后总结:
这里编译报错
error: invalid use of incomplete type ‘struct TABLE’
error: forward declaration of ‘struct TABLE’
直接原因是 在前置声明的情况下,调用了方法。
间接原因是 缺少预定义宏导致的问题,需要添加预定义宏 -DMYSQL_SERVER
| 编译的头文件和库文件路径 |
1、编译,引用的头文件路径使用 -I
有一部分头文件路径,g++会默认引用,如何查看?
`g++ -print-prog-name=cc1plus` -v
2、链接时使用-L指定库的路径,使用-l指定库,运行时使用LD_LIBRARY_PATH指定库的路径。
目前没有找到方法,显示g++默认引用的库文件路径。步骤是:
g++会去找-L
再找gcc的环境变量LIBRARY_PATH
再找内定的目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
3、由于g++搜索默认的头文件和库文件路径,所以有时候不指定,构建也是OK的。
| 编译示例 |
1、C++代码:
#include <iostream>
int main(int argc,char* argv[])
{
printf("Hello,world\n");
}
2、编译 g++ -c hello.cpp
生成hello.o
3、链接 g++ -o hello hello.o
生成hello,可执行程序
4、运行hello, 打印Hello world
5、编译链接,一起执行g++ -o hello hello.cpp
6、g++ hello.cpp 默认生成 a.out
7、编译链接的时候,需要知道头文件的路径,库的名称和路径,分别使用-I -l -L指定,如下:
g++ -o main main.cpp -I/usr/include -lpthread -L/usr/lib
8、宏定义使用-D选项,如下:
g++ -o main main.cpp -DDEBUG
9、特别注意:Windows下面包含头文件,可以使用斜杠或者反斜杠,不区分大小写。
但是Linux下面,不能使用反斜杠,而且区分大小写。如下,#include "../include/aaa.h"
因此,会出现Windows下面编译成功,而在Linux下面编译失败,需要注意。
| 编译链接的有关问题 |
1、普通方法,只声明,不定义,没有问题,只要不调用方法,就不会链接出错。
2、虚方法,只声明,不定义,即使不调用方法,链接也报错。为什么?
为了实现多态性,必须在类的虚方法表里设置这个虚方法的地址(也就是虚方法的实现),没有定义当然报错。
注意:子类重写会在他的虚方法表里重新设置这个地址。
3、纯虚方法,只声明,不定义,没有问题。为什么?
对于纯虚方法,可以认为在类的虚方法表里,对于的纯虚方法slot设置0,也就是地址为0
这个slot值为0,导致两个问题:
a、当前类不能实例化,考虑如果可以实例化,调用对应的虚方法,就是引用地址为0的方法,当然不行。
也就是说,纯虚方法会导致当前类为抽象类(即使提供方法实现),不能实例化。
编译报错:不能实例化抽象类,并且会指出纯虚方法。
b、子类必须重写虚方法,子类的虚方法表对父类进行整体拷贝。考虑如果没有重写,也就是子类对应的slot值也是0,这当然不行。
注意:纯虚方法也可以提供实现,相当于对于的纯虚方法slot值由0再改为实际的方法地址。
| 编译间接依赖的库 |
1、main依赖test.so,test.so依赖link.so,示例代码如下:
[root@localhost link]# more link.h
#include <string>
std::string GetAAA();
[root@localhost link]# more link.cpp
#include "link.h"
std::string GetAAA()
{
return std::string("Andy");
}
[root@localhost link]# more test.h
#include <string>
std::string GetName();
[root@localhost link]# more test.cpp
#include "test.h"
#include "link.h"
std::string GetName()
{
return GetAAA();
}
[root@localhost link]# more main.cpp
#include "test.h"
using namespace std;
int main(int argc, char* argv[])
{
string name = GetName();
printf("name[%s]\n",name.c_str());
return 0;
}
2、对于这种情况,构建有两种方式。
3、第一种方式:先生成link.so,然后生成test.so的时候,链接link.so,把符号表使用占位符关联起来。
然后生成main的时候,只需要链接test.so,如下:
[root@localhost link]# g++ -fPIC -shared -o liblink.so link.cpp
[root@localhost link]# g++ -fPIC -shared -o libtest.so test.cpp -L./ -llink
[root@localhost link]# g++ -o main main.cpp -L./ -ltest
[root@localhost link]# ll
total 44
-rwxr-xr-x 1 root root 5157 Aug 18 18:07 liblink.so
-rwxr-xr-x 1 root root 4557 Aug 18 18:08 libtest.so
-rw-r--r-- 1 root root 74 Aug 18 17:42 link.cpp
-rw-r--r-- 1 root root 40 Aug 18 17:42 link.h
-rwxr-xr-x 1 root root 5992 Aug 18 18:08 main
-rw-r--r-- 1 root root 153 Aug 18 17:33 main.cpp
-rw-r--r-- 1 root root 81 Aug 18 17:42 test.cpp
-rw-r--r-- 1 root root 41 Aug 18 17:33 test.h
[root@localhost link]# ./main
name[Andy]
4、第二种方式:先生成link.so,然后生成test.so的时候,不链接link.so。
然后生成main的时候,同时链接test.so和link.so,如下:
[root@localhost link]# g++ -fPIC -shared -o liblink.so link.cpp
[root@localhost link]# g++ -fPIC -shared -o libtest.so test.cpp
[root@localhost link]# g++ -o main main.cpp -L./ -ltest
.//libtest.so: undefined reference to `GetAAA()'
collect2: ld returned 1 exit status
[root@localhost link]# g++ -o main main.cpp -L./ -ltest -llink
[root@localhost link]# ll
total 44
-rwxr-xr-x 1 root root 5157 Aug 18 18:10 liblink.so
-rwxr-xr-x 1 root root 4533 Aug 18 18:10 libtest.so
-rw-r--r-- 1 root root 74 Aug 18 17:42 link.cpp
-rw-r--r-- 1 root root 40 Aug 18 17:42 link.h
-rwxr-xr-x 1 root root 6016 Aug 18 18:11 main
-rw-r--r-- 1 root root 153 Aug 18 17:33 main.cpp
-rw-r--r-- 1 root root 81 Aug 18 17:42 test.cpp
-rw-r--r-- 1 root root 41 Aug 18 17:33 test.h
[root@localhost link]# ./main
name[Andy]
5、也就是,main-->test.so-->link.so,可以在每一步都链接,也可以在最后链接。
每一步都链接,会导致test.so会稍微大一点,因为里面保存了链接信息。使用ldd可以看到二者的区别,如下:
[root@localhost link]# g++ -fPIC -shared -o libtest.so test.cpp -L./ -llink
[root@localhost link]# ldd libtest.so
linux-gate.so.1 => (0x00ab3000)
liblink.so => ./liblink.so (0x00973000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00110000)
libm.so.6 => /lib/libm.so.6 (0x005a2000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00812000)
libc.so.6 => /lib/libc.so.6 (0x00ca3000)
/lib/ld-linux.so.2 (0x006ab000)
[root@localhost link]# g++ -fPIC -shared -o libtest.so test.cpp
[root@localhost link]# ldd libtest.so
linux-gate.so.1 => (0x00d7a000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x009b9000)
libm.so.6 => /lib/libm.so.6 (0x00c08000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00256000)
libc.so.6 => /lib/libc.so.6 (0x00110000)
/lib/ld-linux.so.2 (0x006ab000)
每一步都链接,test中多了一项链接 liblink.so => ./liblink.so (0x00973000)
6、每一步都链接,只是相当于最后编译的时候少一个链接-llink,但是,编译的时候文件liblink.so还是必须需要的。
7、不管是每步都链接,还是最后一起链接,能否定位到方法的实现,都是在最后一步才确定下来的。
也就是说,对于每步都链接,链接link,生成test.so的时候,即使找不到link的方法实现,生成test.so也不会报错,
最后一步生成main的时候,才报错找不到方法实现。
8、特别注意一点:对于动态库,其中的代码实现不会合并到任何其他动态库或者可执行文件中。
动态库中的代码段只会在内存中加载一次,所有依赖该动态库的主体,必须解决一个问题,就是定位到代码段的实现。
比如,定位到方法的入口。解决这个问题,有两种办法:
a、动态链接,编译的时候使用占位符,然后链接的时候修改占位符的取值,定位到方法实现的地址。
b、动态加载,运行的时候,使用系统库根据方法名称直接找到方法的实现。
被依赖的动态库,暴露出来的接口不能进行名称重整,否者找不到。
9、再次强调,对于动态库,其中的代码实现不会合并到任何其他动态库或者可执行文件中。
| 链接静态库只链接调用的方法 |
1、动态库或者可执行文件链接静态库,只会链接调用的方法,对于没有调用的方法,不会链接。
比如静态库暴露了10个方法,动态库或者可执行文件只调用了一个方法,只会链接这一个方法。
2、示例代码如下:
root@ubuntu:/home/disk1/CPP_2/Link# more test.h
int add(int a, int b);
int sub(int a, int b);
root@ubuntu:/home/disk1/CPP_2/Link# more test1.cpp
#include "test.h"
int add(int a, int b)
{
return a+b;
}
root@ubuntu:/home/disk1/CPP_2/Link# more test2.cpp
#include "test.h"
int sub(int a, int b)
{
return a-b;
}
root@ubuntu:/home/disk1/CPP_2/Link# more main.cpp
#include "test.h"
#include <stdio.h>
int main()
{
int c = add(1,2);
printf("c[%d]\n", c);
return 0;
}
3、测试如下:
root@ubuntu:/home/disk1/CPP_2/Link# g++ -c test1.cpp
root@ubuntu:/home/disk1/CPP_2/Link# g++ -c test2.cpp
root@ubuntu:/home/disk1/CPP_2/Link# ar -rc libtest.a test1.o test2.o
root@ubuntu:/home/disk1/CPP_2/Link# g++ -o main main.cpp -L./ -ltest
root@ubuntu:/home/disk1/CPP_2/Link# ./main
c[3]
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t main|grep "add"
0000000000400562 g F .text 0000000000000014 _Z3addii
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t main|grep "sub"
root@ubuntu:/home/disk1/CPP_2/Link#
可以看到,main中并没有sub方法的代码实现。
4、如果我想把静态库中的所有方法都链接进来(这种需求比较奇葩,也会存在),怎么办?
使用 --whole-archive编译选项,如下:
root@ubuntu:/home/disk1/CPP_2/Link# g++ -o main main.cpp -L./ -ltest
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t main|grep "sub"
root@ubuntu:/home/disk1/CPP_2/Link# g++ -o main main.cpp -Wl,--whole-archive -L./ -ltest -Wl,--no-whole-archive
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t main|grep "sub"
0000000000400576 g F .text 0000000000000016 _Z3subii
可以看到,加上--whole-archive链接静态库时,会把没有调用的方法也合并过来。
5、特别说明,上面是可执行文件链接静态库,动态库链接静态库,道理也是一样的。
动态库和可执行文件没有本质区别,可执行文件多了一个程序入口的main方法。
测试如下:
root@ubuntu:/home/disk1/CPP_2/Link# more aaa.cpp
#include "test.h"
#include <stdio.h>
int aaa()
{
int c = add(1,2);
printf("c[%d]\n", c);
return 0;
}
root@ubuntu:/home/disk1/CPP_2/Link# g++ -fPIC -shared -o libaaa.so aaa.cpp -L./ -ltest
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t libaaa.so |grep "sub"
root@ubuntu:/home/disk1/CPP_2/Link# ll |grep libaaa.so
-rwxr-xr-x 1 root root 8082 Jun 14 15:47 libaaa.so*
root@ubuntu:/home/disk1/CPP_2/Link# g++ -fPIC -shared -o libaaa.so aaa.cpp -Wl,--whole-archive -L./ -ltest -Wl,--no-whole-archive
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t libaaa.so |grep "sub"
0000000000000780 g F .text 0000000000000016 _Z3subii
root@ubuntu:/home/disk1/CPP_2/Link# ll |grep libaaa.so
-rwxr-xr-x 1 root root 8149 Jun 14 15:47 libaaa.so*
可以看到,加上--whole-archive,生成的动态库,包含所有的代码实现,并且文件大小也更大。
6、特别需要注意的是:
链接静态库的最小单元是 o文件,上面的两个方法,分别在两个cpp文件实现,生成不同的o文件
如果生成一个o文件呢?
示例代码
root@ubuntu:/home/disk1/CPP_2/Link# more test1.cpp
#include "test.h"
int add(int a, int b)
{
return a+b;
}
root@ubuntu:/home/disk1/CPP_2/Link# more test2.cpp
#include "test.h"
int sub(int a, int b)
{
return a-b;
}
root@ubuntu:/home/disk1/CPP_2/Link# more test.cpp
#include "test.h"
int add(int a, int b)
{
return a+b;
}
int sub(int a, int b)
{
return a-b;
}
测试如下:
root@ubuntu:/home/disk1/CPP_2/Link# rm -fr *.o *.a
root@ubuntu:/home/disk1/CPP_2/Link# g++ -c test1.cpp
root@ubuntu:/home/disk1/CPP_2/Link# g++ -c test2.cpp
root@ubuntu:/home/disk1/CPP_2/Link# ar -rc libtest.a test1.o test2.o
root@ubuntu:/home/disk1/CPP_2/Link# g++ -o main main.cpp -L./ -ltest
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t main |grep "sub"
root@ubuntu:/home/disk1/CPP_2/Link#
root@ubuntu:/home/disk1/CPP_2/Link# rm -fr *.o *.a
root@ubuntu:/home/disk1/CPP_2/Link# g++ -c test.cpp
root@ubuntu:/home/disk1/CPP_2/Link# ar -rc libtest.a test.o
root@ubuntu:/home/disk1/CPP_2/Link# g++ -o main main.cpp -L./ -ltest
root@ubuntu:/home/disk1/CPP_2/Link# objdump -t main |grep "sub"
0000000000400576 g F .text 0000000000000016 _Z3subii
也就是说,链接的最小单元是 o文件(目标文件)
7、得出的结论是:
链接静态库的方法,默认情况下(不加--whole-archive),是按需链接,也就是说,只链接需要的方法。
但是,链接的最小单元是目标文件,需要目标文件中的一个方法,会把整个目标文件链接进来。
- 参见
| 静态库与动态库的链接 |
测试场景,Test,lib1,lib2,dll1,dll2
分为下面四种情况:
1、Test->lib1->lib2
lib1编译自己的代码,对lib2的部分,只需要lib2的头文件,对lib2的代码实现,使用占位符关联。
生成Test链接的时候,把lib1的代码实现包含进来,再递归,把lib1中关联lib2的代码实现也包含进来。
运行Test的时候,不再需要lib1和lib2。
也就是说,lib2不合并到lib1中,等到exe的时候,一起合并到exe中。
2、Test->lib1->dll2
lib1编译自己的代码,对dll2的部分,只需要dll2的头文件,对dll2的代码实现,使用占位符关联。
生成Test链接的时候,把lib1的代码实现包含进来,但是,dll2中的代码不包含进来。
运行Test的时候,不需要lib1,但是需要dll2【不需要dll的lib文件】
也就是说,dll2不合并到lib1中,等到exe的时候,把lib1合并到exe中。
3、Test->dll1->lib2
dll1编译自己的代码,对lib2的部分,需要lib2的头文件和实现,即lib2,把lib2的代码实现包含到dll1中
生成Test链接的时候,dll1中的代码不包含进来,也不再需要lib2。
【可以这样测试,生成lib2,生成dll1,生成test,删除lib2,删除test.exe,再生成test,
可以成功,说明生成test.exe,链接的时候,根本不需要lib2,
也就是说,静态库会被链接到动态库或者exe中,但是不会链接到其他的lib中】
运行Test的时候,需要dll1,但是不需要lib2
也就是说,lib2合并到dll1中,等到exe的时候,不需要合并dll1,运行时需要dll1。
4、Test->dll1->dll2
dll1编译自己的代码,对dll2的部分,需要dll2的头文件和lib文件,对dll2的代码实现,使用占位符关联。
生成Test链接的时候,Test需要dll1的lib,dll1需要dll2的lib,但是代码实现都不会包含在Test中。
运行Test的时候,需要dll1和dll2【不需要他们的lib】
也就是说,dll2不合并到dll1中,等到exe的时候,二者都不需要合并,运行时需要二者。
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.