main () เป็นจุดเริ่มต้นของโปรแกรม C ++ จริงหรือ?


131

ส่วน $ 3.6.1 / 1 จาก C ++ Standard อ่าน

โปรแกรมจะต้องมีฟังก์ชันโกลบอลที่เรียกว่าmainซึ่งเป็นจุดเริ่มต้นของโปรแกรม

ลองพิจารณารหัสนี้

int square(int i) { return i*i; }
int user_main()
{ 
    for ( int i = 0 ; i < 10 ; ++i )
           std::cout << square(i) << endl;
    return 0;
}
int main_ret= user_main();
int main() 
{
        return main_ret;
}

โค้ดตัวอย่างนี้ทำในสิ่งที่ฉันตั้งใจจะทำนั่นคือการพิมพ์กำลังสองของจำนวนเต็มตั้งแต่ 0 ถึง 9 ก่อนที่จะเข้าสู่main()ฟังก์ชันซึ่งควรจะเป็น "start" ของโปรแกรม

ฉันยังรวบรวมด้วย-pedanticตัวเลือก GCC 4.5.0 มันไม่มีข้อผิดพลาดไม่เตือน!

ดังนั้นคำถามของฉันคือ

รหัสนี้เป็นไปตามมาตรฐานจริงหรือไม่?

หากเป็นไปตามมาตรฐานแล้วจะไม่ทำให้สิ่งที่มาตรฐานกล่าวเป็นโมฆะหรือไม่? main()ยังไม่เริ่มโปรแกรมนี้! user_main()ดำเนินการก่อนmain().

ฉันเข้าใจดีว่าในการเริ่มต้นตัวแปรส่วนกลางmain_retนั้นuse_main()จะดำเนินการก่อน แต่นั่นเป็นสิ่งที่แตกต่างโดยสิ้นเชิง ประเด็นก็คือว่ามันไม่เป็นโมฆะคำสั่งยก $ 3.6.1 / 1 จากมาตรฐานในขณะที่main()ไม่ได้เป็นจุดเริ่มต้นของโปรแกรม; มันเป็นจุดสิ้นสุดของโปรแกรมนี้จริงๆ!


แก้ไข:

คุณนิยามคำว่า 'เริ่มต้น' ได้อย่างไร?

มันทำให้คำจำกัดความของวลี"start of the program" ลดลง แล้วคุณนิยามมันอย่างไร?

คำตอบ:


85

ไม่ C ++ ทำหลายสิ่งหลายอย่างเพื่อ "กำหนดสภาพแวดล้อม" ก่อนที่จะเรียก main; อย่างไรก็ตามสิ่งสำคัญคือการเริ่มต้นอย่างเป็นทางการของส่วน "ผู้ใช้ที่ระบุ" ของโปรแกรม C ++

การตั้งค่าสภาพแวดล้อมบางอย่างไม่สามารถควบคุมได้ (เช่นรหัสเริ่มต้นในการตั้งค่า std :: cout อย่างไรก็ตามสภาพแวดล้อมบางอย่างสามารถควบคุมได้เช่นบล็อกส่วนกลางแบบคงที่ (สำหรับการกำหนดค่าเริ่มต้นตัวแปรส่วนกลางแบบคงที่) โปรดทราบว่าเนื่องจากคุณมีไม่ครบ การควบคุมก่อนหน้าหลักคุณไม่สามารถควบคุมลำดับการเริ่มต้นบล็อกแบบคงที่ได้อย่างสมบูรณ์

หลังจาก main แล้วโค้ดของคุณมีแนวคิด "อยู่ในการควบคุมอย่างเต็มที่" ของโปรแกรมในแง่ที่คุณสามารถระบุคำสั่งที่จะดำเนินการและลำดับที่จะดำเนินการได้ มัลติเธรดสามารถจัดเรียงลำดับการเรียกใช้โค้ดใหม่ได้ แต่คุณยังสามารถควบคุม C ++ ได้เนื่องจากคุณระบุให้มีส่วนของการดำเนินการโค้ด (อาจ) ไม่อยู่ในลำดับ


9
+1 สำหรับสิ่งนี้"โปรดทราบว่าเนื่องจากคุณไม่มีการควบคุมทั้งหมดก่อนหน้าหลักคุณจึงไม่สามารถควบคุมลำดับการเริ่มต้นบล็อกแบบคงที่ได้อย่างสมบูรณ์หลังจากที่ main แล้วโค้ดของคุณจะมีแนวคิด" อยู่ในการควบคุมอย่างสมบูรณ์ "ของ โปรแกรมในความรู้สึกที่คุณทั้งสองสามารถระบุคำแนะนำที่จะดำเนินการและคำสั่งในการที่จะดำเนินการให้" สิ่งนี้ทำให้ฉันทำเครื่องหมายคำตอบนี้ว่าเป็นคำตอบที่ยอมรับ ... ฉันคิดว่าสิ่งเหล่านี้เป็นประเด็นสำคัญมากซึ่งmain()เป็นเหตุผลที่เพียงพอสำหรับการ"เริ่มโปรแกรม"
Nawaz

13
@Nawaz: โปรดทราบว่านอกเหนือจากการควบคุมลำดับการเริ่มต้นอย่างสมบูรณ์แล้วคุณไม่สามารถควบคุมข้อผิดพลาดในการเริ่มต้นได้: คุณไม่สามารถตรวจจับข้อยกเว้นในขอบเขตทั่วโลกได้
André Caron

@Nawaz: บล็อกสากลแบบคงที่คืออะไร? ช่วยอธิบายโดยใช้ตัวอย่างง่ายๆได้ไหม ขอบคุณ
Destructor

@meet: อ็อบเจ็กต์ที่ประกาศในระดับเนมสเปซมีstaticระยะเวลาในการจัดเก็บดังนั้นอ็อบเจ็กต์เหล่านี้ที่อยู่ในหน่วยการแปลที่แตกต่างกันสามารถเริ่มต้นในลำดับใดก็ได้ (เนื่องจากคำสั่งนั้นไม่ได้ระบุโดยมาตรฐาน) ฉันไม่แน่ใจว่าจะตอบคำถามของคุณได้หรือไม่ แต่นั่นคือสิ่งที่ฉันสามารถพูดได้ในบริบทของหัวข้อนี้
Nawaz

88

คุณกำลังอ่านประโยคไม่ถูกต้อง

โปรแกรมจะต้องมีฟังก์ชันโกลบอลที่เรียกว่า main ซึ่งเป็นจุดเริ่มต้นของโปรแกรมที่กำหนดไว้

มาตรฐานกำลังกำหนดคำว่า "เริ่ม" สำหรับวัตถุประสงค์ของส่วนที่เหลือของมาตรฐาน มันไม่ได้บอกว่าไม่มีรันรหัสก่อนที่จะmainถูกเรียกว่า mainมันบอกว่าจุดเริ่มต้นของโปรแกรมจะถือเป็นที่ฟังก์ชั่น

โปรแกรมของคุณเป็นไปตามมาตรฐาน โปรแกรมของคุณยังไม่ "เริ่ม" จนกว่าโปรแกรมหลักจะเริ่มทำงาน ตัวสร้างถูกเรียกก่อนโปรแกรมของคุณ "เริ่ม" ตามคำจำกัดความของ "start" ในมาตรฐาน แต่แทบจะไม่สำคัญ จำนวนมากของรหัสจะถูกดำเนินการก่อนที่จะmainมีการที่เคยเรียกว่าในทุกโปรแกรมไม่เพียงตัวอย่างนี้

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


3
ขออภัยฉันไม่เห็นด้วยกับการตีความของคุณในประโยคนั้น
Lightness Races ใน Orbit

ฉันคิดว่าอดัมเดวิสพูดถูก "หลัก" เหมือนข้อ จำกัด ในการเข้ารหัส
laike9m

@LightnessRacesinOrbit ฉันไม่เคยติดตามเลย แต่สำหรับฉันแล้วประโยคนั้นสามารถต้มลงไปในเชิงตรรกะเป็น"ฟังก์ชันส่วนกลางที่เรียกว่า main คือจุดเริ่มต้นที่กำหนดของโปรแกรม" (เน้นที่เพิ่ม) คุณตีความประโยคนั้นว่าอย่างไร?
Adam Davis

1
@AdamDavis: ฉันจำไม่ได้ว่าความกังวลของฉันคืออะไร ตอนนี้ยังคิดไม่ออก
Lightness Races ใน Orbit

23

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

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

ดูนี่สิ:

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

ขั้นตอนของโปรแกรมของคุณจะเกิดขึ้นอย่างมีประสิทธิภาพ Foo::Foo()


13
+1 แต่โปรดทราบว่าหากคุณมีวัตถุส่วนกลางหลายตัวในหน่วยการแปลที่แตกต่างกันสิ่งนี้จะทำให้คุณมีปัญหาอย่างรวดเร็วเนื่องจากไม่ได้กำหนดลำดับที่เรียกตัวสร้าง คุณสามารถหลีกหนีจากเสื้อกล้ามและการเริ่มต้นที่ขี้เกียจได้ แต่ในสภาพแวดล้อมแบบมัลติเธรดสิ่งต่างๆจะน่าเกลียดอย่างรวดเร็ว พูดคำเดียวอย่าทำในโค้ดจริง
Alexandre C.

3
ในขณะที่คุณควรให้ main () body ที่เหมาะสมในโค้ดของคุณและอนุญาตให้รันการดำเนินการได้แนวคิดของอ็อบเจ็กต์ภายนอกที่เริ่มต้นคือสิ่งที่ไลบรารี LD_PRELOAD จำนวนมากใช้
CashCow

2
@ อเล็กซ์: มาตรฐานระบุว่าไม่ได้กำหนด แต่เป็นลำดับการเชื่อมโยงที่ใช้ได้จริง (โดยปกติขึ้นอยู่กับคอมไพเลอร์) ลำดับการเริ่มต้นของการควบคุม
ThomasMcLeod

1
@ โทมัส: ฉันจะไม่พยายามพึ่งพาจากระยะไกลอย่างแน่นอน ฉันจะไม่พยายามควบคุมระบบสร้างด้วยตนเองอย่างแน่นอน
Alexandre C.

1
@ อเล็กซ์: ไม่สำคัญอีกต่อไป แต่ในวันนี้เราจะใช้คำสั่งลิงค์เพื่อควบคุมอิมเมจบิลด์เพื่อลดการเพจหน่วยความจำกายภาพ มีเหตุผลด้านอื่น ๆ ที่คุณอาจต้องการควบคุมลำดับการเริ่มต้นแม้ว่าจะไม่ส่งผลต่อความหมายของโปรแกรมเช่นการทดสอบเปรียบเทียบประสิทธิภาพการเริ่มต้น
ThomasMcLeod

15

คุณติดแท็กคำถามเป็น "C" ด้วยเช่นกันหากพูดถึง C อย่างเคร่งครัดการเริ่มต้นของคุณควรล้มเหลวตามข้อ 6.7.8 "การเริ่มต้น" ของมาตรฐาน ISO C99

สิ่งที่เกี่ยวข้องมากที่สุดในกรณีนี้ดูเหมือนจะเป็นข้อ จำกัด # 4 ซึ่งระบุว่า:

นิพจน์ทั้งหมดในตัวเริ่มต้นสำหรับอ็อบเจ็กต์ที่มีระยะเวลาการจัดเก็บแบบคงที่จะต้องเป็นนิพจน์คงที่หรือสตริงลิเทอรัล

ดังนั้นคำตอบสำหรับคำถามของคุณคือรหัสไม่สอดคล้องกับมาตรฐาน C

คุณอาจต้องการลบแท็ก "C" หากคุณสนใจเฉพาะมาตรฐาน C ++


4
@ Remo.D คุณช่วยบอกเราได้ไหมว่ามีอะไรอยู่ในส่วนนั้น ไม่ใช่ทุกคนที่มีมาตรฐาน C :)
UmmaGumma

2
เนื่องจากคุณจู้จี้จุกจิกมาก: อนิจจา ANSI C ล้าสมัยไปแล้วตั้งแต่ปี 1989 ISO C90 หรือ C99 เป็นมาตรฐานที่เกี่ยวข้องในการอ้างอิง
Lundin

@ Lundin: ไม่มีใครจู้จี้จุกจิกพอ :) ฉันอ่าน ISO C99 แต่ฉันค่อนข้างมั่นใจว่ามันใช้กับ C90 ได้เช่นกัน
Remo.D

@Ashot คุณพูดถูกเพิ่มประโยคที่ฉันคิดว่าเกี่ยวข้องมากที่สุดที่นี่
Remo.D

3
@Remo: +1 สำหรับการให้ข้อมูลว่า C ไม่ถูกต้อง ฉันไม่รู้ว่า ดูนี่คือวิธีที่ผู้คนเรียนรู้บางครั้งตามแผนบางครั้งก็บังเอิญ!
Nawaz

10

ส่วน 3.6 โดยรวมชัดเจนมากเกี่ยวกับการโต้ตอบmainและการเริ่มต้นแบบไดนามิก "การเริ่มต้นที่กำหนดของโปรแกรม" main()ที่ไม่ได้ใช้ที่ใดและเป็นเพียงสื่อความหมายของความตั้งใจทั่วไปของ ไม่มีเหตุผลใดที่จะตีความวลีนั้นในรูปแบบเชิงบรรทัดฐานที่ขัดแย้งกับข้อกำหนดที่ละเอียดและชัดเจนในมาตรฐาน


9

คอมไพเลอร์มักจะต้องเพิ่มโค้ดก่อน main () เพื่อให้เป็นไปตามมาตรฐาน เนื่องจากมาตรฐานระบุว่าต้องทำ initalization of globals / statics ก่อนที่โปรแกรมจะทำงาน และตามที่กล่าวไว้ก็เช่นเดียวกันกับตัวสร้างวัตถุที่วางไว้ที่ขอบเขตไฟล์ (globals)

ดังนั้นคำถามเดิมจึงเกี่ยวข้องกับ C เช่นกันเนื่องจากในโปรแกรม C คุณจะยังคงมีการเริ่มต้นแบบ globals / static ก่อนที่โปรแกรมจะเริ่มทำงานได้

มาตรฐานถือว่าตัวแปรเหล่านี้เริ่มต้นผ่าน "magic" เนื่องจากไม่ได้บอกว่าควรตั้งค่าอย่างไรก่อนเริ่มต้นโปรแกรม ฉันคิดว่าพวกเขาถือว่าสิ่งนั้นเป็นสิ่งที่อยู่นอกขอบเขตของมาตรฐานภาษาโปรแกรม

แก้ไข: ดูตัวอย่าง ISO 9899: 1999 5.1.2:

อ็อบเจ็กต์ทั้งหมดที่มีระยะเวลาการจัดเก็บแบบคงที่จะถูกเตรียมใช้งาน (ตั้งค่าเป็นค่าเริ่มต้น) ก่อนเริ่มโปรแกรม ลักษณะและระยะเวลาของการเริ่มต้นดังกล่าวไม่ได้ระบุไว้เป็นอย่างอื่น

ทฤษฎีที่อยู่เบื้องหลังวิธีการสร้าง "เวทมนตร์" นี้ย้อนกลับไปสู่การเกิดของ C ซึ่งเป็นภาษาโปรแกรมที่ตั้งใจจะใช้สำหรับ UNIX OS บนคอมพิวเตอร์ที่ใช้ RAM เท่านั้น ตามทฤษฎีแล้วโปรแกรมจะสามารถโหลดข้อมูลที่เตรียมไว้ล่วงหน้าทั้งหมดจากไฟล์ปฏิบัติการลงใน RAM ในเวลาเดียวกันกับที่โปรแกรมอัปโหลดไปยัง RAM

ตั้งแต่นั้นเป็นต้นมาคอมพิวเตอร์และระบบปฏิบัติการได้พัฒนาขึ้นและ C ถูกใช้ในพื้นที่กว้างกว่าที่คาดการณ์ไว้ในตอนแรก ระบบปฏิบัติการพีซีสมัยใหม่มีที่อยู่เสมือนเป็นต้นและระบบฝังตัวทั้งหมดจะรันโค้ดจาก ROM ไม่ใช่ RAM ดังนั้นจึงมีหลายสถานการณ์ที่ไม่สามารถตั้งค่า RAM "โดยอัตโนมัติ" ได้

นอกจากนี้มาตรฐานยังเป็นนามธรรมเกินกว่าที่จะรู้อะไรเกี่ยวกับสแต็กและหน่วยความจำการประมวลผล ฯลฯ สิ่งเหล่านี้ต้องทำเช่นกันก่อนที่โปรแกรมจะเริ่มทำงาน

ดังนั้นโปรแกรม C / C ++ ทุกโปรแกรมจะมีโค้ด init / "copy-down" ที่เรียกใช้งานก่อนที่จะเรียก main เพื่อให้สอดคล้องกับกฎการเริ่มต้นของมาตรฐาน

ตัวอย่างเช่นระบบฝังตัวมักจะมีตัวเลือกที่เรียกว่า "การเริ่มต้นที่ไม่เป็นไปตามมาตรฐาน ISO" ซึ่งขั้นตอนการเริ่มต้นทั้งหมดจะถูกข้ามไปด้วยเหตุผลด้านประสิทธิภาพจากนั้นโค้ดจะเริ่มต้นโดยตรงจาก main แต่ระบบดังกล่าวไม่เป็นไปตามมาตรฐานเนื่องจากคุณไม่สามารถพึ่งพาค่าเริ่มต้นของตัวแปรส่วนกลาง / คงที่ได้


4

"โปรแกรม" ของคุณเพียงแค่ส่งคืนค่าจากตัวแปรส่วนกลาง อย่างอื่นคือรหัสเริ่มต้น ดังนั้นมาตรฐานจึงถือ - คุณมีโปรแกรมที่ไม่สำคัญมากและการเริ่มต้นที่ซับซ้อนมากขึ้น



2

ดูเหมือนเป็นการเล่นลิ้นความหมายภาษาอังกฤษ OP หมายถึงบล็อกโค้ดของเขาก่อนเป็น "รหัส" และต่อมาเป็น "โปรแกรม" ผู้ใช้เขียนโค้ดจากนั้นคอมไพลเลอร์จะเขียนโปรแกรม


1

main ถูกเรียกหลังจากเริ่มต้นตัวแปรส่วนกลางทั้งหมด

สิ่งที่มาตรฐานไม่ได้ระบุคือลำดับการเริ่มต้นของตัวแปรส่วนกลางทั้งหมดของโมดูลทั้งหมดและไลบรารีที่ลิงก์แบบสแตติก


0

ใช่หลักคือ "จุดเริ่มต้น" ของโปรแกรม C ++ ทุกโปรแกรมยกเว้นส่วนขยายเฉพาะการนำไปใช้งาน ถึงกระนั้นบางสิ่งก็เกิดขึ้นก่อน main โดยเฉพาะอย่างยิ่งการเริ่มต้นทั่วโลกเช่น main_ret

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