Tại sao #include <string> lại ngăn lỗi tràn ngăn xếp ở đây?


121

Đây là mã mẫu của tôi:

#include <iostream>
#include <string>
using namespace std;

class MyClass
{
    string figName;
public:
    MyClass(const string& s)
    {
        figName = s;
    }

    const string& getName() const
    {
        return figName;
    }
};

ostream& operator<<(ostream& ausgabe, const MyClass& f)
{
    ausgabe << f.getName();
    return ausgabe;
}

int main()
{
    MyClass f1("Hello");
    cout << f1;
    return 0;
}

Nếu tôi nhận xét ra, #include <string>tôi không gặp bất kỳ lỗi trình biên dịch nào, tôi đoán là vì nó được đưa vào thông qua #include <iostream>. Nếu tôi "nhấp chuột phải -> Đi tới Định nghĩa" trong Microsoft VS thì cả hai đều trỏ đến cùng một dòng trong xstringtệp:

typedef basic_string<char, char_traits<char>, allocator<char> >
    string;

Nhưng khi tôi chạy chương trình của mình, tôi gặp lỗi ngoại lệ:

0x77846B6E (ntdll.dll) trong OperatorString.exe: 0xC00000FD: Tràn ngăn xếp (Tham số: 0x00000001, 0x01202FC4)

Bất kỳ ý tưởng tại sao tôi gặp lỗi thời gian chạy khi bình luận #include <string>? Tôi đang sử dụng VS 2013 Express.


4
Với ân sủng của chúa. hoạt động hoàn hảo trên gcc, xem ideone.com/YCf4OI
v78

bạn đã thử visual studio với visual c ++ và nhận xét bao gồm <string>?
trên không

1
@cbuchart: Mặc dù câu hỏi đã được trả lời, tôi nghĩ rằng đây là một chủ đề đủ phức tạp để có câu trả lời thứ hai bằng các từ khác nhau là có giá trị. Tôi đã bỏ phiếu để phục hồi câu trả lời tuyệt vời của bạn.
Các cuộc đua ánh sáng trong quỹ đạo vào

5
@Ruslan: Đúng là như vậy. Điều đó có nghĩa là, #include<iostream><string>cả hai có thể bao gồm <common/stringimpl.h>.
MSalters

3
Trong Visual Studio 2015, bạn nhận được cảnh báo ...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowkhi chạy dòng nàycl /EHsc main.cpp /Fetest.exe
CroCo

Câu trả lời:


161

Thật vậy, hành vi rất thú vị.

Bất kỳ ý kiến ​​nào tại sao tôi gặp lỗi thời gian chạy khi bình luận #include <string>

Với trình biên dịch MS VC ++, lỗi sẽ xảy ra bởi vì nếu không, #include <string>bạn sẽ không operator<<xác định được std::string.

Khi trình biên dịch cố gắng biên dịch, ausgabe << f.getName();nó sẽ tìm kiếm một operator<<định nghĩa cho std::string. Vì nó không được xác định, trình biên dịch sẽ tìm kiếm các lựa chọn thay thế. Có một operator<<định nghĩa cho MyClassvà trình biên dịch cố gắng sử dụng nó, và để sử dụng nó, nó phải chuyển đổi std::stringthành MyClassvà đây chính xác là những gì sẽ xảy ra vì MyClasscó một hàm tạo không rõ ràng! Vì vậy, trình biên dịch kết thúc việc tạo một phiên bản mới của bạn MyClassvà cố gắng phát trực tuyến lại vào luồng đầu ra của bạn. Điều này dẫn đến một đệ quy vô tận:

 start:
     operator<<(MyClass) -> 
         MyClass::MyClass(MyClass::getName()) -> 
             operator<<(MyClass) -> ... goto start;

Để tránh lỗi, bạn cần #include <string>đảm bảo rằng có một operator<<định nghĩa cho std::string. Ngoài ra, bạn nên làm cho hàm tạo của bạn MyClassrõ ràng để tránh loại chuyển đổi không mong muốn này. Quy tắc khôn ngoan: làm cho các hàm tạo rõ ràng nếu họ chỉ lấy một đối số để tránh chuyển đổi ngầm:

class MyClass
{
    string figName;
public:
    explicit MyClass(const string& s) // <<-- avoid implicit conversion
    {
        figName = s;
    }

    const string& getName() const
    {
        return figName;
    }
};

Có vẻ như operator<<for chỉ std::stringđược định nghĩa khi <string>được bao gồm (với trình biên dịch MS) và vì lý do đó mà mọi thứ được biên dịch, tuy nhiên bạn sẽ có hành vi hơi bất ngờ như operator<<được gọi đệ quy MyClassthay vì gọi operator<<cho std::string.

Điều đó có nghĩa là thông qua #include <iostream>chuỗi chỉ được bao gồm một phần?

Không, chuỗi được bao gồm đầy đủ, nếu không bạn sẽ không thể sử dụng nó.


19
@airborne - Đây không phải là "vấn đề cụ thể của Visual C ++", mà là điều gì có thể xảy ra khi bạn không bao gồm tiêu đề thích hợp. Khi sử dụng std::stringmà không có #include<string>tất cả các loại có thể xảy ra, không giới hạn ở lỗi thời gian biên dịch. Gọi sai chức năng hoặc toán tử rõ ràng là một lựa chọn khác.
Bo Persson

15
Đây không phải là "gọi sai hàm hoặc toán tử"; trình biên dịch đang thực hiện chính xác những gì bạn đã yêu cầu. Bạn chỉ không biết bạn đang nói với nó để làm điều này;)
Lightness Races ở Orbit

18
Sử dụng một loại mà không bao gồm tệp tiêu đề tương ứng của nó là một lỗi. Giai đoạn = Stage. Việc triển khai có làm cho lỗi dễ phát hiện hơn không? Chắc chắn rồi. Nhưng đó không phải là "vấn đề" với việc triển khai, đó là vấn đề với mã bạn đã viết.
Cody Grey

4
Các thư viện tiêu chuẩn được miễn phí bao gồm các mã thông báo được xác định ở nơi khác trong std bên trong chính chúng và không bắt buộc phải bao gồm toàn bộ tiêu đề nếu chúng xác định một mã thông báo.
Yakk - Adam Nevraumont

5
Có phần hài hước khi thấy một loạt các lập trình viên C ++ tranh cãi rằng trình biên dịch và / hoặc thư viện chuẩn nên làm nhiều việc hơn để giúp họ. Theo tiêu chuẩn, việc thực hiện nằm trong phạm vi quyền hạn của nó, như đã được chỉ ra nhiều lần. Có thể sử dụng "trickery" để làm cho điều này rõ ràng hơn cho các lập trình viên không? Chắc chắn rồi, nhưng chúng tôi cũng có thể viết mã bằng Java và tránh được vấn đề này hoàn toàn. Tại sao MSVC nên hiển thị các trình trợ giúp nội bộ của mình? Tại sao một tiêu đề phải kéo theo một loạt các phụ thuộc mà nó thực sự không cần? Điều đó vi phạm toàn bộ tinh thần của ngôn ngữ!
Cody Grey

35

Vấn đề là mã của bạn đang thực hiện một đệ quy vô hạn. Toán tử phát trực tuyến cho std::string( std::ostream& operator<<(std::ostream&, const std::string&)) được khai báo trong <string>tệp tiêu đề, mặc dù std::stringchính nó được khai báo trong tệp tiêu đề khác (được bao gồm bởi cả <iostream><string>).

Khi bạn không bao gồm <string>trình biên dịch cố gắng tìm cách biên dịch ausgabe << f.getName();.

Điều xảy ra là bạn đã xác định cả toán tử phát trực tuyến cho MyClassvà phương thức khởi tạo thừa nhận a std::string, vì vậy trình biên dịch sử dụng nó (thông qua cấu trúc ngầm định ), tạo ra một cuộc gọi đệ quy.

Nếu bạn khai báo hàm tạo explicitcủa mình ( explicit MyClass(const std::string& s)) thì mã của bạn sẽ không biên dịch nữa, vì không có cách nào để gọi toán tử phát trực tuyến bằng std::stringvà bạn sẽ buộc phải bao gồm <string>tiêu đề.

BIÊN TẬP

Môi trường thử nghiệm của tôi là VS 2010 và bắt đầu ở mức cảnh báo 1 ( /W1), nó cảnh báo bạn về sự cố:

cảnh báo C4717: 'operator <<': đệ quy trên tất cả các đường dẫn điều khiển, hàm sẽ gây tràn ngăn xếp thời gian chạy

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.