Tại sao (chỉ) một số trình biên dịch sử dụng cùng một địa chỉ cho các ký tự chuỗi giống hệt nhau?


92

https://godbolt.org/z/cyBiWY

Tôi có thể thấy hai 'some'ký tự trong mã trình hợp dịch do MSVC tạo ra, nhưng chỉ có một ký tự với clang và gcc. Điều này dẫn đến kết quả thực thi mã hoàn toàn khác nhau.

static const char *A = "some";
static const char *B = "some";

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

Bất cứ ai có thể giải thích sự khác biệt và tương đồng giữa những đầu ra biên dịch? Tại sao clang / gcc tối ưu hóa thứ gì đó ngay cả khi không yêu cầu tối ưu hóa? Đây có phải là một số loại hành vi không xác định?

Tôi cũng nhận thấy rằng nếu tôi thay đổi các khai báo thành những khai báo được hiển thị bên dưới, clang / gcc / msvc hoàn toàn không để lại bất kỳ khai báo nào "some"trong mã trình hợp dịch. Tại sao hành vi lại khác nhau?

static const char A[] = "some";
static const char B[] = "some";

4
stackoverflow.com/a/52424271/1133179 Một số câu trả lời có liên quan hay cho một câu hỏi có liên quan chặt chẽ với các trích dẫn tiêu chuẩn.
luk32


6
Đối với MSVC, tùy chọn trình biên dịch / GF kiểm soát hành vi này. Xem docs.microsoft.com/en-us/cpp/build/reference/…
Sjoerd

1
FYI, điều này cũng có thể xảy ra đối với các chức năng.
dùng541686

Câu trả lời:


109

Đây không phải là hành vi không xác định, mà là hành vi không xác định. Đối với các ký tự chuỗi ,

Trình biên dịch được phép, nhưng không bắt buộc, kết hợp lưu trữ cho các ký tự chuỗi bằng nhau hoặc chồng chéo. Điều đó có nghĩa là các ký tự chuỗi giống hệt nhau có thể so sánh bằng hoặc không khi so sánh bằng con trỏ.

Điều đó có nghĩa là kết quả của A == Bcó thể là truehoặc false, mà bạn không nên phụ thuộc vào.

Từ tiêu chuẩn, [lex.string] / 16 :

Cho dù tất cả các ký tự chuỗi là khác biệt (nghĩa là, được lưu trữ trong các đối tượng không trùng lặp) và liệu các đánh giá liên tiếp của một chuỗi ký tự mang lại giống nhau hay một đối tượng khác là không xác định.


36

Các câu trả lời khác giải thích tại sao bạn không thể mong đợi các địa chỉ con trỏ khác nhau. Tuy nhiên, bạn có thể dễ dàng viết lại điều này theo cách đảm bảo rằng ABkhông so sánh ngang bằng:

static const char A[] = "same";
static const char B[] = "same";// but different

void f() {
    if (A == B) {
        throw "Hello, string merging!";
    }
}

Sự khác biệt là ABbây giờ là các mảng ký tự. Điều này có nghĩa là chúng không phải là con trỏ và địa chỉ của chúng phải khác biệt giống như địa chỉ của hai biến số nguyên. C ++ nhầm lẫn điều này vì nó làm cho con trỏ và mảng có vẻ có thể hoán đổi cho nhau ( operator*operator[]dường như hoạt động giống nhau), nhưng chúng thực sự khác nhau. Ví dụ, một cái gì đó như const char *A = "foo"; A++;là hoàn toàn hợp pháp, nhưng const char A[] = "bar"; A++;không phải.

Một cách để suy nghĩ về sự khác biệt là char A[] = "..."nói "cho tôi một khối bộ nhớ và điền vào nó với các ký tự ...theo sau \0", trong khi char *A= "..."nói "cho tôi một địa chỉ mà tôi có thể tìm thấy các ký tự ...theo sau \0".


8
Đây sẽ là một câu trả lời thậm chí còn tốt hơn nếu bạn có thể giải thích tại sao nó khác.
Mark Ransom

Lưu ý rằng *pp[0]không chỉ "có vẻ hoạt động giống nhau" mà theo định nghĩa giống hệt nhau (với điều kiện p+0 == plà quan hệ nhận dạng vì 0là phần tử trung lập trong phép cộng con trỏ-số nguyên). Sau khi tất cả, p[i]được định nghĩa là *(p+i). Câu trả lời làm cho một điểm tốt mặc dù.
Peter - Phục hồi Monica

typeof(*p)typeof(p[0])cả hai charnên thực sự không còn nhiều điều có thể khác nhau. Tôi đồng ý rằng 'dường như hành xử giống nhau' không phải là cách diễn đạt tốt nhất, bởi vì ngữ nghĩa rất khác nhau. Bài đăng của bạn nhắc nhở tôi về cách tốt nhất để các yếu tố tiếp cận của mảng C ++: 0[p], 1[p], 2[p]vv Đây là cách thuận làm điều đó, ít nhất là khi họ muốn nhầm lẫn giữa những người được sinh ra sau khi ngôn ngữ lập trình C.
tobi_s


Điều này thật thú vị, và tôi đã muốn thêm một liên kết đến Câu hỏi thường gặp về C, nhưng tôi nhận ra rằng có rất nhiều câu hỏi liên quan, nhưng dường như không có câu hỏi nào đi đúng vào trọng tâm của câu hỏi này ở đây.
tobi_s

23

Trình biên dịch có chọn sử dụng cùng một vị trí chuỗi hay không ABtùy thuộc vào việc triển khai. Về mặt hình thức, bạn có thể nói rằng hành vi của mã của bạn là không xác định .

Cả hai lựa chọn đều thực hiện đúng tiêu chuẩn C ++.


Hành vi của mã là đưa ra một ngoại lệ hoặc không làm gì, đã chọn, trước lần đầu tiên mã được thực thi, theo kiểu không xác định . Điều đó không có nghĩa là toàn bộ hành vi là không xác định - chỉ đơn thuần là trình biên dịch có thể chọn một trong hai hành vi theo bất kỳ cách nào mà nó thấy phù hợp trước lần đầu tiên hành vi được quan sát.
supercat

3

Đó là một sự tối ưu hóa để tiết kiệm không gian, thường được gọi là "chuỗi gộp". Đây là tài liệu cho MSVC:

https://msdn.microsoft.com/en-us/library/s0s0asdt.aspx

Do đó, nếu bạn thêm / GF vào dòng lệnh, bạn sẽ thấy hành vi tương tự với MSVC.

Nhân tiện, bạn có thể không nên so sánh các chuỗi thông qua con trỏ như vậy, bất kỳ công cụ phân tích tĩnh nào tốt sẽ gắn cờ mã đó là lỗi. Bạn cần so sánh những gì chúng trỏ tới, không phải các giá trị con trỏ thực tế.

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.