std::string的find問題研究

一見發表於2018-12-19

https://download.csdn.net/download/aquester/10861914

目錄

目錄 1

1. 前言 1

2. find字串 1

3. find單個字元 2

4. 問題分析 3

4.1. gcc-4.1.2 3

4.2. gcc-4.8.2 4

5. a.cpp原始碼 5

6. 單個字元版本find原始碼 5

7. 字串版本find原始碼 6

7.1. gcc-4.1.2 6

7.2. gcc-4.8.2 6

8. 結論 7

 

1. 前言

一次偶然,發現完全同一份程式碼,在不同機器上find出現兩個不同執行結果,本文旨在研究find的“詭異”行為,找出背後的原因。

2. find字串

測試程式碼:

// g++ -g -o x x.cpp

#include <string>

#include <iostream>

 

extern "C" int main()

{

    std::string::size_type n = std::string::npos;

    std::string str = "123";

    std::string::size_type m = str.find("2"n); // 按照期望,m值應為npos

    std::cout << "n=" << n << ", m=" << m << std::endl;

    return 0;

}

 

i386輸出結果(gcc (GCC) 4.1.2):

n=4294967295, m=1

 

這裡m值為1,是一個非期望的值。

 

i86_64編譯成64位輸出結果(gcc (GCC) 4.8.5):

n=18446744073709551615, m=18446744073709551615

 

i86_64編譯成32位輸出結果(gcc (GCC) 4.8.5):

n=4294967295, m=4294967295

 

i386上編譯放到i86_64上執行的輸出結果:

n=4294967295, m=4294967295

 

i386上編譯成共享庫後放到i86_64上執行的輸出結果:

// g++ -g -o libx.so -fPIC -shared x.cpp

n=4294967295, m=4294967295

3. find單個字元

測試程式碼:

// g++ -g -o x x.cpp

#include <string>

#include <iostream>

 

extern "C" int main()

{

    std::string::size_type n = std::string::npos;

    std::string str = "123";

    std::string::size_type m = str.find('2', n);

    std::cout << "n=" << n << ", m=" << m << std::endl;

    return 0;

}

 

i386輸出結果(gcc (GCC) 4.1.2):

n=4294967295, m=4294967295

 

i86_64編譯成64位輸出結果(gcc (GCC) 4.8.5):

n=18446744073709551615, m=18446744073709551615

 

i86_64編譯成32位輸出結果(gcc (GCC) 4.8.5):

n=4294967295, m=4294967295

 

i386上編譯放到i86_64上執行的輸出結果:

n=4294967295, m=4294967295

 

i386上編譯成共享庫後放到i86_64上執行的輸出結果:

n=4294967295, m=4294967295

4. 問題分析

對於字串版本的find,出現不同的結果。小技巧:加上編譯選項“-D_GLIBCXX_DEBUG”,方可DEBUG進入find。

4.1. gcc-4.1.2

以下為i386環境。

g++ -g -o x x.cpp -D_GLIBCXX_DEBUG

Breakpoint 2, main () at x.cpp:6

6           std::string::size_type n = std::string::npos;

(gdb) n

7           std::string str = "123";

(gdb) 

8           std::string::size_type m = str.find("2", n);

(gdb) s

std::string::find (this=0xbfb54a10, __s=0x804b8f2 "2", __pos=4294967295) at /usr/include/c++/4.1.2/bits/basic_string.h:1579

1579            return this->find(__s, __pos, traits_type::length(__s));

(gdb) s

std::char_traits<char>::length (__s=0x804b8f2 "2") at /usr/include/c++/4.1.2/bits/char_traits.h:257

257           { return strlen(__s); }

(gdb) finish

Run till exit from #0  std::char_traits<char>::length (__s=0x804b8f2 "2") at /usr/include/c++/4.1.2/bits/char_traits.h:257

0x0804a8d9 in std::string::find (this=0xbfb54a10, __s=0x804b8f2 "2", __pos=4294967295) at /usr/include/c++/4.1.2/bits/basic_string.h:1579

1579            return this->find(__s, __pos, traits_type::length(__s));

Value returned is $1 = 1

(gdb) s

std::string::find (this=0xbfb54a10, __s=0x804b8f2 "2", __pos=4294967295, __n=1) at /usr/include/c++/4.1.2/bits/basic_string.tcc:721

721           size_type __ret = npos;

723           if (__pos + __n <= __size)

(gdb) p __pos

$2 = 4294967295

(gdb) p __n

$3 = 1

(gdb) p __size

$4 = 3

(gdb) p __pos + __n

$5 = 0

4.2. gcc-4.8.2

以下為x86_64環境。

// g++ -g -o x x.cpp -m32 -D_GLIBCXX_DEBUG

Breakpoint 1, main () at x.cpp:6

6           std::string::size_type n = std::string::npos;

Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.tl2.3.i686 libgcc-4.8.5-4.el7.i686 libstdc++-4.8.5-4.el7.i686

(gdb) n

7           std::string str = "123";

(gdb) 

8           std::string::size_type m = str.find("2", n);

(gdb) s

std::string::find (this=0xffffd300, __s=0x80499d8 "2", __pos=4294967295) at /usr/include/c++/4.8.2/bits/basic_string.h:1864

1864            return this->find(__s, __pos, traits_type::length(__s));

(gdb) s

std::char_traits<char>::length (__s=0x80499d8 "2") at /usr/include/c++/4.8.2/bits/char_traits.h:259

259           { return __builtin_strlen(__s); }

(gdb) finish

Run till exit from #0  std::char_traits<char>::length (__s=0x80499d8 "2") at /usr/include/c++/4.8.2/bits/char_traits.h:259

0x0804931f in std::string::find (this=0xffffd300, __s=0x80499d8 "2", __pos=4294967295) at /usr/include/c++/4.8.2/bits/basic_string.h:1864

1864            return this->find(__s, __pos, traits_type::length(__s));

Value returned is $1 = 1

(gdb) s

std::string::find (this=0xffffd300, __s=0x80499d8 "2", __pos=4294967295, __n=1) at /usr/include/c++/4.8.2/bits/basic_string.tcc:740

740           const size_type __size = this->size();

5. a.cpp原始碼

// g++ -g -o a a.cpp -ldl -m32

#include <dlfcn.h>

#include <stdio.h>

 

int main()

{

    typedef int (*X)();

    

    void* p = dlopen("./libx.so", RTLD_NOW);

    if (!p)

        printf("dlopen failed: %s\n", dlerror());

    else {

        X x = (X)dlsym(p, "main");

        (*x)();

    }

    return 0;

}

6. 單個字元版本find原始碼

gcc-4.1.2版本的find原始碼,gcc-4.8.2的實現相同。

// basic_string.tcc

template<typename _CharT, typename _Traits, typename _Alloc>

typename basic_string<_CharT, _Traits, _Alloc>::size_type

basic_string<_CharT, _Traits, _Alloc>::

find(_CharT __c, size_type __pos) const _GLIBCXX_NOEXCEPT

{

    size_type __ret = npos;

    const size_type __size = this->size();

 

    if (__pos < __size)

    {

        const _CharT* __data = _M_data();

        const size_type __n = __size - __pos;

        const _CharT* __p = traits_type::find(__data + __pos, __n, __c);

        if (__p)

            __ret = __p - __data;

    }

 

    return __ret;

}

7. 字串版本find原始碼

7.1. gcc-4.1.2

// /usr/include/c++/4.1.2/bits/basic_string.h

1575       size_type

1576       find(const _CharT* __s, size_type __pos = 0) const

1577       {

1578     __glibcxx_requires_string(__s);

1579     return this->find(__s, __pos, traits_type::length(__s));

1580       }

 

// /usr/include/c++/4.1.2/bits/basic_string.tcc

715   template<typename _CharT, typename _Traits, typename _Alloc>

716     typename basic_string<_CharT, _Traits, _Alloc>::size_type

717     basic_string<_CharT, _Traits, _Alloc>::

718     find(const _CharT* __s, size_type __pos, size_type __n) const

719     {

720       __glibcxx_requires_string_len(__s, __n);

721       size_type __ret = npos;

722       const size_type __size = this->size();

723       if (__pos + __n <= __size) // 這裡溢位

724     {

725       const _CharT* __data = _M_data();

726       const _CharT* __p = std::search(__data + __pos, __data + __size,

727                       __s, __s + __n, traits_type::eq);

728       if (__p != __data + __size || __n == 0)

729         __ret = __p - __data;

730     }

731       return __ret;

732     }

7.2. gcc-4.8.2

實現和gcc-4.1.2不同了,新的實現不存在溢位漏洞。

// /usr/include/c++/4.8.2/bits/basic_string.h

1860       size_type

1861       find(const _CharT* __s, size_type __pos = 0) const

1862       {

1863         __glibcxx_requires_string(__s);

1864         return this->find(__s, __pos, traits_type::length(__s));

1865       }

 

// /usr/include/c++/4.8.2/bits/basic_string.tcc

 734   template<typename _CharT, typename _Traits, typename _Alloc>

 735     typename basic_string<_CharT, _Traits, _Alloc>::size_type

 736     basic_string<_CharT, _Traits, _Alloc>::

 737     find(const _CharT* __s, size_type __pos, size_type __n) const

 738     {

 739       __glibcxx_requires_string_len(__s, __n);

 740       const size_type __size = this->size();

 741       const _CharT* __data = _M_data();

 742 

 743       if (__n == 0)

 744         return __pos <= __size ? __pos : npos;

 745 

 746       if (__n <= __size) // 這裡不存在溢位

 747         {

 748           for (; __pos <= __size - __n; ++__pos)

 749             if (traits_type::eq(__data[__pos], __s[0])

 750                 && traits_type::compare(__data + __pos + 1,

 751                                         __s + 1, __n - 1) == 0)

 752               return __pos;

 753         }

 754       return npos;

 755     }

8. 結論

一些低版本的find實現存在bug,存在溢位。注:std::string::size_type實際為size_t,是一個無符號整數型別,在i386上為4位元組無符號整數型別,在x86_84上為8位元組無符號整數型別,對應的有符號型別為ssize_t。

相關文章