Dlaczego #include <string> zapobiega tutaj błędowi przepełnienia stosu?


121

Oto mój przykładowy kod:

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

Jeśli wykomentuję, #include <string>nie otrzymam żadnego błędu kompilatora, myślę, że jest to rodzaj dołączenia #include <iostream>. Jeśli kliknę prawym przyciskiem myszy -> Idź do definicji w programie Microsoft VS, oba wskazują ten sam wiersz w xstringpliku:

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

Ale kiedy uruchamiam program, pojawia się błąd wyjątku:

0x77846B6E (ntdll.dll) w OperatorString.exe: 0xC00000FD: przepełnienie stosu (parametr: 0x00000001, 0x01202FC4)

Masz jakieś pojęcie, dlaczego podczas komentowania pojawia się błąd wykonania #include <string>? Używam VS 2013 Express.


4
Z łaską Bożą. działa idealnie na gcc, zobacz ideone.com/YCf4OI
— v78

czy wypróbowałeś Visual Studio z Visual C ++ i skomentowałeś include <string>?
— powietrzu

1
@cbuchart: Chociaż odpowiedź na to pytanie została już udzielona, ​​myślę, że jest to na tyle złożony temat, że posiadanie drugiej odpowiedzi w innych słowach jest cenne. Głosowałem za cofnięciem Twojej świetnej odpowiedzi.
— Wyścigi lekkości na orbicie

5
@Ruslan: W rzeczywistości tak. To znaczy, #include<iostream>i <string>oba mogą obejmować <common/stringimpl.h>.
— MSalters

3
W programie Visual Studio 2015 ...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowcl /EHsc main.cpp /Fetest.exe
— pojawia

Odpowiedzi:


161

Rzeczywiście, bardzo ciekawe zachowanie.

Każdy pomysł, dlaczego otrzymuję błąd w czasie wykonywania podczas komentowania #include <string>

W przypadku kompilatora MS VC ++ błąd występuje, ponieważ jeśli tego nie #include <string>zrobisz, nie operator<<zdefiniujesz dla std::string.

Kiedy kompilator próbuje skompilować ausgabe << f.getName();, szuka operator<<zdefiniowanego pliku std::string. Ponieważ nie zostało zdefiniowane, kompilator szuka alternatyw. Istnieje operator<<zdefiniowane for MyClassi kompilator próbuje go użyć, a aby go użyć, musi dokonać konwersji std::stringna MyClassi tak właśnie się dzieje, ponieważ MyClassma niejawny konstruktor! Tak więc kompilator kończy tworzenie nowej instancji Twojego MyClassi próbuje ponownie przesłać strumieniowo do strumienia wyjściowego. Powoduje to nieskończoną rekursję:

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

Aby uniknąć błędu, musisz #include <string>upewnić się, że operator<<zdefiniowano dla std::string. Powinieneś także MyClasswyraźnie określić konstruktor, aby uniknąć tego rodzaju nieoczekiwanej konwersji. Zasada mądrości: uczyń konstruktory jawnymi, jeśli przyjmują tylko jeden argument, aby uniknąć niejawnej konwersji:

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

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

To wygląda operator<<na std::stringdostanie zdefiniowane tylko wtedy, gdy <string>jest włączone (z kompilatora MS) iz tego powodu wszystko kompilacji, jednak można dostać nieco nieoczekiwane zachowanie jak operator<<jest uzyskiwanie nazwie rekurencyjnie dla MyClasszamiast dzwonić operator<<do std::string.

Czy to oznacza, że #include <iostream>string jest uwzględniany tylko częściowo?

Nie, ciąg jest w pełni uwzględniony, w przeciwnym razie nie byłbyś w stanie go użyć.


19
@airborne - To nie jest „problem związany z Visual C ++”, ale co może się stać, jeśli nie dołączysz odpowiedniego nagłówka. Podczas korzystania std::stringbez #include<string>wszelkiego rodzaju rzeczy może się zdarzyć, nie ograniczając się do błędu czasu kompilacji. Wywołanie niewłaściwej funkcji lub niewłaściwego operatora to najwyraźniej inna opcja.
— Bo Persson

15
Cóż, to nie jest „wywoływanie niewłaściwej funkcji lub niewłaściwego operatora”; kompilator robi dokładnie to, co mu kazałeś. Po prostu nie wiedziałeś, że kazałeś mu to zrobić;)
— Lightness Races in Orbit

18
Użycie typu bez dołączania odpowiedniego pliku nagłówkowego jest błędem. Kropka. Czy implementacja mogła ułatwić wykrycie błędu? Pewnie. Ale to nie jest „problem” z implementacją, to problem z napisanym przez Ciebie kodem.
— Cody Grey

4
Biblioteki standardowe mogą zawierać tokeny zdefiniowane w innym miejscu w std w sobie i nie muszą zawierać całego nagłówka, jeśli definiują jeden token.
— Yakk - Adam Nevraumont

5
Dość zabawne jest to, że grupa programistów C ++ argumentuje, że kompilator i / lub biblioteka standardowa powinny włożyć więcej pracy, aby im pomóc. Wdrożenie jest tutaj zgodne z jego prawami, zgodnie ze standardem, jak wielokrotnie podkreślano. Czy można użyć „oszustwa”, aby uczynić to bardziej oczywistym dla programisty? Jasne, ale moglibyśmy również napisać kod w Javie i całkowicie uniknąć tego problemu. Dlaczego MSVC ma uwidaczniać swoich wewnętrznych pomocników? Dlaczego nagłówek miałby przeciągać kilka zależności, których w rzeczywistości nie potrzebuje? To narusza całego ducha języka!
— Cody Grey

35

Problem polega na tym, że Twój kod wykonuje nieskończoną rekurencję. Operator przesyłania strumieniowego dla std::string( std::ostream& operator<<(std::ostream&, const std::string&)) jest zadeklarowany w <string>pliku nagłówkowym, chociaż std::stringsam jest zadeklarowany w innym pliku nagłówkowym (dołączonym przez oba <iostream>i <string>).

Jeśli nie <string>dołączasz kompilatora, kompilator próbuje znaleźć sposób na kompilację ausgabe << f.getName();.

Zdarza się, że zdefiniowałeś zarówno operator przesyłania strumieniowego dla, jak MyClassi konstruktor, który dopuszcza a std::string, więc kompilator używa go (poprzez niejawną konstrukcję ), tworząc wywołanie rekurencyjne.

Jeśli zadeklarujesz explicitswój konstruktor ( explicit MyClass(const std::string& s)), twój kod nie będzie już kompilowany, ponieważ nie ma możliwości wywołania operatora przesyłania strumieniowego za pomocą std::string, a będziesz zmuszony dołączyć <string>nagłówek.

EDYTOWAĆ

Moje środowisko testowe to VS 2010 i począwszy od poziomu ostrzeżenia 1 ( /W1) ostrzega Cię o problemie:

ostrzeżenie C4717: 'operator <<': rekurencja na wszystkich ścieżkach sterowania, funkcja spowoduje przepełnienie stosu w czasie wykonywania

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.