如何在无C++运行环境下,运行大部分的C++代码(二)

前言

承接上一篇 如何在无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++标准库。

下一篇 如何在无C++运行环境下,运行大部分的C++代码(三)

 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值