C++流实现内幕---由boost::lexical_cast引发的一个问题

简介:


中午同事碰见一个关于使用boost::lexical_cast产生异常的问题,关键代码如下

  1. string str(8,'/0');
  2. strncpy(&str.at(0),"1234567",7);
  3. cout << lexical_cast<int>(str) << endl;

结果运行的时候发生如下异常

  1. terminate called after throwing an instance of 'boost::bad_lexical_cast'
  2.   what():  bad lexical cast: source type value could not be interpreted as target

我们知道boost::lexical_cast最终使用的stringstream实现的数值类型转换,所以,我们使用如下例子,做测试

  1. stringstream ss;
  2. ss << str;
  3. ss >> result;
  4. cout << "new Result: " << result << endl;

编译运行后,输出

new Result: 1234567

可以正常显示,好像没有问题,

我们察看一下boost的源代码

vim /usr/include/boost/lexical_cast.hpp

察看lexical_cast函数

  1. template<typename Target, typename Source>
  2.     Target lexical_cast(Source arg)
  3.     {
  4.         detail::lexical_stream<Target, Source> interpreter;
  5.         Target result;
  6.  
  7.         if(!(interpreter << arg && interpreter >> result))
  8.             throw_exception(bad_lexical_cast(typeid(Target), typeid(Source)));
  9.         return result;
  10. }

可见lexical_cast函数非常简单,就是具体执行operator<<operator>>两个操作,只要这两个操作有一个失败就抛出一个异常,为了确认是那步出的错,我们在程序中手工执行这两个操作。代码如下

  1. detail::lexical_stream<int, string> interpreter;
  2.     int result;
  3.  
  4.     if(!(interpreter << str ))
  5.     {
  6.         cout << "Error 1" << endl;
  7.     }
  8.     if(!(interpreter >> result))
  9.     {
  10.         cout << "Error 2" << endl;
  11.     }
  12. cout << result << endl;

编译运行后输出

Error 2

从这里我们知道,lexical_cast是在执行输出流的时候发生的问题,察看detailoperator>>函数,其源代码如下

  1. template<typename InputStreamable>
  2.             bool operator>>(InputStreamable &output)
  3.             {
  4.                 return !is_pointer<InputStreamable>::value &&
  5.                        stream >> output &&
  6.                        (stream >> std::ws).eof();
  7.             }

根据以上代码和我们使用stringstream做的测试,基本上可以确定在stream>>output(包括次步)都是正确的,可能出现问题的是(stream >> std::ws).eof();

这里解释下std::wsstringstring::eof()函数

Std::ws函数声明在

/usr/include/c++/3.4.4/bits/istream.tcc

源代码如下

  1. // 27.6.1.4 Standard basic_istream manipulators
  2.   template<typename _CharT, typename _Traits>
  3.     basic_istream<_CharT,_Traits>&
  4.     ws(basic_istream<_CharT,_Traits>& __in)
  5.     {
  6.       typedef basic_istream<_CharT, _Traits>        __istream_type;
  7.       typedef typename __istream_type::__streambuf_type __streambuf_type;
  8.       typedef typename __istream_type::__ctype_type __ctype_type;
  9.       typedef typename __istream_type::int_type     __int_type;
  10.  
  11.       const __ctype_type& __ct = use_facet<__ctype_type>(__in.getloc());
  12.       const __int_type __eof = _Traits::eof();
  13.       __streambuf_type* __sb = __in.rdbuf();
  14.       __int_type __c = __sb->sgetc();
  15.  
  16.       while (!_Traits::eq_int_type(__c, __eof)
  17.          && __ct.is(ctype_base::space, _Traits::to_char_type(__c)))
  18.     __c = __sb->snextc();
  19.  
  20.        if (_Traits::eq_int_type(__c, __eof))
  21.      __in.setstate(ios_base::eofbit);
  22.       return __in;
  23. }

主要作用是过滤输入流中的空格,/n/r等字符。stream >> std::ws目的就是把输入流中转换完整形后的剩余流内容(假如有的话)写入std::ws,当然只能写入其中的空格和/n/r等字符。

stringstring::eof()函数参考 http://www.cppreference.com/wiki/io/eof 部分

该函数的主要作用是,如果到达流的结束位置返回true,否则返回false

根据以上信息,我们编写测试用例

  1. stringstream ss;
  2. ss << str;
  3. ss >> result;
  4. cout << "new Result: " << result << endl;

  5. cout << ss.eof() << endl;
  6. cout << (ss >> std::ws).eof() << endl;

编译运行后输出

new Result: 1234567

0

0

由此可见,虽然我们使用ss时,可以输出想要的正确结果,但是我们缺少最后的安全验证,而boost::lexical_cast就做了这方面的验证。

其实例子中的’/0’在开始的时候,起了不小的误导作用,开始以为是boost::lexical_cast无法处理最后末尾是’/0’的字符串,到现在其实不然,我们把’/0’转换为’a’字符一样会出现这种问题,但是我们使用’/n’,’/r’和空格等字符就不会出现这种问题,现在我们知道其根源就是在字符转换过程中输入流没有输入全部字符,所以流的结束标志EOF,一直为0

其实在上面的应用中我们不能一直认为boost::lexical_cast的方法一定是好的。在我们编成过程中,常见的转换是把一段字符串中含有数字和字母的字符串中的数字串转换为整形,这样的如果我们使用boost::lexical_cast的话,永远得不到正确结果了,每次都会有异常抛出,这时候我们可以使用stringstream,转换后不判断eof(),这样就可以得到我们想要的整数。

       在上面的测试中,突然想到一个变态的想法,STL中的字符串转为整形的流实现是怎么做的,不过SGISTL真够难堪的。

       大体查找过程如下

       (1): Vim /usr/include/c++/3.4.4/sstream

发现引用了istream

       (2):  Vim /usr/include/c++/3.4.4/ istream

发现operator<<(int)的实现在bits/istream.tcc文件中

       (3): Vim /usr/include/c++/3.4.4/ bits/istream.tcc

发现const __num_get_type& __ng = __check_facet(this->_M_num_get);__ng.get(*this, 0, *this, __err, __l);所以查找__num_get_type类型中的get函数,同时发现istream.tcc中的#include <locale> 比较陌生,同时在istream中查找__num_get_type 类型为typedef num_get<_CharT, istreambuf_iterator<_CharT, _Traits> > __num_get_type; 所以,最终要查找的类型为num_get

 

        (4): Vim /usr/include/c++/3.4.4/locale

发现这个文件中包括以下头文件

  1. #include <bits/localefwd.h>
  2. #include <bits/locale_classes.h>
  3. #include <bits/locale_facets.h>
  4. #include <bits/locale_facets.tcc>

逐个察看

       (5):  Vim /usr/include/c++/3.4.4/ bits/localefwd.h

发现模板类num_get声明

template<typename _CharT, typename _InIter = istreambuf_iterator<_CharT> >

    class num_get;

 

       (6): Vim /usr/include/c++/3.4.4/ bits/locale_facets.h

在这个文件中发现num_get的实现

template<typename _CharT, typename _InIter>

class num_get : public locale::facet

查找get方法

iter_type

      get(iter_type __in, iter_type __end, ios_base& __io,

      ios_base::iostate& __err, bool& __v) const

      { return this->do_get(__in, __end, __io, __err, __v); }

查找do_get方法

 

       (7):  Vim /usr/include/c++/3.4.4/ bits/locale_facets.tcc

发现

  1. // _GLIBCXX_RESOLVE_LIB_DEFECTS
  2.   // 17.  Bad bool parsing
  3.   template<typename _CharT, typename _InIter>
  4.     _InIter
  5.     num_get<_CharT, _InIter>::
  6.     do_get(iter_type __beg, iter_type __end, ios_base& __io,
  7.            ios_base::iostate& __err, bool& __v) const
  8.     {
  9.       if (!(__io.flags() & ios_base::boolalpha))
  10.         {
  11.       // Parse bool values as long.
  12.           // NB: We can't just call do_get(long) here, as it might
  13.           // refer to a derived class.
  14.       long __l = -1;
  15.           __beg = _M_extract_int(__beg, __end, __io, __err, __l);
  16.       if (__l == 0 || __l == 1)
  17.         __v = __l;
  18.       Else
  19. ...

查找_M_extract_int 方法

终于找到

  1.  template<typename _CharT, typename _InIter>
  2.     template<typename _ValueT>
  3.       _InIter
  4.       num_get<_CharT, _InIter>::
  5.       _M_extract_int(_InIter __beg, _InIter __end, ios_base& __io,
  6.              ios_base::iostate& __err, _ValueT& __v) const
  7.       {
  8.         typedef char_traits<_CharT>         __traits_type;
  9.     typedef typename numpunct<_CharT>::__cache_type __cache_type;
  10.     __use_cache<__cache_type> __uc;
  11.     const locale& __loc = __io._M_getloc();
  12.     const __cache_type* __lc = __uc(__loc);
  13. const _CharT* __lit = __lc->_M_atoms_in;
  14. ....

分析_M_extract_int的关键代码,

如下

  1. int __base = __oct ? 8 : (__basefield == ios_base::hex ? 16 : 10);
  2.           const _ValueT __new_result = __result * __base
  3.                                          - __digit;
  4.             __overflow |= __new_result > __result;
  5.             __result = __new_result;
  6.             ++__sep_pos;
  7.             __found_num = true;

根据以上代码C++中的流转换,没有使用什么特别的技巧,在由字符串转为数字时,使用的也是查找字符*10(8,16)的方法,只是这个过程中多了很多步我们想不到的安全验证。

 

总算搞明白了,sgi真不是给人看得,你也可以了解float类型是怎么实现的,参考_M_extract_float函数。

目录
相关文章
|
23天前
|
开发框架 Linux C语言
C、C++、boost、Qt在嵌入式系统开发中的使用
C、C++、boost、Qt在嵌入式系统开发中的使用
31 1
|
4月前
|
设计模式 网络协议 Java
C++ Boost 异步网络编程基础
Boost库为C++提供了强大的支持,尤其在多线程和网络编程方面。其中,Boost.Asio库是一个基于前摄器设计模式的库,用于实现高并发和网络相关的开发。Boost.Asio核心类是`io_service`,它相当于前摄模式下的`Proactor`角色。所有的IO操作都需要通过`io_service`来实现。在异步模式下,程序除了发起IO操作外,还需要定义一个用于回调的完成处理函数。`io_service`将IO操作交给操作系统执行,但它不同步等待,而是立即返回。调用`io_service`的`run`成员函数可以等待异步操作完成。当异步操作完成时,`io_service`会从操作系统获取结
80 1
C++ Boost 异步网络编程基础
|
8月前
|
XML JSON 数据格式
4.8 C++ Boost 应用JSON解析库
property_tree 是 Boost 库中的一个头文件库,用于处理和解析基于 XML、Json 或者 INFO 格式的数据。 property_tree 可以提供一个轻量级的、灵活的、基于二叉数的通用容器,可以处理包括简单值(如 int、float)和复杂数据结构(如结构体和嵌套容器)在内的各种数据类型。它可以解析数据文件到内存中,然后通过迭代器访问它们。
46 0
|
3月前
|
存储 Unix Linux
boost C++知识点(六)
boost C++知识点(六)
|
3月前
|
域名解析 网络协议 编译器
boost C++知识点(五)
boost C++知识点(五)
|
3月前
|
存储 C++ 容器
boost C++知识点(四)
boost C++知识点(四)
|
3月前
|
算法 C++
boost C++知识点(三)
boost C++知识点(三)
|
3月前
|
存储 安全 C++
boost C++知识点(一)
boost C++知识点(一)
|
4月前
|
存储 网络协议 安全
C++ Boost 实现异步端口扫描器
端口扫描是一种用于识别目标系统上哪些网络端口处于开放、关闭或监听状态的网络活动。在计算机网络中,端口是一个虚拟的通信端点,用于在计算机之间传输数据。每个端口都关联着特定类型的网络服务或应用程序。端口扫描通常是网络管理员、安全专业人员或黑客用来评估网络安全的一种方法。通过扫描目标系统的端口,可以了解系统上哪些服务在运行、哪些端口是开放的,从而评估系统的安全性。
42 0
C++ Boost 实现异步端口扫描器
|
8月前
|
存储 网络协议 API
4.9 C++ Boost 命令行解析库
命令行解析库是一种用于简化处理命令行参数的工具,它可以帮助开发者更方便地解析命令行参数并提供适当的帮助信息。C++语言中,常用的命令行解析库有许多,通过本文的学习,读者可以了解不同的命令行解析库和它们在C++项目中的应用,从而更加灵活和高效地处理命令行参数。
97 0

热门文章

最新文章