เหตุใด #include <string> จึงป้องกันข้อผิดพลาดสแต็กล้นที่นี่


121

นี่คือรหัสตัวอย่างของฉัน:

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

ถ้าผมแสดงความคิดเห็นออก#include <string>ฉันไม่ได้รับรวบรวมข้อผิดพลาดใด ๆ #include <iostream>ผมคิดว่าเพราะมันเป็นชนิดของการรวมผ่าน ถ้าฉัน"คลิกขวา -> ไปที่คำจำกัดความ"ใน Microsoft VS ทั้งคู่ชี้ไปที่บรรทัดเดียวกันในxstringไฟล์:

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

แต่เมื่อฉันเรียกใช้โปรแกรมฉันได้รับข้อผิดพลาดข้อยกเว้น:

0x77846B6E (ntdll.dll) ใน OperatorString.exe: 0xC00000FD: Stack overflow (พารามิเตอร์: 0x00000001, 0x01202FC4)

ความคิดว่าทำไมฉันจะได้รับข้อผิดพลาด runtime เมื่อแสดงความคิดเห็นออกมา#include <string>? ฉันใช้ VS 2013 Express


4
ด้วยพระคุณของพระเจ้า ทำงานได้อย่างสมบูรณ์บน gcc ดูideone.com/YCf4OI
v78

คุณลองสตูดิโอวิชวลด้วย Visual c ++ และแสดงความคิดเห็นรวม <string> หรือไม่
ในอากาศ

1
@cbuchart: แม้ว่าคำถามจะได้รับคำตอบแล้ว แต่ฉันคิดว่านี่เป็นหัวข้อที่ซับซ้อนเพียงพอที่การมีคำตอบที่สองในคำที่แตกต่างกันนั้นมีค่า ฉันโหวตให้ยกเลิกการลบคำตอบที่ดีของคุณ
Lightness Races ใน Orbit

5
@Ruslan: อย่างมีประสิทธิภาพพวกเขาเป็น นั่นคือการพูด#include<iostream>และทั้งสองอาจจะรวมถึง<string> <common/stringimpl.h>
MSalters

3
ใน Visual Studio 2015 คุณจะได้รับคำเตือน...\main.cpp(23) : warning C4717: 'operator<<': recursive on all control paths, function will cause runtime stack overflowเมื่อเรียกใช้บรรทัดนี้cl /EHsc main.cpp /Fetest.exe
CroCo

คำตอบ:


161

พฤติกรรมที่น่าสนใจมาก

มีความคิดว่าทำไมฉันถึงได้รับข้อผิดพลาดรันไทม์เมื่อแสดงความคิดเห็น #include <string>

ด้วยคอมไพเลอร์ MS VC ++ ข้อผิดพลาดเกิดขึ้นเพราะถ้าคุณไม่ทำ#include <string>คุณจะไม่ได้operator<<กำหนดไว้สำหรับstd::string.

เมื่อคอมไพเลอร์พยายามที่จะรวบรวมausgabe << f.getName();มันก็ดูการกำหนดไว้สำหรับoperator<< std::stringเนื่องจากไม่ได้กำหนดไว้คอมไพลเลอร์จึงมองหาทางเลือกอื่น มีการoperator<<กำหนดไว้สำหรับMyClassและคอมไพเลอร์พยายามที่จะใช้และการใช้งานจะต้องแปลงstd::stringเป็นMyClassและนี่คือสิ่งที่เกิดขึ้นเนื่องจากMyClassมีตัวสร้างที่ไม่ชัดเจน! ดังนั้นคอมไพเลอร์จะสร้างอินสแตนซ์ใหม่ของคุณMyClassและพยายามสตรีมอีกครั้งไปยังสตรีมเอาต์พุตของคุณ สิ่งนี้ส่งผลให้เกิดการเรียกซ้ำไม่รู้จบ:

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

เพื่อหลีกเลี่ยงข้อผิดพลาดที่คุณจำเป็นต้อง#include <string>ตรวจสอบให้แน่ใจว่ามีการกำหนดไว้สำหรับoperator<< std::stringนอกจากนี้คุณควรกำหนดให้ตัวสร้างของคุณMyClassชัดเจนเพื่อหลีกเลี่ยงการแปลงที่ไม่คาดคิดประเภทนี้ กฎแห่งปัญญา: ทำให้ตัวสร้างมีความชัดเจนหากพวกเขาใช้อาร์กิวเมนต์เดียวเพื่อหลีกเลี่ยงการแปลงโดยปริยาย:

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

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

ดูเหมือนว่าoperator<<สำหรับการstd::stringได้รับการกำหนดไว้เฉพาะเมื่อ<string>รวมอยู่ (กับคอมไพเลอร์ MS) และที่รวบรวมเหตุผลทุกอย่าง แต่คุณจะได้รับพฤติกรรมค่อนข้างไม่คาดคิดoperator<<จะได้รับการเรียกซ้ำสำหรับMyClassแทนการโทรสำหรับoperator<<std::string

นั่นหมายความว่าผ่าน#include <iostream>สตริงจะรวมเพียงบางส่วนหรือไม่?

ไม่สตริงถูกรวมไว้อย่างสมบูรณ์มิฉะนั้นคุณจะไม่สามารถใช้งานได้


19
@airborne - ไม่ใช่ "ปัญหาเฉพาะของ Visual C ++" แต่จะเกิดอะไรขึ้นเมื่อคุณไม่รวมส่วนหัวที่เหมาะสม เมื่อใช้std::stringโดยไม่มี#include<string>สิ่งต่างๆเกิดขึ้นได้ไม่ จำกัด เฉพาะข้อผิดพลาดเวลาคอมไพล์ การเรียกใช้ฟังก์ชันหรือตัวดำเนินการที่ไม่ถูกต้องเป็นอีกทางเลือกหนึ่ง
Bo Persson

15
นี่ไม่ใช่ "การเรียกใช้ฟังก์ชันหรือตัวดำเนินการที่ไม่ถูกต้อง"; คอมไพเลอร์กำลังทำในสิ่งที่คุณบอกให้ทำ คุณไม่รู้ว่าคุณกำลังบอกให้ทำสิ่งนี้)
Lightness Races in Orbit

18
การใช้ประเภทโดยไม่รวมไฟล์ส่วนหัวที่เกี่ยวข้องถือเป็นข้อบกพร่อง ระยะเวลา การติดตั้งทำให้จุดบกพร่องง่ายขึ้นหรือไม่? แน่ใจ แต่นั่นไม่ใช่"ปัญหา"ในการใช้งาน แต่เป็นปัญหากับโค้ดที่คุณเขียน
โคดี้เกรย์

4
ไลบรารีมาตรฐานมีอิสระที่จะรวมโทเค็นที่กำหนดไว้ที่อื่นใน std ภายในตัวเองและไม่จำเป็นต้องรวมส่วนหัวทั้งหมดหากพวกเขากำหนดโทเค็นเดียว
Yakk - Adam Nevraumont

5
ค่อนข้างตลกที่เห็นโปรแกรมเมอร์ C ++ จำนวนมากเถียงว่าคอมไพเลอร์และ / หรือไลบรารีมาตรฐานควรทำงานมากขึ้นเพื่อช่วยพวกเขา การนำไปใช้งานนั้นอยู่ในสิทธิ์ของมันที่นี่ตามมาตรฐานดังที่ได้ระบุไว้หลายครั้ง "เล่ห์เหลี่ยม" สามารถนำมาใช้เพื่อให้ชัดเจนขึ้นสำหรับโปรแกรมเมอร์หรือไม่? แน่นอน แต่เราสามารถเขียนโค้ดใน Java และหลีกเลี่ยงปัญหานี้ได้โดยสิ้นเชิง เหตุใด MSVC จึงควรทำให้ผู้ช่วยภายในมองเห็นได้ เหตุใดส่วนหัวจึงควรลากในการอ้างอิงจำนวนมากซึ่งไม่จำเป็นต้องใช้จริง? ที่ฝืนจิตวิญญาณทั้งภาษา!
โคดี้เกรย์

35

ปัญหาคือโค้ดของคุณกำลังทำการเรียกซ้ำไม่สิ้นสุด ตัวดำเนินการสตรีมสำหรับstd::string( std::ostream& operator<<(std::ostream&, const std::string&)) ถูกประกาศใน<string>ไฟล์ส่วนหัวแม้ว่าจะstd::stringมีการประกาศตัวเองในไฟล์ส่วนหัวอื่น ๆ (รวมทั้ง<iostream>และ<string>)

เมื่อคุณไม่ได้รวมถึงคอมไพเลอร์พยายามที่จะหาวิธีที่จะรวบรวม<string>ausgabe << f.getName();

มันเกิดขึ้นที่คุณได้กำหนดทั้งตัวดำเนินการสตรีมMyClassและตัวสร้างที่ยอมรับ a std::stringดังนั้นคอมไพเลอร์จึงใช้มัน (ผ่านการสร้างโดยนัย ) สร้างการเรียกซ้ำ

หากคุณประกาศตัวexplicitสร้างของคุณ ( explicit MyClass(const std::string& s)) โค้ดของคุณจะไม่คอมไพล์อีกต่อไปเนื่องจากไม่มีวิธีใดที่จะโทรหาตัวดำเนินการสตรีมด้วยstd::stringและคุณจะต้องรวม<string>ส่วนหัว

แก้ไข

สภาพแวดล้อมการทดสอบของฉันคือ VS 2010 และเริ่มต้นที่ระดับการเตือน 1 ( /W1) จะเตือนคุณเกี่ยวกับปัญหา:

คำเตือน C4717: 'operator <<': เรียกซ้ำในเส้นทางควบคุมทั้งหมดฟังก์ชันจะทำให้รันไทม์สแตกล้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.