string::append的问题

在使用libcurl的过程中,按照教程使用string类型进行数据接收:

1
2
3
4
5
6
size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp)
{
auto sz = size * nmemb;
((string*)userp)->append((char*)buffer, 0, sz);
return sz;
}

但是在测试过程中,接收图片时却总是出现问题,收到的数据远小于原始数据。

于是编写测试代码:

1
2
3
4
5
6
7
8
void test_append()
{
char buff[1024];
for(int i = 0; i < 1024; ++i)
buff[i] = rand() % 256;
cout << "append(n) : " << string().append(buff, 1024).size() << endl;
cout << "append(p, n): " << string().append(buff, 0, 1024).size() << endl;
}

得到输出:

1
2
append(n)   : 1024
append(p, n): 131

调试发现,buff[131]为0。

进一步编写测试:

1
2
3
4
5
6
7
8
9
void test_append()
{
const char* str = "0\000123456789";
const int sz = 12;
cout << "append(n) : " << string().append(str, sz).size() << endl;
cout << "append(p, n): " << string().append(str, 0, sz).size() << endl;
cout << "append(s, n): " << string().append(string(str, sz)).size() << endl;
cout << "append(s) : " << string().append(string(str)).size() << endl;
}

得到输出:

1
2
3
4
append(n)   : 12
append(p, n): 1
append(s, n): 12
append(s) : 1

那么可以大致猜测,调用append(str, 0, sz)string(str)时,将str作为\0结尾的字符串进行了截断。
翻看头文件进行验证。

首先是调用string().append(buff, 1024)时,实际上是调用了这个函数:

1
2
3
4
5
6
basic_string& append(const _CharT* __s, size_type __n)
{
__glibcxx_requires_string_len(__s, __n);
_M_check_length(size_type(0), __n, "basic_string::append");
return _M_append(__s, __n);
}

而调用string().append(buff, 0, 1024)时,则是调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
basic_string& append(const basic_string& __str, size_type __pos, size_type __n = npos)
{
return _M_append(__str._M_data()
+ __str._M_check(__pos, "basic_string::append"),
__str._M_limit(__pos, __n));
}
size_type _M_check(size_type __pos, const char* __s) const
{
if (__pos > this->size())
__throw_out_of_range_fmt(__N("%s: __pos (which is %zu) > "
"this->size() (which is %zu)"),
__s, __pos, this->size());
return __pos;
}
size_type _M_limit(size_type __pos, size_type __off) const _GLIBCXX_NOEXCEPT
{
const bool __testoff = __off < this->size() - __pos;
return __testoff ? __off : this->size() - __pos;
}

可以注意到,这里原本const char*的实参实际上是对应了const basic_string& __str,也就是说,先调用了string(str)构造了一个临时的string对象。数据就是在这里被截断的。

string(const char*)的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
basic_string(const _CharT* __s, const _Alloc& __a = _Alloc()) : _M_dataplus(_M_local_data(), __a)
{
_M_construct(__s, __s ? __s + traits_type::length(__s) : __s+npos);
}

template<typename _CharT, typename _Traits, typename _Alloc>
void basic_string<_CharT, _Traits, _Alloc>::_M_construct(size_type __n, _CharT __c)
{
if (__n > size_type(_S_local_capacity))
{
_M_data(_M_create(__n, size_type(0)));
_M_capacity(__n);
}

if (__n)
this->_S_assign(_M_data(), __n, __c);

_M_set_length(__n);
}

如果不是遇到问题,平时还真不一定能注意到这些细节。总之,多学习多记录吧。

最后,我的测试环境如下:

1
2
3
4
5
[spes@Gensoukyo bin]$ g++ --version
g++ (GCC) 10.1.0
Copyright © 2020 Free Software Foundation, Inc.
本程序是自由软件;请参看源代码的版权声明。本软件没有任何担保;
包括没有适销性和某一专用目的下的适用性担保。