在gcc中的使用调试版c++运行库

来源:互联网 发布:unity3d 骰子模型 编辑:程序博客网 时间:2024/05/17 02:49

在gcc中的使用调试版c++运行库

背景

vc中的debugrelease版很大的一个区别就是他们会分别链接debugrelease版的c/c++运行库,而且之间相互不能混用。debug版的c++运行库能够对一些无效迭代器、越界等行为提供检查,提前在debug版发现问题。
这么想来在linux下gcc应该也有相同的功能,但是平时在使用gcc编译程序时好像并没有什么选项能够链接debug版的c++运行库。vc下的c++运行库叫msvcpxxx_d.dll,但gcc下好像并没有听说过libstdc++_d.so之类的东西。
我觉得gcc不可能没有提供这种基本的功能。但是我在搜索后惊讶的发现居然没有一篇中文文章描述如何启用这个功能。我不得不翻墙后硬着头皮阅读google的结果(感谢公司提供的自动翻墙),让我惊讶的是搜索结果的前几条都清楚的描述了怎样启动这一功能。我不相信没有中国人懂这个东西,但是为什么就没有一篇中文的文档呢?以前听人说中国人没有分享精神,倒是没什么体会,今天终于体会到了。见贤思齐焉,见不贤而内自省也。所以我决定将我这几天对这个知识的了解写成中文文档并分享出来。
以下内容皆是我阅读gnu官方文档的理解,因为我英文水平有限,不保证内容无误。如有错漏还请见谅。

开启调试功能

在gcc中使用debug版的c++运行库很简单,只需要在编译源文件的时候定义_GLIBCXX_DEBUG宏即可。即在g++命令行中附加-D_GLIBCXX_DEBUG或者直接在代码中包含c++标准库头文件前定义这个宏。

混合链接

众所周知vc下debugrelease版的库不能混用,就是如果一个跨模块使用了debug版运行库的库是不能去链接一个使用了release版运行库的库的,否则可能会在运行时崩溃。但是gcc下有一些不同,只要不将除string以外的容器作为返回值的情况下是可以混合链接分别使用了debugrelease版的库的。

跨版本链接

vc下的vs运行库比较多,情况复杂,官方也明确说明不建议跨版本链接不同版本的c++运行库。
而对于gcc,官网上的说明以gcc5.1开始作为一个分界线,从gcc5.1开始完全实现了c++11的标准。对stringlist做了修改。不能跨模块使用。
当然也可以定义_GLIBCXX_USE_CXX11_ABI宏为0来强制使用旧版的库,就不存在这个问题了。

不过gcc相比vc有一点好的就是在将容器做除了返回值的跨模块调用的时候如果不能跨版本会在链接阶段就失败,而vc会在运行时可能才出现崩溃。

原理

对于以上的结论我一直有一个疑问,gcc是怎么实现的允许大部分混合链接和跨版本链接,vc为什么就是不行呢?在阅读了官方文档的描述中总算有了一个大致的了解。
- 混合链接:

gcc使用了一个特殊的命名空间用法,将debugrelease版的类定义在了两个命名空间里,让debug的类继承release版的类,并在_GLIBCXX_DEBUG宏定义生效的时候给release版的类套一个命名空间并将debug版的命名空间设为inline namespace(inline namespace的作用就是内联的命名空间引入到外层空间,也就是在使用的时候可以忽略掉内联的命名空间,但是编译的时候还是起作用的)。此时在std中引用的就是debug版的类了。

//release版的类namespace std{  template<typename _Tp, typename _Alloc = allocator<_Tp> >    class list    {      // ...     };} // namespace std//当定义了_GLIBCXX_DEBUG的代码namespace std{  namespace __cxx1998  {    template<typename _Tp, typename _Alloc = allocator<_Tp> >      class list      {    // ...      };  } // namespace __gnu_norm  namespace __debug  {    template<typename _Tp, typename _Alloc = allocator<_Tp> >      class list      : public __cxx1998::list<_Tp, _Alloc>,    public __gnu_debug::_Safe_sequence<list<_Tp, _Alloc> >      {    // ...      };  } // namespace __cxx1998  // namespace __debug __attribute__ ((strong));  inline namespace __debug { }}

可以看到,debug版的代码首先给release版的类套了一层__cxx1998命名空间,然后在__debug命名空间里继承了这个类,最后将__debug命名空间设为内联的。
但是实际上两个不同版本的类在不同的命名空间里,名字不一样,编译器会认为这是两个类,链接的时候也会当成两个类链接,不会出现混乱。
但是为什么不能在返回值里混合使用呢?因为编译后的对象文件里导出的函数签名里有参数的类型的详细信息,链接器就是靠这个链接相应的类的。而众所周知函数签名里是不会包括返回值的类型信息的,所以链接的时候完全无法一一匹配。
但那又为什么单单string可以在返回值里混合使用呢?那是因为gnu的标准库作者考虑到了string作为返回值使用时一个非常常见的行为,所以对string做了特殊的处理,使得debugrelease版的string类可以通用。
而vc应该是从一开始就没能使用这样的设计导致完全无法混合链接,当然也可能和vc对新标准支持的晚导致的(inline namespace 特性是c++11标准引入的)。

  • 跨版本链接
    gcc的跨版本链接使用了和上面同样的技术原理,新的版本被套上了一层命名空间。std::list<int>实际是std::__cxx11::list<int>

总结

vc和gcc的运行库都是开放源代码的,源码面前了无秘密,理论上只要看懂源码就可以对这些知识了如指掌。但因为我时间精力有限,并没能阅读源码,只能对官方文档进行了解读。所以我的理解可能含有并不全面或有错漏,但还是希望能对其他人有所帮助。

0 0
原创粉丝点击