前言
承接上一篇 如何在无C++运行环境下,运行大部分的C++代码(一)
使用gcc编译C++的过程
编译C++的虚函数
// test.cpp
#include <stdio.h>
class Parent
{
public:
void Function1()
{
printf("This is parent,function1\n");
}
virtual void Function2()
{
printf("This is parent,function2\n");
}
};
class Child : public Parent
{
public:
void Function1()
{
printf("This is child,function1\n");
}
void Function2()
{
printf("This is child,function2\n");
}
};
int main(int argc, char *argv[])
{
Child child;
child.Function1();
child.Function2();
Parent *p = &child;
p->Function1();
p->Function2();
return 0;
}
使用g++编译并运行
chenls@chenls-pc:Desktop$ g++ test.cpp
chenls@chenls-pc:Desktop$ ./a.out
This is child,function1
This is child,function2
This is parent,function1
This is child,function2
因为Function2带有virtual为虚函数,p是Child类的实例,所以最终会调用Child的方法。
更多关于C++虚函数的知识,请查看 C++ 虚函数表解析
更多关于C++动态绑定的知识,请查看 C++动态绑定和虚函数表vtable
使用gcc编译
chenls@chenls-pc:Desktop$ gcc test.cpp
/usr/bin/ld: /tmp/cczwJUV9.o:(.data.rel.ro._ZTI5Child[_ZTI5Child]+0x0): undefined reference to `vtable for __cxxabiv1::__si_class_type_info'
/usr/bin/ld: /tmp/cczwJUV9.o:(.data.rel.ro._ZTI6Parent[_ZTI6Parent]+0x0): undefined reference to `vtable for __cxxabiv1::__class_type_info'
collect2: error: ld returned 1 exit status
编译报错!
非虚成员函数属于静态绑定:编译器在编译期间,根据指针(或对象)的类型完成了绑定。而虚函数在编译期间无法绑定,因为它也不确定最终会调用父类还是子类的函数,所以需要在运行时动态绑定。
现在的问题是,我们无法直接使用虚函数。所以要么我们避开虚函数的使用(把代码改成非虚函数的实现),要么找到虚函数表vtable相关的实现。
使用gcc同时链接libstdc++
chenls@chenls-pc:Desktop$ gcc test.cpp -lstdc++
chenls@chenls-pc:Desktop$ ./a.out
This is child,function1
This is child,function2
This is parent,function1
This is child,function2
链接libstdc++后,又可以正常编译运行了,说明我们找不到的undefined reference to `vtable for __cxxabiv1等相关的方法在libstdc++.so中实现了。那么我们是否可以自行加入这个库的源码,使其正常运行呢?
C++标准库
C++ 标准库可以分为两部分:
- 标准函数库: 这个库是由通用的、独立的、不属于任何类的函数组成的。函数库继承自 C 语言。
- 面向对象类库: 这个库是类及其相关函数的集合。
C++ 标准库有多种实现,libstdc++ 是 GNU 项目的标准库(也就是GCC默认使用的库),libc++ 为 LLVM 项目重新编写,包含 C++ 11 标准库(也就是clang默认使用的库)。
我们接下来主要来学习libc++的使用。
LLVM简介
LLVM项目是模块化和可重用的编译器及工具链技术的集合。其中还包含子项目Clang、libc++和libc++ABI。其官网可查看 https://llvm.org/
更多关于LLVM的说明,请查看 LLVM编译原理和使用
"libc++" C++ Standard Library
libc++ is an implementation of the C++ standard library, targeting C++11, C++14 and above.
其官网可查看 https://libcxx.llvm.org/
"libc++abi" C++ Standard Library Support
libc++abi is a new implementation of low level support for a standard C++ library.
Features and Goals
- Correctness as defined by the C++11 standard.
- Provide a portable sublayer to ease the porting of libc++
- On Mac OS X, be ABI compatible with the existing low-level support.
其官网可查看 https://libcxxabi.llvm.org/
还记得我们在编译虚函数中缺失的vtable相关的方法吗?是的,它在libc++abi项目中实现了。接下来我们要尝试自己编译libc++abi项目。
libc++abi的编译
参考官网文档 https://libcxxabi.llvm.org/
Get it and get involved!
To check out the code (including llvm and others), use:
git clone https://github.com/llvm/llvm-project.git
To do a standalone build:
- Check out the source tree. This includes the other subprojects, but you'll only use the libcxxabi part.
cd llvm-project
mkdir build-libcxxabi && cd build-libcxxabi
cmake -DLIBCXXABI_LIBCXX_PATH=path/to/libcxx ../libcxxabi # on linux you may need -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
make
By default CMake uses llvm-config
to locate the required LLVM sources. If CMake cannot find llvm-config
then you must configure CMake using either of the following options.
-DLLVM_CONFIG_PATH=path/to/llvm-config
-DLLVM_PATH=path/to/llvm-source-root
整理成build_libcxxabi.sh脚本,放在llvm文件夹目录下
#build_libcxxabi.sh
clear
mkdir -p build-libcxxabi
rm -rf build-libcxxabi/*
cd build-libcxxabi
cmake -DLIBCXXABI_LIBCXX_PATH=../libcxx \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ ../libcxxabi/
make
cd -
开始编译,然后生成libc++abi.a
chenls@chenls-pc:llvm-project$ ./build_libcxxabi.sh
chenls@chenls-pc:llvm-project$ ls -al build-libcxxabi/lib/libc++abi.a
-rw-rw-r-- 1 chenls chenls 763254 5月 6 19:06 build-libcxxabi/lib/libc++abi.a
重新编译C++的虚函数
我们继续使用上面虚函数的例子,增加libc++abi.a重新编译
chenls@chenls-pc:llvm-project$ gcc ~/Desktop/test.cpp -L build-libcxxabi/lib -l:libc++abi.a
/usr/bin/ld: build-libcxxabi/lib/libc++abi.a(cxa_exception_storage.cpp.o): in function `std::__1::__libcpp_tls_set(unsigned int, void*)':
cxa_exception_storage.cpp:(.text._ZNSt3__116__libcpp_tls_setEjPv[_ZNSt3__116__libcpp_tls_setEjPv]+0x17): undefined reference to `pthread_setspecific'
/usr/bin/ld: build-libcxxabi/lib/libc++abi.a(cxa_exception_storage.cpp.o): in function `std::__1::__libcpp_execute_once(int*, void (*)())':
cxa_exception_storage.cpp:(.text._ZNSt3__121__libcpp_execute_onceEPiPFvvE[_ZNSt3__121__libcpp_execute_onceEPiPFvvE]+0x19): undefined reference to `pthread_once'
/usr/bin/ld: build-libcxxabi/lib/libc++abi.a(cxa_exception_storage.cpp.o): in function `std::__1::__libcpp_tls_get(unsigned int)':
cxa_exception_storage.cpp:(.text._ZNSt3__116__libcpp_tls_getEj[_ZNSt3__116__libcpp_tls_getEj]+0xf): undefined reference to `pthread_getspecific'
/usr/bin/ld: build-libcxxabi/lib/libc++abi.a(cxa_exception_storage.cpp.o): in function `std::__1::__libcpp_tls_create(unsigned int*, void (*)(void*))':
cxa_exception_storage.cpp:(.text._ZNSt3__119__libcpp_tls_createEPjPFvPvE[_ZNSt3__119__libcpp_tls_createEPjPFvPvE]+0x19): undefined reference to `pthread_key_create'
collect2: error: ld returned 1 exit status
出现线程相关的函数借口未定义,我们可以将libcxxabi/CMakeLists.txt中LIBCXXABI_ENABLE_THREADS关闭。
option(LIBCXXABI_ENABLE_THREADS "Build with threads enabled" OFF)
重新编译,生成libc++abi.a
chenls@chenls-pc:llvm-project$ ./build_libcxxabi.sh
chenls@chenls-pc:llvm-project$ ls -al build-libcxxabi/lib/libc++abi.a
-rw-rw-r-- 1 chenls chenls 744484 5月 6 19:48 build-libcxxabi/lib/libc++abi.a
使用新的libc++abi.a,重新编译
chenls@chenls-pc:llvm-project$ gcc ~/Desktop/test.cpp -L build-libcxxabi/lib -l:libc++abi.a
chenls@chenls-pc:llvm-project$ ./a.out
This is child,function1
This is child,function2
This is parent,function1
This is child,function2
chenls@chenls-pc:llvm-project$ readelf -a ./a.out | grep NEEDED
0x0000000000000001 (NEEDED) 共享库:[libgcc_s.so.1]
0x0000000000000001 (NEEDED) 共享库:[libc.so.6]
现在我们编译通过了,其中不再依赖c++的标准库,但其中新增了libgcc_s.so.1的依赖。
上面我们在编译时使用的是libc++abi.a,其中包含了libc++abi中所有的源码。其实我们可以精简其中的调用,我们只需要vtable相关的实现。编译时找到未定义方法的对应源码,将其.o加入编译。
#build_test.sh
gcc ~/Desktop/test.cpp \
~/Desktop/new_delete.cpp \
./build-libcxxabi/src/CMakeFiles/cxxabi_static.dir/stdlib_typeinfo.cpp.o \
./build-libcxxabi/src/CMakeFiles/cxxabi_static.dir/private_typeinfo.cpp.o \
./build-libcxxabi/src/CMakeFiles/cxxabi_static.dir/cxa_virtual.cpp.o \
./build-libcxxabi/src/CMakeFiles/cxxabi_static.dir/abort_message.cpp.o \
./build-libcxxabi/src/CMakeFiles/cxxabi_static.dir/stdlib_exception.cpp.o \
使用cmake编译出来的.o文件,重新编译运行
chenls@chenls-pc:llvm-project$ ./build_test.sh
chenls@chenls-pc:llvm-project$ ./a.out
This is child,function1
This is child,function2
This is parent,function1
This is child,function2
chenls@chenls-pc:llvm-project$ readelf -a ./a.out | grep NEEDED
0x0000000000000001 (NEEDED) 共享库:[libc.so.6]
这时候,我们发现现在只依赖 libc.so,不再依赖其他库,达到了我们的目的。
还记得我们可以手动链接libstdc++库吗,这样链接后是可用的,只是增加了对libstdc++.so的动态依赖。
chenls@chenls-pc:llvm-project$ gcc ~/Desktop/test.cpp -lstdc++
chenls@chenls-pc:llvm-project$ ./a.out
This is child,function1
This is child,function2
This is parent,function1
This is child,function2
chenls@chenls-pc:llvm-project$ ls -al ./a.out
-rwxrwxr-x 1 chenls chenls 17536 5月 7 11:09 ./a.out
chenls@chenls-pc:llvm-project$ readelf -a ./a.out | grep NEEDED
0x0000000000000001 (NEEDED) 共享库:[libstdc++.so.6]
0x0000000000000001 (NEEDED) 共享库:[libc.so.6]
我们可以在编译时,增加-static 选项,使其变成静态依赖,这样在运行时,不再动态依赖libstdc++.so,所以这样编译出来的a.out也会变大,从原来的17536字节变成了953784字节。
chenls@chenls-pc:llvm-project$ gcc ~/Desktop/test.cpp -static -lstdc++
chenls@chenls-pc:llvm-project$ ./a.out
This is child,function1
This is child,function2
This is parent,function1
This is child,function2
chenls@chenls-pc:llvm-project$ ls -al ./a.out
-rwxrwxr-x 1 chenls chenls 953784 5月 7 11:09 ./a.out
chenls@chenls-pc:llvm-project$ readelf -a ./a.out | grep NEEDED
chenls@chenls-pc:llvm-project$
以上我们直接静态依赖libstdc++和手动编译的libcxxabi.a都可以达到我们的目的。而我们手动编译的libcxxabi.a,就是来替代编译器中内置的libstdc++,那么为啥我们还需要手动编译libcxxabi的源码呢?是因为往往在无C++运行环境的平台上,编译器工具也没有内置libstdc++静态库,所以我们需要手动编译一个C++标准库。