与字符串字面量的比较未在编译时解决

8

我最近发现了类似以下的代码:

#include <string>

// test if the extension is either .bar or .foo
bool test_extension(const std::string& ext) {
    return ext == ".bar" || ".foo";
    // it obviously should be
    // return ext == ".bar" || ext == ".foo";
}

这个函数显然没有按照注释所建议的那样执行。但这不是重点。请注意,这不是Can you use 2 or more OR conditions in an if statement?的重复,因为我完全知道如何正确编写函数!


我开始想知道编译器如何处理这个代码片段。我的第一反应是,这基本上会被编译为return true;。将示例插入到godbolt中,显示GCC 9.2和clang 9都不使用优化-O2进行此优化。

但是,将代码更改为1

#include <string>

using namespace std::string_literals;

bool test_extension(const std::string& ext) {
    return ext == ".bar"s || ".foo";
}

似乎做到了目的,因为装配现在本质上是:

mov     eax, 1
ret

我的核心问题是:我是否忽略了什么,以致于编译器无法对第一个代码片段进行相同的优化?


1使用".foo"s将无法编译,因为编译器不希望将std::string转换为bool;-)


编辑

以下代码片段也被“正确地”优化为return true;

#include <string>

bool test_extension(const std::string& ext) {
    return ".foo" || ext == ".bar";
}

3
“string::compare(const char *)”这个函数是否有一些编译器无法消除的副作用,而“operator==(string, string)”却没有这些副作用?虽然看起来不太可能,但是即使对于第一个片段,编译器已经确定结果始终为真(还有“mov eax, 1”“ret”),所以这似乎并不确定。 - Max Langhof
2
也许是因为 operator==(string const&, string const&)noexcept 的,而 operator==(string const&, char const*) 不是?我现在没有时间深入挖掘。 - AProgrammer
@MaxLanghof 如果我没记错的话,有人在(现已删除的)评论中提到短路是运行时的事情。这让我重新表述了问题的某些部分。你认为他们在这方面是错误的吗? - AlexV
2
@AlexV 我不确定那是什么意思。对于表达式 a || b 的短路运算,意味着“只有在表达式 afalse 时才计算表达式 b”。这与运行时或编译时无关。即使 foo() 具有副作用,true || foo() 也可以被优化为 true,因为(无论是否被优化)右侧都不会被计算。但是,除非编译器能够证明调用 foo() 没有可观察的副作用,否则 foo() || true 不能被优化为 true - Max Langhof
1
当我使用您提供的Compiler Explorer链接并勾选“编译为二进制文件并反汇编输出”选项时,它会突然编译成xor eax,eax,即使没有该选项,它也会调用字符串比较函数。我不知道该怎么解释这个现象。 - Daniel H
显示剩余6条评论
1个回答

3

这将更加令人费解: 如果我们创建一个自定义字符类型MyCharT并使用它来制作我们自己的定制std::basic_string,会发生什么?

#include <string>

struct MyCharT {
    char c;
    bool operator==(const MyCharT& rhs) const {
        return c == rhs.c;
    }
    bool operator<(const MyCharT& rhs) const {
        return c < rhs.c;
    }
};
typedef std::basic_string<MyCharT> my_string;

bool test_extension_custom(const my_string& ext) {
    const MyCharT c[] = {'.','b','a','r', '\0'};
    return ext == c || ".foo";
}

// Here's a similar implementation using regular
// std::string, for comparison
bool test_extension(const std::string& ext) {
    const char c[] = ".bar";
    return ext == c || ".foo";
}

当然,自定义类型比普通的char更容易进行优化,对吧?
以下是生成的汇编代码:
test_extension_custom(std::__cxx11::basic_string<MyCharT, std::char_traits<MyCharT>, std::allocator<MyCharT> > const&):
        mov     eax, 1
        ret
test_extension(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&):
        sub     rsp, 24
        lea     rsi, [rsp+11]
        mov     DWORD PTR [rsp+11], 1918984750
        mov     BYTE PTR [rsp+15], 0
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(char const*) const
        mov     eax, 1
        add     rsp, 24
        ret

点击查看实际效果!


惊呆了!

那么,“自定义”字符串类型和std::string有什么区别呢?

小字符串优化

至少在GCC上,小字符串优化实际上已经编译进了libstdc++二进制文件中。这意味着,在编译您的函数时,编译器无法访问此实现,因此不能知道是否存在任何副作用。因此,它无法优化对compare(char const*)的调用。我们的“自定义”类没有这个问题,因为SSO仅针对纯粹的std::string实现。

顺便说一句,如果使用-std=c++2a编译,编译器会将其优化掉。不幸的是,我对C++ 20还不够熟悉,不知道是什么改变使这成为可能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接