Perché #include <stringa> impedisce un errore di overflow dello stack qui?


121

Questo è il mio codice di esempio:

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

Se commento fuori #include <string>non ricevo alcun errore del compilatore, immagino perché è un po 'incluso #include <iostream>. Se faccio "clic con il pulsante destro del mouse -> Vai a definizione" in Microsoft VS entrambi puntano alla stessa riga nel xstringfile:

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

Ma quando eseguo il mio programma, ottengo un errore di eccezione:

0x77846B6E (ntdll.dll) in OperatorString.exe: 0xC00000FD: overflow dello stack (parametro: 0x00000001, 0x01202FC4)

Qualche idea sul perché ricevo un errore di runtime quando commento #include <string>? Sto usando VS 2013 Express.


4
Con la grazia di Dio. funziona perfettamente su gcc, vedi ideone.com/YCf4OI
v78

hai provato Visual Studio con Visual C ++ e il commento include <string>?
volo

1
@cbuchart: Sebbene la domanda abbia già avuto una risposta, penso che questo sia un argomento abbastanza complesso che avere una seconda risposta in parole diverse è prezioso. Ho votato per annullare l'eliminazione della tua ottima risposta.
Gare di leggerezza in orbita

5
@Ruslan: Effettivamente, lo sono. Vale a dire, #include<iostream>e <string>potrebbero includere entrambi <common/stringimpl.h>.
MSalters

3
In Visual Studio 2015, ricevi un avviso ...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowcon l'esecuzione di questa rigacl /EHsc main.cpp /Fetest.exe
CroCo

Risposte:


161

In effetti, un comportamento molto interessante.

Qualche idea del motivo per cui ottengo un errore di runtime quando commento #include <string>

Con il compilatore MS VC ++ l'errore si verifica perché se non lo #include <string>fai non avrai operator<<definito per std::string.

Quando il compilatore tenta di compilare ausgabe << f.getName();, cerca un operator<<definito per std::string. Poiché non è stato definito, il compilatore cerca delle alternative. C'è un operator<<definito per MyClasse le prove del compilatore per usarlo, e usarlo deve convertire std::stringa MyClass, e questo è esattamente ciò che accade perché MyClassha un costruttore non esplicito! Quindi, il compilatore finisce per creare una nuova istanza della tua MyClasse prova a trasmetterla di nuovo al tuo flusso di output. Ciò si traduce in una ricorsione infinita:

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

Per evitare l'errore è necessario #include <string>assicurarsi che sia presente un file operator<<definito per std::string. Inoltre dovresti rendere MyClassesplicito il tuo costruttore per evitare questo tipo di conversione inaspettata. Regola di saggezza: rendere espliciti i costruttori se accettano un solo argomento per evitare la conversione implicita:

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

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

Sembra che operator<<per std::stringottiene definita solo quando <string>è incluso (con il compilatore MS) e per questo motivo tutto viene compilato, ma si ottiene un comportamento un po 'inaspettato, come operator<<è sempre chiamato in modo ricorsivo per MyClassinvece di chiamare operator<<per std::string.

Significa che attraverso la #include <iostream>stringa è incluso solo in parte?

No, la stringa è completamente inclusa, altrimenti non saresti in grado di usarla.


19
@airborne - Non è un "problema specifico di Visual C ++", ma cosa può accadere quando non includi l'intestazione corretta. Quando si usa std::stringsenza un #include<string>tutto può succedere, non limitato a un errore in fase di compilazione. Chiamare la funzione o l'operatore sbagliato è apparentemente un'altra opzione.
Bo Persson

15
Bene, questo non è "chiamare la funzione o l'operatore sbagliato"; il compilatore sta facendo esattamente quello che gli hai detto di fare. Semplicemente non sapevi che gli stavi dicendo di farlo;)
Lightness Races in Orbit

18
Usare un tipo senza includere il suo file di intestazione corrispondente è un bug. Periodo. L'implementazione potrebbe aver reso il bug più facile da individuare? Sicuro. Ma questo non è un "problema" con l'implementazione, è un problema con il codice che hai scritto.
Cody Grey

4
Le librerie standard sono libere di includere token che sono definiti altrove in std all'interno di se stesse e non è necessario includere l'intera intestazione se definiscono un token.
Yakk - Adam Nevraumont

5
È piuttosto divertente vedere un gruppo di programmatori C ++ che sostengono che il compilatore e / o la libreria standard dovrebbero fare più lavoro per aiutarli. L'implementazione rientra nei suoi diritti qui, secondo lo standard, come è stato sottolineato più volte. Potrebbe essere usato un "trucco" per rendere questo più ovvio per il programmatore? Certo, ma potremmo anche scrivere codice in Java ed evitare del tutto questo problema. Perché MSVC dovrebbe rendere visibili i suoi helper interni? Perché un'intestazione dovrebbe trascinare in un gruppo di dipendenze di cui non ha effettivamente bisogno? Ciò viola l'intero spirito della lingua!
Cody Grey

35

Il problema è che il tuo codice sta facendo una ricorsione infinita. L'operatore di streaming per std::string( std::ostream& operator<<(std::ostream&, const std::string&)) è dichiarato nel <string>file di intestazione, sebbene esso std::stringstesso sia dichiarato in un altro file di intestazione (incluso da entrambi <iostream>e <string>).

Quando non includi <string>il compilatore cerca di trovare un modo per compilare ausgabe << f.getName();.

Succede che hai definito sia un operatore di streaming per MyClassche un costruttore che ammette a std::string, quindi il compilatore lo usa (tramite costruzione implicita ), creando una chiamata ricorsiva.

Se dichiari il explicittuo constructor ( explicit MyClass(const std::string& s)), il tuo codice non verrà più compilato, poiché non c'è modo di chiamare l'operatore di streaming con std::stringe sarai costretto a includere l' <string>intestazione.

MODIFICARE

Il mio ambiente di test è VS 2010 e, a partire dal livello di avviso 1 ( /W1), ti avverte del problema:

avviso C4717: 'operator <<': ricorsivo su tutti i percorsi di controllo, la funzione causerà un overflow dello stack di runtime

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.