添加字符时,编译器停止优化未使用的字符串 [英] Compiler stops optimizing unused string away when adding characters

查看:101
本文介绍了添加字符时,编译器停止优化未使用的字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我很好奇以下代码为何:

I am curious why the following piece of code:

#include <string>
int main()
{
    std::string a = "ABCDEFGHIJKLMNO";
}

当使用 -O3 产生以下代码:

main:                                   # @main
    xor     eax, eax
    ret

(我完全理解,不需要未使用的 a ,以便编译器可以完全从生成的代码中忽略它)

(I perfectly understand that there is no need for the unused a so the compiler can entirely omit it from the generated code)

但是以下程序:

#include <string>
int main()
{
    std::string a = "ABCDEFGHIJKLMNOP"; // <-- !!! One Extra P 
}

收益率:

main:                                   # @main
        push    rbx
        sub     rsp, 48
        lea     rbx, [rsp + 32]
        mov     qword ptr [rsp + 16], rbx
        mov     qword ptr [rsp + 8], 16
        lea     rdi, [rsp + 16]
        lea     rsi, [rsp + 8]
        xor     edx, edx
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
        mov     qword ptr [rsp + 16], rax
        mov     rcx, qword ptr [rsp + 8]
        mov     qword ptr [rsp + 32], rcx
        movups  xmm0, xmmword ptr [rip + .L.str]
        movups  xmmword ptr [rax], xmm0
        mov     qword ptr [rsp + 24], rcx
        mov     rax, qword ptr [rsp + 16]
        mov     byte ptr [rax + rcx], 0
        mov     rdi, qword ptr [rsp + 16]
        cmp     rdi, rbx
        je      .LBB0_3
        call    operator delete(void*)
.LBB0_3:
        xor     eax, eax
        add     rsp, 48
        pop     rbx
        ret
        mov     rdi, rax
        call    _Unwind_Resume
.L.str:
        .asciz  "ABCDEFGHIJKLMNOP"

当使用相同的 -O3 进行编译时。我不明白为什么它不认识到 a 仍未使用,无论字符串长了一个字节如何。

when compiled with the same -O3. I don't understand why it does not recognize that the a is still unused, regardless that the string is one byte longer.

此问题与gcc 9.1和clang 8.0有关,(在线: https://gcc.godbolt .org / z / p1Z8Ns ),因为在我看来,其他编译器要么完全丢弃未使用的变量(ellcc),要么为该变量生成代码,而不管字符串的长度如何。

This question is relevant to gcc 9.1 and clang 8.0, (online: https://gcc.godbolt.org/z/p1Z8Ns) because other compilers in my observation either entirely drop the unused variable (ellcc) or generate code for it regardless the length of the string.

推荐答案

这是由于小字符串优化所致。当字符串数据少于或等于16个字符(包括空终止符)时,它将存储在 std :: string 对象本身局部的缓冲区中。否则,它将在堆上分配内存并将数据存储在堆上。

This is due to the small string optimization. When the string data is less than or equal 16 characters, including the null terminator, it is stored in a buffer local to the std::string object itself. Otherwise, it allocates memory on the heap and stores the data over there.

第一个字符串 ABCDEFGHIJKLMNO 空终止符的大小正好为16。添加 P 使其超过缓冲区,因此将调用 new 在内部,不可避免地导致系统调用。如果可以确保没有副作用,编译器可以优化一些东西。系统调用可能无法做到这一点-通过进行约束,更改正在构造的对象的本地缓冲区可以进行这种副作用分析。

The first string "ABCDEFGHIJKLMNO" plus the null terminator is exactly of size 16. Adding "P" makes it exceed the buffer, hence new is being called internally, inevitably leading to a system call. The compiler can optimize something away if it's possible to ensure that there are no side effects. A system call probably makes it impossible to do this - by constrast, changing a buffer local to the object under construction allows for such a side effect analysis.

跟踪本地缓冲区在libstdc ++版本9.1中,显示了 bits / basic_string.h 的以下部分:

Tracing the local buffer in libstdc++, version 9.1, reveals these parts of bits/basic_string.h:


template<typename _CharT, typename _Traits, typename _Alloc>
class basic_string
{
   // ...

  enum { _S_local_capacity = 15 / sizeof(_CharT) };

  union
    {
      _CharT           _M_local_buf[_S_local_capacity + 1];
      size_type        _M_allocated_capacity;
    };
   // ...
 };


这可以让您发现本地缓冲区的大小 _S_local_capacity 和本地缓冲区本身( _M_local_buf )。当构造函数触发 basic_string :: _ M_construct 被调用时,您具有 bits / basic_string.tcc

which lets you spot the local buffer size _S_local_capacity and the local buffer itself (_M_local_buf). When the constructor triggers basic_string::_M_construct being called, you have in bits/basic_string.tcc:


void _M_construct(_InIterator __beg, _InIterator __end, ...)
{
  size_type __len = 0;
  size_type __capacity = size_type(_S_local_capacity);

  while (__beg != __end && __len < __capacity)
  {
    _M_data()[__len++] = *__beg;
    ++__beg;
  }


其中的本地缓冲区填充有内容。在这部分之后,我们到达耗尽本地容量的分支-分配了新的存储(通过 M_create 中的分配),将本地缓冲区复制到新的存储并填充其余的初始化参数:

where the local buffer is filled with its content. Right after this part, we get to the branch where the local capacity is exhausted - new storage is allocated (through the allocate in M_create), the local buffer is copied into the new storage and filled with the rest of the initializing argument:


  while (__beg != __end)
  {
    if (__len == __capacity)
      {
        // Allocate more space.
        __capacity = __len + 1;
        pointer __another = _M_create(__capacity, __len);
        this->_S_copy(__another, _M_data(), __len);
        _M_dispose();
        _M_data(__another);
        _M_capacity(__capacity);
      }
    _M_data()[__len++] = *__beg;
    ++__beg;
  }


作为小注释,小字符串优化它本身就是一个话题。为了了解单个位的调整如何在很大程度上产生影响,我建议这个话题。它还提到了 gcc (libstdc ++)附带的 std :: string 实现是如何工作的,并在过去更改为匹配标准的较新版本。

As a side note, small string optimization is quite a topic on its own. To get a feeling for how tweaking individual bits can make a difference at large scale, I'd recommend this talk. It also mentions how the std::string implementation that ships with gcc (libstdc++) works and changed during the past to match newer versions of the standard.

这篇关于添加字符时,编译器停止优化未使用的字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆