So sánh với chuỗi ký tự không được giải quyết tại thời điểm biên dịch


8

Gần đây tôi đã tìm thấy một cái gì đó giống với các dòng sau:

#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";
}

Các chức năng rõ ràng không làm những gì bình luận cho thấy. Nhưng đó không phải là vấn đề ở đây. Xin lưu ý rằng đây không phải là bản sao của Bạn có thể sử dụng 2 hoặc nhiều điều kiện HOẶC trong câu lệnh if không? vì tôi hoàn toàn nhận thức được làm thế nào bạn sẽ viết đúng chức năng!


Tôi bắt đầu tự hỏi làm thế nào một trình biên dịch có thể xử lý đoạn trích này. Trực giác đầu tiên của tôi sẽ là điều này sẽ được biên soạn return true;về cơ bản. Việc đưa ví dụ vào godbolt , cho thấy cả GCC 9.2 và clang 9 đều không thực hiện tối ưu hóa này với tối ưu hóa -O2.

Tuy nhiên, thay đổi mã thành 1

#include <string>

using namespace std::string_literals;

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

dường như thực hiện mánh khóe kể từ khi lắp ráp về bản chất là:

mov     eax, 1
ret

Vì vậy, câu hỏi cốt lõi của tôi là: Có điều gì tôi đã bỏ lỡ không cho phép trình biên dịch thực hiện tối ưu hóa tương tự trên đoạn mã đầu tiên không?


1 Với ".foo"sđiều này thậm chí sẽ không biên dịch, vì trình biên dịch không muốn chuyển đổi std::stringthành bool;-)


Biên tập

Đoạn mã sau đây cũng được tối ưu hóa "đúng" thành return true;:

#include <string>

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

3
Hừm, string::compare(const char*)có một số tác dụng phụ mà trình biên dịch sẽ không loại bỏ (điều operator==(string, string)đó không có)? Có vẻ như không thể, nhưng trình biên dịch đã xác định rằng kết quả luôn luôn đúng (cũng có mov eax, 1 ret) ngay cả đối với đoạn đầu tiên.
Max Langhof

2
Có lẽ vì operator==(string const&, string const&)noexcepttrong khi operator==(string const&, char const*)không phải là? Bây giờ tôi không có thời gian để đào thêm.
AProgrammer

@MaxLanghof Khi thay đổi thứ tự thành foo || ext == ".bar", cuộc gọi được tối ưu hóa đi (xem chỉnh sửa). Điều đó có mâu thuẫn với lý thuyết của bạn không?
AlexV

2
@AlexV Tôi không chắc điều đó có nghĩa là gì. Đoản mạch cho biểu thức a || bcó nghĩa là "chỉ đánh giá biểu thức bnếu biểu thức afalse". Đó là trực giao với thời gian chạy hoặc thời gian biên dịch. true || foo()có thể được tối ưu hóa true, ngay cả khi foo()có tác dụng phụ, bởi vì (dù có tối ưu hóa hay không), phía bên tay phải không bao giờ được đánh giá. Nhưng foo() || truekhông thể được tối ưu hóa truetrừ khi trình biên dịch có thể chứng minh rằng việc gọi foo()không có tác dụng phụ có thể quan sát được.
Max Langhof

1
Khi tôi lấy liên kết Compiler Explorer được cung cấp của bạn và kiểm tra tùy chọn "Biên dịch thành nhị phân và tháo rời đầu ra", nó đột nhiên được biên dịch xor eax,eaxngay cả khi không có tùy chọn đó, nó gọi hàm so sánh chuỗi. Tôi không có ý tưởng gì để làm điều đó.
Daniel H

Câu trả lời:


3

Điều này sẽ làm vấy bẩn đầu của bạn hơn nữa: Điều gì xảy ra nếu chúng ta tạo một loại char tùy chỉnh MyCharTvà sử dụng nó để tạo tùy chỉnh của riêng mình 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";
}

Chắc chắn, một loại tùy chỉnh không thể được tối ưu hóa dễ dàng hơn một loại đơn giản char, phải không?

Đây là hội nghị kết quả:

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

Xem nó trực tiếp!


Tâm trí!

Vậy, sự khác biệt giữa loại chuỗi "tùy chỉnh" của tôi và là std::stringgì?

Tối ưu hóa chuỗi nhỏ

Ít nhất là trên GCC, Tối ưu hóa chuỗi nhỏ thực sự được biên dịch thành nhị phân cho libstdc ++. Điều này có nghĩa là, trong quá trình biên dịch hàm của bạn, trình biên dịch không có quyền truy cập vào việc triển khai này, do đó, nó không thể biết liệu có bất kỳ tác dụng phụ nào không. Bởi vì điều này, nó không thể tối ưu hóa cuộc gọi compare(char const*)đi. Lớp "tùy chỉnh" của chúng tôi không gặp phải vấn đề này vì SSO chỉ được triển khai cho đơn giản std::string.

BTW, nếu bạn biên dịch với -std=c++2a, trình biên dịch sẽ tối ưu hóa nó đi . Rất tiếc, tôi không đủ hiểu biết về C ++ 20 để biết những thay đổi nào có thể xảy ra.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.