โปรแกรมที่มีตัวแปร global ที่เรียกว่า main แทนฟังก์ชัน main ทำงานได้อย่างไร?


97

พิจารณาโปรแกรมต่อไปนี้:

#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 ); 

การใช้ g ++ 4.8.1 (mingw64) บน Windows 7 OS โปรแกรมจะคอมไพล์และทำงานได้ดีการพิมพ์:

C ++ ยอดเยี่ยมมาก!

ไปที่คอนโซล mainดูเหมือนจะเป็นตัวแปรส่วนกลางแทนที่จะเป็นฟังก์ชัน โปรแกรมนี้จะรันโดยไม่มีฟังก์ชันได้main()อย่างไร รหัสนี้เป็นไปตามมาตรฐาน C ++ หรือไม่ มีการกำหนดพฤติกรรมของโปรแกรมไว้อย่างดีหรือไม่? ฉันยังใช้-pedantic-errorsตัวเลือกนี้ แต่โปรแกรมยังคงรวบรวมและทำงาน


11
@ πάνταῥεῖ: ทำไมต้องมีแท็กทนายความภาษา?
Destructor

14
โปรดสังเกตว่า195เป็นรหัสสำหรับRETคำสั่งและในรูปแบบการเรียก C ผู้เรียกจะล้างสแต็ก
Brian

2
@PravasiMeet "แล้วโปรแกรมนี้ทำงานอย่างไร" - คุณไม่คิดว่าควรรันโค้ดเริ่มต้นสำหรับตัวแปร (แม้จะไม่มีmain()ฟังก์ชันก็ตามอันที่จริงแล้วมันไม่เกี่ยวข้องกันเลย)
ครัวซองต์ Paramagnetic

4
ฉันเป็นหนึ่งในผู้ที่พบว่าโปรแกรม segfaults ตามที่เป็นอยู่ (64-bit linux, g ++ 5.1 / clang 3.6) อย่างไรก็ตามฉันสามารถแก้ไขสิ่งนี้ได้โดยการแก้ไขเป็นint main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(และรวมถึง<cstdlib>) แม้ว่าโปรแกรมจะยังคงมีรูปแบบที่ไม่ถูกต้องตามกฎหมายก็ตาม
Mike Kinghan

11
@ ไบรอันคุณควรพูดถึงสถาปัตยกรรมเมื่อทำงบเช่นนั้น ทั้งโลกไม่ใช่ VAX หรือ x86 หรืออะไรก็ได้.
dmckee --- อดีตผู้ดูแลลูกแมว

คำตอบ:


85

ก่อนที่จะเข้าสู่ส่วนของคำถามเกี่ยวกับสิ่งที่เกิดขึ้นสิ่งสำคัญคือต้องชี้ให้เห็นว่าโปรแกรมมีรูปแบบไม่ถูกต้องตามรายงานข้อบกพร่อง 1886: การเชื่อมโยงภาษาสำหรับ main () :

[... ] โปรแกรมที่ประกาศตัวแปรหลักในขอบเขตส่วนกลางหรือที่ประกาศชื่อหลักด้วยการเชื่อมโยงภาษา C (ในเนมสเปซใด ๆ ) มีรูปแบบไม่ถูกต้อง [... ]

clang และ gcc เวอร์ชันล่าสุดทำให้เกิดข้อผิดพลาดและโปรแกรมจะไม่คอมไพล์ ( ดูตัวอย่าง gcc live ):

error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^

เหตุใดจึงไม่มีการวินิจฉัยใน gcc และ clang เวอร์ชันเก่า รายงานข้อบกพร่องนี้ยังไม่มีการเสนอข้อยุติจนถึงปลายปี 2014 ดังนั้นกรณีนี้จึงเพิ่งเกิดขึ้นอย่างชัดเจนซึ่งต้องได้รับการวินิจฉัย

ก่อนหน้านี้ดูเหมือนว่านี่จะเป็นพฤติกรรมที่ไม่ได้กำหนดเนื่องจากเรากำลังละเมิดข้อกำหนดที่จะต้องมีของร่างมาตรฐาน C ++ จากส่วน3.6.1 [basic.start.main] :

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

พฤติกรรมที่ไม่ได้กำหนดนั้นไม่สามารถคาดเดาได้และไม่จำเป็นต้องมีการวินิจฉัย ความไม่สอดคล้องที่เราเห็นจากการสร้างพฤติกรรมซ้ำนั้นเป็นพฤติกรรมที่ไม่ได้กำหนดโดยทั่วไป

แล้วโค้ดนั้นทำอะไรได้จริงและทำไมในบางกรณีจึงให้ผลลัพธ์? มาดูกันว่าเรามีอะไรบ้าง:

declarator  
|        initializer----------------------------------
|        |                                           |
v        v                                           v
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^      ^                                   ^
    |      |                                   |
    |      |                                   comma operator
    |      primary expression
global variable of type int

เรามีmainซึ่งเป็นint ที่ประกาศในเนมสเปซส่วนกลางและกำลังเริ่มต้นตัวแปรมีระยะเวลาการจัดเก็บแบบคงที่ มันเป็นเรื่องการดำเนินงานที่กำหนดไว้ว่าจะเริ่มต้นที่จะเกิดขึ้นก่อนที่ความพยายามที่จะเรียกร้องmainจะทำ แต่ปรากฏ GCC mainไม่ทำเช่นนี้ก่อนที่จะเรียก

รหัสใช้ตัวดำเนินการลูกน้ำตัวถูกดำเนินการด้านซ้ายคือนิพจน์ค่าที่ถูกละทิ้งและใช้ที่นี่เพื่อผลข้างเคียงของการเรียกstd::coutเท่านั้น ผลของผู้ประกอบการจุลภาคเป็นตัวถูกดำเนินการที่เหมาะสมซึ่งในกรณีนี้คือ prvalue ซึ่งได้รับมอบหมายให้ตัวแปร195main

เราสามารถเห็นsergej ชี้ให้เห็นการแสดงที่สร้างขึ้นซึ่งcoutถูกเรียกในระหว่างการเริ่มต้นแบบคงที่ แม้ว่าประเด็นที่น่าสนใจกว่าสำหรับการสนทนาเห็นเซสชัน Godbolt สดจะเป็นดังนี้:

main:
.zero   4

และต่อมา:

movl    $195, main(%rip)

สถานการณ์มีแนวโน้มว่าโปรแกรมกระโดดไปสัญลักษณ์mainคาดหวังว่ารหัสที่ถูกต้องที่จะมีและในบางกรณีจะ SEG ดังนั้นหากเป็นเช่นนั้นเราคาดว่าการจัดเก็บรหัสเครื่องที่ถูกต้องในตัวแปรmainอาจนำไปสู่โปรแกรมที่ใช้งานได้สมมติว่าเราอยู่ในส่วนที่อนุญาตให้เรียกใช้โค้ด เราสามารถมองเห็นนี้ 1984 IOCCC รายการไม่เพียงแค่นั้น

ดูเหมือนว่าเราสามารถรับ gcc เพื่อทำสิ่งนี้ใน C โดยใช้ ( ดูสด ):

const int main = 195 ;

มันเกิดความผิดพลาดหากตัวแปรmainไม่ใช่ const น่าจะเป็นเพราะมันไม่ได้อยู่ในตำแหน่งที่เรียกใช้งานได้ Hat Tip สำหรับความคิดเห็นนี้ที่นี่ซึ่งทำให้ฉันมีความคิดนี้

นอกจากนี้โปรดดูคำตอบของ FUZxxl ที่นี่สำหรับคำถามรุ่น C เฉพาะ


เหตุใดการนำไปใช้งานจึงไม่ให้คำเตือนด้วย (เมื่อฉันใช้ -Wall & -Wextra มันยังไม่เตือนแม้แต่ครั้งเดียว) ทำไม? คุณคิดอย่างไรกับคำตอบของ @Mark B สำหรับคำถามนี้?
Destructor

IMHO คอมไพลเลอร์ไม่ควรให้คำเตือนเนื่องจากmainไม่ใช่ตัวระบุที่สงวนไว้ (3.6.1 / 3) ในกรณีนี้ฉันคิดว่าการจัดการเคสนี้ของ VS2013 (ดูคำตอบของ Francis Cugler) นั้นถูกต้องกว่าในการจัดการมากกว่า gcc & clang
cdmh

@PravasiMeet ฉันได้อัปเดตคำตอบของฉันแล้วว่าทำไม gcc เวอร์ชันก่อนหน้าไม่ให้การวินิจฉัย
Shafik Yaghmour

2
... และแน่นอนเมื่อฉันทดสอบโปรแกรมของ OP บน Linux / x86-64 ด้วย g ++ 5.2 (ซึ่งยอมรับโปรแกรม - ฉันเดาว่าคุณไม่ได้ล้อเล่นเกี่ยวกับ "เวอร์ชันล่าสุด") มันขัดข้องตรงที่ฉันคาดไว้ จะ.
zwol

1
@ วอลเตอร์ฉันไม่เชื่อว่าสิ่งเหล่านี้ซ้ำกันอดีตกำลังถามคำถามที่แคบกว่านี้มาก เห็นได้ชัดว่ามีกลุ่มผู้ใช้ SO กลุ่มหนึ่งที่มีมุมมองของรายการซ้ำที่ลดน้อยลงซึ่งฉันไม่สมเหตุสมผลกับฉันมากนักเนื่องจากเราสามารถต้มคำถาม SO ส่วนใหญ่กับคำถามเก่า ๆ บางรุ่นได้ดังนั้น SO จะไม่มีประโยชน์มากนัก
Shafik Yaghmour

20

จาก 3.6.1 / 1:

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

จากนี้ดูเหมือนว่า g ++ จะอนุญาตให้โปรแกรม (น่าจะเป็นอนุประโยค "อิสระ") โดยไม่มีฟังก์ชันหลัก

จากนั้นจาก 3.6.1 / 3:

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

ดังนั้นเราจึงได้เรียนรู้ว่าการตั้งชื่อตัวแปรจำนวนเต็มเป็นเรื่องmainปกติดี

สุดท้ายหากคุณสงสัยว่าเหตุใดจึงมีการพิมพ์เอาต์พุตการเริ่มต้นint mainใช้ตัวดำเนินการลูกน้ำเพื่อดำเนินการcoutที่การเริ่มต้นแบบคงที่จากนั้นระบุค่าที่แท้จริงเพื่อทำการเริ่มต้น


7
เป็นเรื่องน่าสนใจที่จะทราบว่าการเชื่อมโยงล้มเหลวหากคุณเปลี่ยนชื่อmainเป็นอย่างอื่น: (.text+0x20): undefined reference to main ''
Fred Larson

1
คุณไม่จำเป็นต้องระบุกับ gcc ว่าโปรแกรมของคุณเป็นอิสระหรือไม่?
Shafik Yaghmour

9

gcc 4.8.1 สร้างชุดประกอบ x86 ต่อไปนี้:

.LC0:
    .string "C++ is excellent!\n"
    subq    $8, %rsp    #,
    movl    std::__ioinit, %edi #,
    call    std::ios_base::Init::Init() #
    movl    $__dso_handle, %edx #,
    movl    std::__ioinit, %esi #,
    movl    std::ios_base::Init::~Init(), %edi  #,
    call    __cxa_atexit    #
    movl    $.LC0, %esi #,
    movl    std::cout, %edi #,
    call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)   #
    movl    $195, main(%rip)    #, main
    addq    $8, %rsp    #,
    ret
main:
    .zero   4

สังเกตว่าcoutถูกเรียกในระหว่างการเริ่มต้นไม่ใช่ในmainฟังก์ชัน!

.zero 4ประกาศ 4 (0 ที่เริ่ม) ไบต์เริ่มต้นในสถานที่mainที่mainเป็นชื่อของตัวแปร [!]

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


1
โปรดทราบว่าBrian ชี้ให้เห็นว่า 195เป็นรหัสที่กำหนดไว้สำหรับretสถาปัตยกรรมบางอย่าง ดังนั้นการพูดคำสั่งเป็นศูนย์อาจไม่ถูกต้อง
Shafik Yaghmour

@ShafikYaghmour ขอบคุณสำหรับความคิดเห็นคุณพูดถูก ฉันสับสนกับคำสั่งแอสเซมเบลอร์
เสิร์จ

8

นั่นเป็นโปรแกรมที่ไม่ดี มันขัดข้องในสภาพแวดล้อมการทดสอบของฉัน cygwin64 / g ++ 4.9.3

จากมาตรฐาน:

3.6.1 ฟังก์ชันหลัก [basic.start.main]

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


ฉันคิดว่าก่อนรายงานข้อบกพร่องที่ฉันอ้างถึงนี่เป็นเพียงพฤติกรรมที่ไม่ได้กำหนดไว้อย่างชัดเจน
Shafik Yaghmour

@ShafikYaghmour หลักการทั่วไปที่จะนำไปใช้ในทุกสถานที่ที่มาตรฐานใช้จะต้อง ?
ราหู

ฉันอยากจะตอบว่าใช่ แต่ฉันไม่เห็นคำอธิบายที่ดีเกี่ยวกับความแตกต่าง จากสิ่งที่ฉันสามารถบอกได้จากการสนทนานี้ NDR ที่มีรูปแบบไม่ดีและพฤติกรรมที่ไม่ได้กำหนดอาจมีความหมายเหมือนกันเนื่องจากไม่จำเป็นต้องมีการวินิจฉัย สิ่งนี้ดูเหมือนจะบ่งบอกถึงรูปแบบที่ไม่ดีและ UB มีความแตกต่างกัน แต่ไม่แน่ใจ
Shafik Yaghmour

3
C99 มาตรา 4 ("ความสอดคล้อง") ทำให้สิ่งนี้ไม่คลุมเครือ: "หากมีการละเมิดข้อกำหนด" จะ "หรือ" ไม่ "ที่ปรากฏนอกข้อ จำกัด พฤติกรรมนั้นจะไม่ได้กำหนดไว้ ฉันไม่พบถ้อยคำที่เทียบเท่าใน C ++ 98 หรือ C ++ 11 แต่ฉันสงสัยอย่างยิ่งว่าคณะกรรมการหมายความว่าจะอยู่ที่นั่น (คณะกรรมการ C และ C ++ จำเป็นต้องนั่งลงและแยกแยะความแตกต่างของคำศัพท์ทั้งหมดระหว่างสองมาตรฐาน)
zwol

7

เหตุผลที่ฉันเชื่อว่าสิ่งนี้ได้ผลคือคอมไพเลอร์ไม่ทราบว่ากำลังรวบรวมmain()ฟังก์ชันดังนั้นจึงรวบรวมจำนวนเต็มส่วนกลางพร้อมผลข้างเคียงของการกำหนด

รูปแบบวัตถุที่ว่านี้แปลหน่วยเรียบเรียงจะไม่สามารถแยกความแตกต่างระหว่างสัญลักษณ์ฟังก์ชั่นและสัญลักษณ์ตัวแปร

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

เมื่อฉันวิ่งตัวอย่างที่พิมพ์ออกมา แต่แล้วก็เกิดจากการseg ผิด ฉันคิดว่าเมื่อระบบรันไทม์พยายามที่จะดำเนินการตัวแปร intราวกับว่ามันเป็นฟังก์ชั่น


4

ฉันได้ลองสิ่งนี้บน Win7 64bit OS โดยใช้ VS2013 และคอมไพล์ได้อย่างถูกต้อง แต่เมื่อฉันพยายามสร้างแอปพลิเคชันฉันได้รับข้อความนี้จากหน้าต่างผลลัพธ์

1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------
1>LINK : fatal error LNK1561: entry point must be defined
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

2
FWIW นั่นเป็นข้อผิดพลาดของตัวเชื่อมโยงไม่ใช่ข้อความจากดีบักเกอร์ การรวบรวมสำเร็จ แต่ตัวเชื่อมโยงไม่พบฟังก์ชันmain()เนื่องจากเป็นตัวแปรประเภทint
cdmh

ขอบคุณสำหรับการตอบกลับฉันจะอธิบายคำตอบเริ่มต้นของฉันเพื่อสะท้อนสิ่งนี้
Francis Cugler

-1

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

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