C++ 字符串拼接 源码研究

C++字符串拼接有多种方式,不同方式原理和性能会有一些差异,本文进行一个小小归纳对比,如有疏漏错误,欢迎指出。
本文源码解析使用glibc-2.32版本, C ++ 11。

C语言字符串拼接
1. 手工拼接操作
char *(char *dest, const char *src, size_t, n)
{
  // 保存头部指针
  char *s = dest;

  // 找到字符串末尾位置
  s1 += strlen (dest);

  // 计算拼接长度
  size_t ss = strnlen (src, n);

  // 末尾置 '\0'
  s1[ss] = '\0';
  
  // 复制字符串
  while (*s1++ !='\0')
  {
      *s1++ = *src++;
  }

  return s;
}
2. 使用strcat/strncat进行字符串拼接

在glibc中,strcat 使用了 strcpy 实现拼接

char *
STRCAT (char *dest, const char *src)
{
  strcpy (dest + strlen (dest), src);
  return dest;
}

strncat使用了 memcpy方法

char *
STRNCAT (char *s1, const char *s2, size_t n)
{
  char *s = s1;

  /* Find the end of S1.  */
  s1 += strlen (s1);

  size_t ss = __strnlen (s2, n);

  s1[ss] = '\0';
  memcpy (s1, s2, ss);

  return s;
}

strcpy也是调用memcpy,strcpy不需传入拷贝长度,内部通过strlen找到’\0’为结尾。

char *
STRCPY (char *dest, const char *src)
{
  return memcpy (dest, src, strlen (src) + 1);
}

在较早版本的glibc(至少2.11之前)中,strcpy使用类似手工方式进行了字符串拼接,以’\0’为结尾。
因此网上通常会看到memcpy与strcpy的区别说明:

a. 复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意类型的内容。strcpy只用于字符串复制,并且还会复制字符串的结束符。memcpy对于复制的内容没有限制,用途更广。
b. 复制的方法不同。strcpy不需要指定长度,遇到结束符’\0’才会结束,所以容易溢出。memcpy则是根据第三个参数决定复制的长度
c. 用途不同。通常在复制字符串时用strcpy,在复制其他类型数据时一般用memcpy。

// glibc 2.11
/* Copy SRC to DEST.  */
char *
strcpy (dest, src)
     char *dest;
     const char *src;
{
  reg_char c;
  char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src);
  const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1;
  size_t n;

  do
    {
      c = *s++;
      s[off] = c;
    }
  while (c != '\0');

  n = s - src;
  (void) CHECK_BOUNDS_HIGH (src + n);
  (void) CHECK_BOUNDS_HIGH (dest + n);

  return dest;
}

在glibc 2.32中,memcpy拼接有两种形式:

a. 长度小于16字节的内容,会通过数组赋值进行一个个字节拷贝
b. 对于大于16字节的内容,会进行对齐,整体内存虚拟页拷贝操作

memcpy的实现源码可以在memcpy.c中看到,也比较好理解,其中关于虚拟内存页拷贝也涉及一些对齐细节,此处省略相关代码内容。关于memcpy的细节可以看:
https://programmer.group/learning-notes-of-memcpy-source-code-under-glibc.html
https://blog.csdn.net/xiao_huocai/article/details/103198250

通常认为memcpy与strcpy的区别

3. 使用sprintf进行字符串拼接
printf(sFilePath, "%s/%s", "c://", "a.txt");

sprintf的源码实现较为复杂,暂时无法清晰捋出来,后续可以新开文章进一步学习。

C++语言字符串拼接
1. 使用+号或+=拼接
    std::string l_czTempStr;
    std::string s1="Test data1";
    std::string s2="Test data2";
    std::string s3="Test data3";
    l_czTempStr += s1 + s2 + s3;

其源码是对+号进行重载,调用append方法

  template<typename _CharT, typename _Traits, typename _Alloc>
    basic_string<_CharT, _Traits, _Alloc>
    operator+(const _CharT* __lhs,
	      const basic_string<_CharT, _Traits, _Alloc>& __rhs)
    {
      __glibcxx_requires_string(__lhs);
      typedef basic_string<_CharT, _Traits, _Alloc> __string_type;
      typedef typename __string_type::size_type	  __size_type;
      const __size_type __len = _Traits::length(__lhs);
      __string_type __str;
      __str.reserve(__len + __rhs.size());
      __str.append(__lhs, __len);
      __str.append(__rhs);
      return __str;
    }
2. 使用append拼接
    std::string l_czTempStr;
    std::string s1="Test data1";
    l_czTempStr.append(s1); 

append有四个重载方法,对应char*和string以及append范围限定

append(size_type __n, _CharT __c)
append(const _CharT* __s, size_type __n)
append(const basic_string& __str)
append(const basic_string& __str, size_type __pos, size_type __n)

append(const basic_string& __str) 方法源码:

  template<typename _CharT, typename _Traits, typename _Alloc>
    basic_string<_CharT, _Traits, _Alloc>&
    basic_string<_CharT, _Traits, _Alloc>::
    append(const basic_string& __str)
    {
      const size_type __size = __str.size();
      if (__size)
	{
	  const size_type __len = __size + this->size();
	  if (__len > this->capacity() || _M_rep()->_M_is_shared())
	    this->reserve(__len);
	  // 内存拷贝
	  _M_copy(_M_data() + this->size(), __str._M_data(), __size);
	  _M_rep()->_M_set_length_and_sharable(__len);
	}
      return *this;
    }  

可以看出append方法的具体行为(暂时不挖掘STL的string实现细节):

a. 首先计算了要拼接的长度,__size
b. __size对比当前拥有的内存可容纳的长度capacity(),如果空间不足,通过reverse方法请求更多的内存空间
c. 使用_M_copy进行内存拷贝
d. _M_rep进行内存空间设置

3. 使用stringstream拼接
    std::ostringstream oss;
    std::string s1="Test data1";
    oss << s1;
    l_czTempStr = oss.str();

stringstream源码也尚未吃透,坑留在这里

小结

此篇文章最初是想解惑,C++项目中字符串拼接使用什么方法更优,其原理、源码实现是什么。目前发现坑越挖越大,先尽量吃透其源码实现(还剩下很多坑),后续再总结其实现原理导致的性能不同问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值