当前位置: 首页 > 知识库问答 >
问题:

编译器在添加字符时停止优化未使用的字符串

通和裕
2023-03-14

我很好奇为什么下面的代码:

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

当使用-O3编译时,生成以下代码:

main:                                   # @main
    xor     eax, eax
    ret
#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仍未使用,而不管字符串长了一个字节。

这个问题与GCC9.1和CLANG8.0(在线:https://gcc.godbolt.org/z/p1z8ns)相关,因为在我看来,其他编译器要么完全删除未使用的变量(ellcc),要么为其生成代码,而不管字符串的长度如何。

共有1个答案

伊锦
2023-03-14

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

第一个字符串“abcdefghijklmno”加上空结束符的大小正好是16。添加“p”使其超出缓冲区,因此在内部调用new,不可避免地导致系统调用。如果有可能确保没有副作用,编译器可以优化一些内容。一个系统调用可能使它不可能做到这一点-通过约束,改变构建中的对象的本地缓冲区允许这样的副作用分析。

在libstdc++++9.1版中跟踪本地缓冲区可以显示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;
    };
   // ...
 };
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;
  }
  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实现是如何工作的,并在过去进行了更改,以匹配标准的新版本。

 类似资料:
  • 问题内容: 当我使用null字符时,stringbuilder将停止添加新元素。 例如: 退货 我知道不应打印null值(或像打印null字符串值一样打印),但是在这种情况下,为什么stringbuilder无法添加更多元素? 注意:我在ubuntu上使用jdk 1.6.0_38。 问题答案: 空字符是一个保留字符,它指示字符串的结尾,因此,如果您打印后跟的内容,则在此之后将不打印其他任何内容。

  • 问题内容: 我目前正在翻译中编写一个针对Java字节码的玩具编译器。 我想知道是否可以在编写.class文件之前在发出的字节码中进行各种简单的窥孔优化的目录,也许是摘要。我实际上知道一些具有此功能的库,但是我想自己实现。 问题答案: 您知道Proguard吗?http://proguard.sourceforge.net/ 这是一个很棒的字节码优化器,它实现了很多优化。请参阅常见问题解答以获取列表

  • 问题内容: 因此,我想在字符串中添加一个字符,在某些情况下,我想将该字符加倍,然后将其添加到字符串中(即先添加到字符串本身)。我尝试如下所示。 这引发了一个错误,但是我已经在字符串中添加了一个字符,所以我尝试了: 哪个有效。为什么在求和中包含字符串会导致它起作用?是否添加了一个字符串属性,该字符串属性由于存在字符串而只能在字符转换为字符串时才能使用? 问题答案: 这是因为String + Char

  • 我最近遇到了这个精彩的cpp2015演讲cppCon 2015:钱德勒·卡鲁斯“调整C:基准、CPU和编译器!哦,天哪!” 提到的防止编译器优化代码的技术之一是使用以下函数。 我在努力理解这一点。问题如下。 1)逃避比重击有什么好处? 2) 从上面的例子来看,clobber()似乎可以防止前面的语句(push_back)以这种方式进行优化。如果是这样,为什么下面的代码片段不正确? 如果这还不够混乱

  • 以下代码: 给出编译器错误: 此行有多个标记-类型列表中的方法add(capture#1-of?extends String)不适用于参数(String)-方法add(capture#1-of?)类型列表中的不适用于参数(字符串) 是什么导致了这个错误?我应该不能添加Strings或它的子类型,因为我正在类型参数中扩展String吗?

  • 看完这些讨论——问题1,问题2,文章 我对Java字符串常量池有以下理解(如果我错了,请纠正我): 编译源代码时,编译器会在我们的程序中查找所有字符串文字(放在双引号中的那些),并在堆区域中创建不同的(无重复)对象,并在称为字符串常量池(方法区域内的区域)的特殊内存区域中维护它们的引用。任何其他字符串对象都是在运行时创建的。 假设我们的代码有以下语句: 当编译上述代码时, 第1行:在堆中创建一个S