众所周知从Xcode10起,苹果摒弃了对libstdc++库的支持转而支持libc++库了。这两个库在Xcode9甚至更早的版本就已经同时存在于系统中而且可供开发者选择,固然在Xcode9时代苹果就已经宣布了将要废弃libstdc++的信息了。html
一个app应用程序中若是用到C++相关的代码和类库那么就须要连接C++标准库。C++标准库是一套基于C++语言之上的函数和类库,其早期代码都定义在std命名空间中,大部分类都是用template模板实现的,它主要由IO流,string字符串类,和STL组成。标准库中的实现代码除了分布在没有后缀的头文件(好比vector等大部分模板类)外还有一部分代码被存放到了相应的动态库中,也就是存放在libstdc++.dylib或者libc++.dylib中。至于为何一个标准库由两个动态库来实现则会在后面进行详细介绍。ios
一门语言老是不可能一成不变的,C++也是如此,随着时间的推移它也会有升级变化的改进需求。可是C++这门语言却不像Swift那样不负责任,它的标准和规范的升级相对来讲比较严谨。我的以为缘由是其自己已经很是庞大并且完善了,能升级的基本都是微小的调整了。也许你会发现其余不少语言都是C++这门语言的裁剪版。因此能够说学好C++,走遍天下都不怕! 下面这个表格列出的就是C++的各类版本:c++
Year | C++ Standard | Informal name |
---|---|---|
1998 | ISO/IEC 14882:1998[20] | C++98 |
2003 | ISO/IEC 14882:2003[21] | C++03 |
2011 | ISO/IEC 14882:2011[22] | C++11, C++0x |
2014 | ISO/IEC 14882:2014[23] | C++14, C++1y |
2017 | ISO/IEC 14882:2017[8] | C++17, C++1z |
2020 | to be determined | C++20 |
在C++11标准出来之前,市面上的编译器厂商基本上支持的都是C++98的版本。大部分的书籍或者知识里面的语法和规则都是基于C++98的。C++11主要添加了: 类型自动推导、线程API支持、智能指针内存管理、lamda表达式、STL扩展等能力(若是你想更加详细了解这些新规范,请参考:C++11新特性介绍)。各大编译器厂商为了自身的须要会对规范进行一些定制化处理**(这些语法的标准以及厂商的定制化称为方言Dialect)**。目前比较流行的C++编译器有微软的VC++,GNU组织的gcc(g++), 苹果的LLVM(clang++)等。这些厂商或多或少的对C++的规范进行一些裁剪或者扩充以及对C++的各个版本的支持力度也有所不一样。就目前来讲主流的编译器几乎都对C++11标准已经彻底支持了。git
正如前面所说的C++有不一样的版本,其中的libstdc++.dylib所表明的就是C++98版本的标准库实现动态库,而libc++.dylib所表明的则是C++11版本的标准库实现动态库。也就是说libc++其实一个更加新的C++标准库实现,它彻底支持C++11标准,而苹果的Xcode10将再也不支持老版本的标准库libstdc++实现,而是升级为只支持新版本的标准库libc++实现了。某个静态库若是之前是依赖于libstdc++库中的代码,那么这个静态库在Xcode10中被连接时将会报符号找不到的连接错误信息:Undefined symbols for architecture XXX
,好比下面的提示:github
Undefined symbols for architecture x86_64:
"std::__throw_length_error(char const*)", referenced from:
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&) in libcpplib.a(cpplib.o)
"std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::allocator<char>::allocator()", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::string::c_str() const", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::allocator<char>::~allocator()", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
ld: symbol(s) not found for architecture x86_64
复制代码
可能你会想按理来讲libc++库中的代码实现应该只是libstdc++中代码实现的升级版本,应该要存在着兼容的状况,那为何还会报符号未定义的错误呢?答案我将会在后面详细说明。bash
在查看一个程序运行时所加载的全部C++动态库时,你会发现有一个叫libc++abi.dylib的动态库存在。这个库主要是对C++的: new/delete、try/catch/throw、typeid等关键字的实现支持。这些关键字并非一些简单的关键字,它们还承载着必定的功能。其实在一些语言中为了使用上的简化每每会将一些能力提炼成为一个特殊的关键字,这样在使用这些能力时每每再也不须要编写任何的代码,只要借助对应的关键字就能够简化这些功能的实现。除了C++外一个典型的例子就是GO语言中的chan 关键字。对于C++这门语言来讲系统会将上述的那些关键字所实现的功能的代码存放到了一个库中,这个库就是libc++abi.dylib库。下面将简单的介绍一下libc++abi.dylib中都有那些功能:数据结构
void * operator new(size_t size);
void operator delete(void *p);
复制代码
而这两个函数的实现代码就是存放在libc++abi这个动态库中的。app
extern _LIBCXXABI_FUNC_VIS _LIBCXXABI_NORETURN void
__cxa_throw(void *thrown_exception, std::type_info *tinfo,
void (*dest)(void *));
// 2.5.3 Exception Handlers
extern _LIBCXXABI_FUNC_VIS void *
__cxa_get_exception_ptr(void *exceptionObject) throw();
extern _LIBCXXABI_FUNC_VIS void *
__cxa_begin_catch(void *exceptionObject) throw();
extern _LIBCXXABI_FUNC_VIS void __cxa_end_catch();
复制代码
来实现异常处理的,而这些函数的实现代码也是存放在libc++abi这个动态库中。ide
能够看出libc++abi这个动态库是一个支持C++语法的核心库。函数
Xcode中创建的工程项目能够选择使用的C++的方言和C++的标准库版本,在工程的Build Settings中的Apple Clang - Language - C++中的分组中的C++ Language Dialect中选择使用的C++方言类型;C++ Standard Library中选择使用的C++标准库的版本。
咱们能够经过下面的代码来验证C++语言对于方言的支持选项,由于在C++11中才引入了对lamda表达式的支持,所以你能够在你工程的某个.mm文件的函数实现内写一段lamda表达式:
//test.mm
void foo()
{
auto f = []{ NSLog(@"test"); };
f();
}
复制代码
默认状况下Xcode对于方言的支持是c++14,所以上面的代码能够被编译经过,若是将C++ Language Dialect的选项改成:C++98[-std=c++98]
后就会发现编译时报错:
xxxxxxx\test.mm:52:16: error: expected identifier
auto f = [] { NSLog(@"test"); };
^
1 error generated.
复制代码
对于方言的选择以及语言类型的选择体如今编译选项-std=
上,这个选项经过查看Xcode的编译消息详情就能够看出:若是文件的后缀是.m,那么-std=
后面的值就是C Language Dialect中的选项;若是文件的后缀是.mm,那么-std=
后面的值就是C++ Language Dialect中的选项。
Xcode中对于C++标准库C++ Stadard Library选项的选择影响的是连接的标准库动态库的版本以及对应的头文件的搜索路径。
若是你选择的标准库是libc++
。那么头文件的搜索路径将会是:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1,而且连接的动态库就是libc++.dylib。
若是你选择的标准库是libstdc++
,那么头文件的搜索路径将会是:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/c++/4.2.1,而且连接的动态库就是libstdc++.dylib。
对于标准库的选择,体如今编译选项 -libstd=
上,查看Xcode的编译消息详情就能够看出:若是某个文件的后缀是.mm,那么-libstd=
后面的值就是C++ Standard Library中的选项值。
在低于Xcode10的IDE中还能够在工程的Build Phases的Link Binary With Libraries中同时添加对libc++.tbd和libstdc++.tbd的连接引用,那么这里就会带来一个问题?为何能够在一个工程中能够同时引入两个定义了相同内容的类库呢?难道不会在编译时报符号冲突或者重名的错误吗?但实际又不会报符号名冲突的错误,缘由就是C++11中引入的一个新特性来保证不会处问题的,这个新特性就是内联命名空间(inline namespace)。
假如你在两个不一样的动态库中定义和导出了一个相同的函数或者类,而且当将这两个动态库都加入依赖后。一旦在程序中调用那个同名函数时,就会出现函数重复定义或者引入不明确的连接错误。可这个问题却不会发生在不一样版本的C++标准库:libstdc++和libc++中,你能够在程序中同时依赖这两个库,而不会产生编译连接错误。咱们知道libc++中的内容是libstdc++中的超集,为何在同时引入两个库时不会报函数或者类名冲突呢? 答案就是C++11中提供了对inline namespace的支持。前面说过老版本C++标准库中的全部类的定义都是在std这个命名空间中。当你选择的是libstdc++是你就会在全部头文件中内容都定义在两个宏:_GLIBCXX_BEGIN_NAMESPACE和_GLIBCXX_END_NAMESPACE之间,好比中的标准输入和输出流对象的定义片断:
_GLIBCXX_BEGIN_NAMESPACE(std)
extern istream cin; ///< Linked to standard input
extern ostream cout; ///< Linked to standard output
extern ostream cerr; ///< Linked to standard error (unbuffered)
extern ostream clog; ///< Linked to standard error (buffered)
#ifdef _GLIBCXX_USE_WCHAR_T
extern wistream wcin; ///< Linked to standard input
extern wostream wcout; ///< Linked to standard output
extern wostream wcerr; ///< Linked to standard error (unbuffered)
extern wostream wclog; ///< Linked to standard error (buffered)
#endif
_GLIBCXX_END_NAMESPACE
复制代码
上述的两个宏则定义在<bits/c++config.h>下面,展开这两个宏定义:
# define _GLIBCXX_BEGIN_NAMESPACE(X) namespace X {
# define _GLIBCXX_END_NAMESPACE }
namespace std {
}
复制代码
所以能够明确早期的C++标准库中的全部类和函数以及变量都是定义在std这个命名空间中的。
当你使用libc++标准库时,你会发现全部头文件中的类和方法都定义在_LIBCPP_BEGIN_NAMESPACE_STD和_LIBCPP_END_NAMESPACE_STD以内。好比中的标准输入和输出流对象的定义片断:
LIBCPP_BEGIN_NAMESPACE_STD
#ifndef _LIBCPP_HAS_NO_STDIN
extern _LIBCPP_FUNC_VIS istream cin;
extern _LIBCPP_FUNC_VIS wistream wcin;
#endif
#ifndef _LIBCPP_HAS_NO_STDOUT
extern _LIBCPP_FUNC_VIS ostream cout;
extern _LIBCPP_FUNC_VIS wostream wcout;
#endif
extern _LIBCPP_FUNC_VIS ostream cerr;
extern _LIBCPP_FUNC_VIS wostream wcerr;
extern _LIBCPP_FUNC_VIS ostream clog;
extern _LIBCPP_FUNC_VIS wostream wclog;
复制代码
上述两个宏的定义在<__config>中能够看到,展开后的定义以下:
//为了更好理解,我把下面的宏和命令空间中的定义进行了简化处理
#define _LIBCPP_BEGIN_NAMESPACE_STD namespace std {inline namespace __1 {
#define _LIBCPP_END_NAMESPACE_STD } }
namespace std {
inline namespace __1 {
}
}
复制代码
能够看出在libc++中,全部的类和方法以及变量都不是直接在std这个命名空间中被定义,而是放到其子命名空间std::__1中去了。子命名空间中的 inline关键字则是C++11中为命名空间添加的新关键字:**能够在父命名空间中定义内联的子命名空间,内联的子命名空间能够把其包含的名字导入到父命名空间中,从而在父命名空间中能够直接访问子命名空间中定义的名字,而不用经过域限定符Child::name的形式来访问。**就以下面的例子:
#include <iostream>
void main()
{
std::__1::cout << "hello1" << std::__1::endl;
std::cout << "hello2" << std::endl;
}
复制代码
在C++11中的标准输出流对象cout真实的定义是在std::__1这个命名空间中,可是由于std::__1::是内联子命名空间因此能够经过父命名空间std::来访问。 正是由于内联命名空间的使用,因此工程中的代码是能够切换不一样版本的C++标准库的,并且还能够同时连接两个不一样的C++标准库libstdc++.dylib和libc++.dylib,由于这两个不一样版本中的代码所在命名空间是不同的,所以不会产生符号重复和冲突的错误!其实C++中的命名空间引入inline关键字就是为了解决版本的兼容性和冲突的。 这也就能够解释当咱们把一个依赖libstdc++.dylib的静态库,引入到Xcode10的工程中时会报以下的错误:
Undefined symbols for architecture x86_64:
"std::__throw_length_error(char const*)", referenced from:
std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&) in libcpplib.a(cpplib.o)
"std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::allocator<char>::allocator()", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::string::c_str() const", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::allocator<char>::~allocator()", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
"std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()", referenced from:
-[cpplib testfn] in libcpplib.a(cpplib.o)
ld: symbol(s) not found for architecture x86_64
复制代码
其缘由就是由于出错的C++类是在std::这个命名空间中被定义的(由于C++的命名修饰规则的缘由,一个方法或者函数被修饰后的名称是包含其所在的命名空间的)。可是新版本的C++标准库中的全部符号都是在std::__1这个命名空间中,所以连接器将没法找到这个符号。好比标准输入流对象cin在libc++中和libstdc++中的定义就不同:
__ZNSt3__13cinE //这是cin在libc++.dylib库中的被修饰事后的真实名字
__ZSt3cin //这是cin在libstdc++.dylib库中的被修饰事后的真实名字
复制代码
一个问题:刚才不是说到的内联子命名空间是能够直接经过父命名空间来访问的。为何这里又不能够呢?上述的内联命名空间的访问只是在编译时是没有问题的,可是在连接这个阶段是不会认内联命名空间的,连接阶段只认被修饰事后的符号,也就是在连接阶段是没有内联命名空间这个概念的。
那既然在Xcode10中报连接错误,又怎么解决这种问题呢?方法有两个:
在Xcode10中已经找不到libstdc++.dylib这个库了,并且当工程中有依赖libstdc++这个库时或者工程设置里面的C++ Stadard Library选项设置为libstdc++时,就会报以下的错误:
clang: warning: libstdc++ is deprecated; move to libc++ [-Wdeprecated]
ld: library not found for -lstdc++
clang: error: linker command failed with exit code 1 (use -v to see invocation)
复制代码
前面已经分析了Xcode10对两个标准库支持的前因后果,并且也简单的介绍了只要将老版本中的libstdc++.dylib拷贝到新版本的IDE环境中便可,具体的方法和流程你们能够参考以下两篇文章:
blog.csdn.net/box_kun/art… blog.csdn.net/u010960265/…
但其实这样是有风险的,由于Xcode10中对于C++标准库的头文件都是基于C++11的,所以当你经过上述方法引入了老版本的C++标准库时,虽然在编译连接时不会报错正常编译经过,可是在运行时就可能会出现崩溃的问题,尤为是当你的静态库中将某个老的C++标准库中类的对象做为接口或者函数参数暴露出来给外界使用时就有可能由于新老版本的数据结构和内部实现的差别而形成运行时的崩溃!总之为了完全的解决这些问题,仍是要求将你的静态库中的代码在Xcode10中从新编译是最好的解决方案。
en.wikipedia.org/wiki/C++ blog.csdn.net/ftell/artic… blog.csdn.net/fengbingchu… blog.csdn.net/Jxianxu/art…