簡體   English   中英

字符串流無符號輸入驗證

[英]stringstream unsigned input validation

我正在編寫程序的一部分,該程序分析並驗證程序控制台參數中的某些用戶輸入。 我選擇將stringstream用於此目的,但是在讀取無符號類型時遇到問題。

下一個模板旨在從給定的字符串中讀取請求的類型:

#include <iostream>
#include <sstream>
#include <string>

using std::string;
using std::stringstream;
using std::cout;
using std::endl;

template<typename ValueType>
ValueType read_value(string s)
{   
    stringstream ss(s);
    ValueType res;
    ss >> res;
    if (ss.fail() or not ss.eof())
        throw string("Bad argument: ") + s;
    return res;
}
// +template specializations for strings, etc. 

int main(void)
{   
    cout << read_value<unsigned int>("-10") << endl;
}   

如果類型是無符號的並且輸入字符串包含負數,我希望看到異常拋出(由ss.fail() = true引起)。 但是stringstream產生強制轉換為無符號類型值(書面樣本中為4294967286)。

如何固定該示例以實現所需的行為(最好是不回退到c函數)? 我知道可以通過簡單的第一個符號檢查來完成,但是我可以放置前導空格。 我可以編寫自己的解析器,但不要相信這個問題是如此不可預測,並且標准庫無法解決它。

隱藏在字符串流運算符深處的無符號類型函數是strtoull和strtoul。 它們以描述的方式工作,但是提到的功能是低級的 為什么stringstream不提供某些驗證級別? (我只是希望我做錯了,但是確實需要做一些動作才能實現)。

版本免責聲明:對於C ++ 03,答案是不同的。 以下處理C ++ 11。

首先,讓我們分析發生了什么。

ss >> res; 這將調用std::istream::operator>>(unsigned) 在[istream.formatted.arithmetic] / 1中,效果定義如下:

這些提取器充當格式化的輸入函數(如27.7.2.2.1中所述)。 構造哨兵對象后,將發生轉換,就像由以下代碼片段執行的那樣:

 typedef num_get< charT,istreambuf_iterator<charT,traits> > numget; iostate err = iostate::goodbit; use_facet< numget >(loc).get(*this, 0, *this, err, val); setstate(err); 

在上面的片段中, loc代表basic_ios類的私有成員。

在[istream :: sentry]的格式化輸入函數之后, sentry對象的主要作用是消耗前導空白字符。 如果發生錯誤(流處於失敗/ eof狀態),這也將阻止執行上面顯示的代碼。

使用的語言環境是"C"語言環境。 理由:

對於該stringstream通過構成stringstream ss(s); ,該iostream的語言環境是構建時的當前全局語言環境(保證在[ios.base.locales] / 4的兔子洞的深處)。 由於未在OP的程序中更改全局語言環境,因此[locale.cons] / 2指定“經典”語言環境,即"C"語言環境。

use_facet< numget >(loc).get使用成員函數num_get<char>::get(iter_type in, iter_type end, ios_base&, ios_base::iostate& err, unsigned int& v) const; 在[locale.num.get]中指定(請注意unsigned int ,一切都還可以)。 [facet.num.get.virtuals]中描述了“ C”語言環境的字符串-> unsigned int轉換的詳細信息。 一些有趣的細節:

  • 對於無符號整數值,使用函數strtoull
  • 如果轉換失敗,則將ios_base::failbit分配給err 特別是:“要存儲的數值可以是以下其中之一:[...]最負的可表示值,或者對於無符號整數類型為零,如果該字段表示的值太大而無法在值中表示ios_base::failbit分配給err 。”

我們需要轉到strtoull ,以了解第5段中strtoull的定義:

如果主題序列以減號開頭,則轉換結果取反(返回類型)。

並根據第8段:

如果正確的值超出可表示的值的范圍,則返回LONG_MINLONG_MAXLLONG_MINLLONG_MAXULONG_MAXULLONG_MAX (根據返回值和值的符號,如果有的話),並且宏ERANGE值為存儲在errno

看來,它已經在過去的辯論,如果負值被認為是有效的輸入strotoul 無論如何,問題在於此功能。 快速檢查gcc表示它被視為有效輸入,因此您觀察到的行為也是如此。


歷史注釋:C ++ 03

C ++ 03在num_get轉換中使用了scanf 不幸的是,我還不確定(如何)指定scanf的轉換以及在什么情況下會發生錯誤。


顯式錯誤檢查:

我們可以通過使用帶符號的值進行轉換並測試<0來手動插入該檢查,或者尋找-字符(由於可能存在的本地化問題,這不是一個好主意)。

一個num_get構面,用於支持顯式檢查簽名。 對於無符號類型,拒絕以'-' (在空格之后)開頭的任何非零數字,並使用默認的C語言環境的num_get進行實際轉換。

#include <locale>
#include <istream>
#include <ios>
#include <algorithm>

template <class charT, class InputIterator = std::istreambuf_iterator<charT> >
class num_get_strictsignedness : public std::num_get <charT, InputIterator>
{
public:
    typedef charT char_type;
    typedef InputIterator iter_type;

    explicit num_get_strictsignedness(std::size_t refs = 0)
        : std::num_get<charT, InputIterator>(refs)
    {}
    ~num_get_strictsignedness()
    {}

private:
    #define DEFINE_DO_GET(TYPE) \
        virtual iter_type do_get(iter_type in, iter_type end,      \
            std::ios_base& str, std::ios_base::iostate& err,       \
            TYPE& val) const override                              \
        {  return do_get_templ(in, end, str, err, val);  }         // MACRO END

    DEFINE_DO_GET(unsigned short)
    DEFINE_DO_GET(unsigned int)
    DEFINE_DO_GET(unsigned long)
    DEFINE_DO_GET(unsigned long long)

    // not sure if a static locale::id is required..

    template <class T>
    iter_type do_get_templ(iter_type in, iter_type end, std::ios_base& str,
                           std::ios_base::iostate& err, T& val) const
    {
        using namespace std;

        if(in == end)
        {
            err |= ios_base::eofbit;
            return in;
        }

        // leading white spaces have already been discarded by the
        // formatted input function (via sentry's constructor)

        // (assuming that) the sign, if present, has to be the first character
        // for the formatting required by the locale used for conversion

        // use the "C" locale; could use any locale, e.g. as a data member

        // note: the signedness check isn't actually required
        //       (because we only overload the unsigned versions)
        bool do_check = false;
        if(std::is_unsigned<T>{} && *in == '-')
        {
            ++in;  // not required
            do_check = true;
        }

        in = use_facet< num_get<charT, InputIterator> >(locale::classic())
                 .get(in, end, str, err, val);

        if(do_check && 0 != val)
        {
            err |= ios_base::failbit;
            val = 0;
        }

        return in;
    }
};

用法示例:

#include <sstream>
#include <iostream>
int main()
{
    std::locale loc( std::locale::classic(),
                     new num_get_strictsignedness<char>() );
    std::stringstream ss("-10");
    ss.imbue(loc);
    unsigned int ui = 42;
    ss >> ui;
    std::cout << "ui = "<<ui << std::endl;
    if(ss)
    {
        std::cout << "extraction succeeded" << std::endl;
    }else
    {
        std::cout << "extraction failed" << std::endl;
    }
}

筆記:

  • 不需要在免費存儲區上進行分配,您可以使用(靜態)局部變量,在其中將ctor中的ref計數器初始化為1
  • 對於要支持的每種字符類型(如charwchar_tcharXY_t ),都需要添加自己的構面(可以是num_get_strictsignedness模板的不同實例化)
  • 接受"-0"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM