Làm thế nào một chương trình với một biến toàn cục được gọi là main thay vì một hàm chính có thể hoạt động?


97

Hãy xem xét chương trình sau:

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

Sử dụng g ++ 4.8.1 (mingw64) trên HĐH Windows 7, chương trình biên dịch và chạy tốt, in:

C ++ là tuyệt vời!

vào bảng điều khiển. maindường như là một biến toàn cục hơn là một hàm; làm thế nào chương trình này có thể thực thi mà không có chức năng main()? Mã này có tuân theo tiêu chuẩn C ++ không? Hành vi của chương trình có được xác định rõ không? Tôi cũng đã sử dụng -pedantic-errorstùy chọn nhưng chương trình vẫn biên dịch và chạy.


11
@ πάνταῥεῖ: tại sao thẻ luật sư ngôn ngữ lại cần thiết?
Kẻ hủy diệt

14
Lưu ý rằng đó 195là mã lựa chọn cho RETlệnh và trong quy ước gọi C, trình gọi xóa ngăn xếp.
Brian

2
@PravasiMeet "thì làm sao chương trình này thực thi" - sao bạn không nghĩ rằng các mã khởi tạo cho một biến nên thực hiện (thậm chí không có các main()chức năng trong thực tế, họ là hoàn toàn không liên quan?.)
Các thuận Croissant

4
Tôi là một trong những người nhận thấy rằng chương trình mặc định như vậy (64-bit linux, g ++ 5.1 / clang 3.6). Tuy nhiên, tôi có thể khắc phục điều này bằng cách sửa đổi nó thành int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(và bao gồm <cstdlib>), mặc dù chương trình vẫn chưa được hình thành hợp pháp.
Mike Kinghan

11
@Brian Bạn nên đề cập đến kiến ​​trúc khi đưa ra các câu lệnh như vậy. Tất cả thế giới không phải là VAX. Hoặc x86. Hay bất cứ cái gì.
dmckee --- cựu điều hành kitten

Câu trả lời:


84

Trước khi đi sâu vào phần nội dung của câu hỏi về những gì đang xảy ra, điều quan trọng là phải chỉ ra rằng chương trình đó không hợp lệ theo báo cáo lỗi 1886: Liên kết ngôn ngữ cho main () :

[...] Một chương trình khai báo một biến chính ở phạm vi toàn cục hoặc khai báo tên chính với liên kết ngôn ngữ C (trong bất kỳ không gian tên nào) là không đúng. [...]

Các phiên bản mới nhất của clang và gcc gây ra lỗi này và chương trình sẽ không biên dịch ( xem ví dụ trực tiếp về gcc ):

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

Vậy tại sao không có chẩn đoán trong các phiên bản cũ hơn của gcc và clang? Báo cáo khiếm khuyết này thậm chí không có giải pháp được đề xuất cho đến cuối năm 2014 và vì vậy trường hợp này chỉ mới được hình thành rõ ràng gần đây, cần được chẩn đoán.

Trước đó, có vẻ như đây sẽ là hành vi không xác định vì chúng tôi đang vi phạm yêu cầu phải có của tiêu chuẩn C ++ dự thảo từ phần 3.6.1 [basic.start.main] :

Một chương trình phải chứa một hàm toàn cục được gọi là hàm chính, là hàm khởi động được chỉ định của chương trình. [...]

Hành vi không xác định là không thể đoán trước và không cần chẩn đoán. Sự không nhất quán mà chúng ta thấy khi tái tạo hành vi là hành vi không xác định điển hình.

Vì vậy, mã thực sự đang làm gì và tại sao trong một số trường hợp, nó tạo ra kết quả? Hãy xem những gì chúng tôi có:

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

Chúng ta có mainmột int được khai báo trong không gian tên chung và đang được khởi tạo, biến có thời lượng lưu trữ tĩnh. Việc thực thi được xác định liệu quá trình khởi tạo có diễn ra trước khi thực hiện một cuộc gọi hay không mainnhưng có vẻ như gcc thực hiện điều này trước khi gọi main.

Đoạn mã sử dụng toán tử dấu phẩy , toán hạng bên trái là một biểu thức giá trị bị loại bỏ và được sử dụng ở đây chỉ cho tác dụng phụ của việc gọi std::cout. Kết quả của toán tử dấu phẩy là toán hạng bên phải, trong trường hợp này là giá trị prvalue 195được gán cho biến main.

Chúng ta có thể thấy sergej chỉ ra các chương trình lắp ráp được tạo ra coutđược gọi trong quá trình khởi tạo tĩnh. Mặc dù điểm thú vị hơn để thảo luận, hãy xem phiên hỗ trợ trực tiếp sẽ là:

main:
.zero   4

và tiếp theo:

movl    $195, main(%rip)

Tình huống có thể xảy ra là chương trình nhảy đến biểu tượng mainmong đợi mã hợp lệ ở đó và trong một số trường hợp sẽ xảy ra lỗi . Vì vậy, nếu trường hợp đó xảy ra, chúng tôi mong đợi việc lưu trữ mã máy hợp lệ trong biến maincó thể dẫn đến chương trình khả thi , giả sử chúng tôi đang ở trong một phân đoạn cho phép thực thi mã. Chúng ta có thể thấy mục IOCCC 1984 này làm được điều đó .

Có vẻ như chúng ta có thể sử dụng gcc để thực hiện việc này trong C ( xem trực tiếp ):

const int main = 195 ;

Sẽ xảy ra lỗi nếu biến maincó lẽ không phải là const vì nó không được đặt ở vị trí thực thi, Hat Mẹo cho nhận xét này ở đây đã cho tôi ý tưởng này.

Cũng xem câu trả lời FUZxxl tại đây cho phiên bản C cụ thể của câu hỏi này.


Tại sao việc triển khai cũng không đưa ra bất kỳ cảnh báo nào. (Khi tôi sử dụng -Wall & -Wextra nó vẫn không đưa ra cảnh báo duy nhất). Tại sao? Bạn nghĩ gì về câu trả lời của @Mark B cho câu hỏi này?
Kẻ hủy diệt

IMHO, trình biên dịch không nên đưa ra cảnh báo vì mainkhông phải là số nhận dạng dành riêng (3.6.1 / 3). Trong trường hợp này, tôi nghĩ cách xử lý của VS2013 đối với trường hợp này (xem câu trả lời của Francis Cugler) trong việc xử lý nó đúng hơn là gcc & clang.
cdmh

@PravasiMeet Tôi đã cập nhật wrt câu trả lời của mình về lý do tại sao các phiên bản trước đó của gcc không đưa ra chẩn đoán.
Shafik Yaghmour,

2
... và thực sự, khi tôi kiểm tra chương trình của OP trên Linux / x86-64, với g ++ 5.2 (chấp nhận chương trình - tôi đoán bạn không đùa về "phiên bản gần đây nhất"), nó bị lỗi đúng như tôi mong đợi sẽ.
zwol

1
@Walter Tôi không tin rằng đây là những bản sao mà trước đây đang đặt câu hỏi hẹp hơn nhiều. Rõ ràng là có một nhóm người dùng SO có quan điểm đơn giản hơn về các bản sao mà tôi không có ý nghĩa nhiều đối với tôi vì chúng tôi có thể tổng hợp hầu hết các câu hỏi SO thành một số phiên bản của các câu hỏi cũ hơn vì vậy SO sẽ không hữu ích lắm.
Shafik Yaghmour

20

Từ 3.6.1 / 1:

Một chương trình phải chứa một hàm toàn cục được gọi là hàm chính, là hàm khởi động được chỉ định của chương trình. Việc triển khai được xác định liệu một chương trình trong môi trường đích tự do có được yêu cầu để xác định một chức năng chính hay không.

Từ điều này, có vẻ như g ++ sẽ cho phép một chương trình (có lẽ là mệnh đề "tự do") mà không có hàm chính.

Sau đó từ 3.6.1 / 3:

Chức năng chính sẽ không được sử dụng (3.2) trong một chương trình. Mối liên kết (3.5) của main được xác định thực hiện. Một chương trình khai báo main là nội tuyến hoặc tĩnh không đúng định dạng. Tên chính không được bảo lưu.

Vì vậy, ở đây chúng ta biết rằng hoàn toàn tốt nếu có một biến số nguyên được đặt tên main.

Cuối cùng, nếu bạn đang thắc mắc tại sao đầu ra được in, thì việc khởi tạo hàm int mainsử dụng toán tử dấu phẩy để thực thi couttại init tĩnh và sau đó cung cấp một giá trị tích phân thực tế để thực hiện khởi tạo.


7
Thật thú vị khi lưu ý rằng liên kết thất bại nếu bạn đổi tên mainđể cái gì khác: (.text+0x20): undefined reference to chính `
Fred Larson

1
Bạn không phải chỉ định cho gcc rằng chương trình của bạn là đích tự do?
Shafik Yaghmour,

9

gcc 4.8.1 tạo lắp ráp x86 sau:

.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

Lưu ý rằng nó coutđược gọi trong quá trình khởi tạo, không phải trong mainhàm!

.zero 4khai báo 4 byte (0 được khởi tạo) bắt đầu từ vị trí main, ở đó mainlà tên của biến [!] .

mainhiệu được hiểu là thời điểm bắt đầu chương trình. Hành vi phụ thuộc vào nền tảng.


1
Lưu ý như Brian đã chỉ ra 195 là mã lựa chọn cho retmột số kiến ​​trúc. Vì vậy, nói hướng dẫn bằng không có thể không chính xác.
Shafik Yaghmour

@ShafikYaghmour Cảm ơn nhận xét của bạn, bạn đã đúng. Tôi đã bị rối với các chỉ thị của trình hợp dịch.
sergej

8

Đó là một chương trình sai lầm. Nó bị lỗi trên môi trường thử nghiệm của tôi, cygwin64 / g ++ 4.9.3.

Từ tiêu chuẩn:

3.6.1 Chức năng chính [basic.start.main]

1 Một chương trình phải chứa một hàm toàn cục được gọi là hàm chính, là hàm khởi động được chỉ định của chương trình.


Tôi nghĩ rằng trước báo cáo lỗi mà tôi đã trích dẫn, đây chỉ là hành vi đơn giản không xác định.
Shafik Yaghmour,

@ShafikYaghmour, Đó có phải là nguyên tắc chung được áp dụng tại tất cả những nơi việc sử dụng tiêu chuẩn có trách nhiệm ?
R Sahu

Tôi muốn nói có nhưng tôi không thấy mô tả tốt về sự khác biệt. Từ những gì tôi có thể nói từ cuộc thảo luận này , NDR không được hình thành và hành vi không xác định có thể đồng nghĩa với nhau vì không cần chẩn đoán. Điều này dường như ngụ ý không hợp lý và UB là khác biệt nhưng không chắc chắn.
Shafik Yaghmour

3
C99 phần 4 ("Sự phù hợp") làm cho điều này trở nên rõ ràng: "Nếu một yêu cầu 'sẽ' hoặc 'sẽ không' xuất hiện bên ngoài một ràng buộc bị vi phạm, hành vi đó là không xác định." Tôi không thể tìm thấy từ ngữ tương đương trong C ++ 98 hoặc C ++ 11, nhưng tôi thực sự nghi ngờ ủy ban có nghĩa là nó ở đó. (Các ủy ban C và C ++ thực sự cần phải ngồi xuống và sắt ra tất cả sự khác nhau về thuật ngữ giữa hai tiêu chuẩn.)
Zwol

7

Lý do tôi tin rằng điều này hiệu quả là trình biên dịch không biết nó đang biên dịch main()hàm nên nó biên dịch một số nguyên toàn cục với các tác dụng phụ của phép gán.

Các định dạng đối tượng rằng đây dịch đơn vị được biên dịch vào là không có khả năng phân biệt giữa một biểu tượng chức năng và một biểu tượng biến .

Vì vậy, trình liên kết vui vẻ liên kết với biểu tượng chính (biến) và coi nó như một lệnh gọi hàm. Nhưng không phải cho đến khi hệ thống thời gian chạy đã chạy mã khởi tạo biến toàn cục.

Khi tôi chạy mẫu, nó in ra nhưng sau đó nó gây ra lỗi seg . Tôi giả sử đó là khi hệ thống thời gian chạy cố gắng thực thi một biến int như thể nó là một hàm .


4

Tôi đã thử điều này trên hệ điều hành Win7 64bit sử dụng VS2013 và nó biên dịch chính xác nhưng khi tôi cố gắng xây dựng ứng dụng, tôi nhận được thông báo này từ cửa sổ đầu ra.

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, đó là lỗi trình liên kết, không phải thông báo từ trình gỡ lỗi. Việc lập đã thành công, nhưng mối liên kết không thể tìm thấy một chức năng main()bởi vì nó là một biến kiểuint
cdmh

Cảm ơn câu trả lời Tôi sẽ viết lại câu trả lời đầu tiên của mình để phản ánh điều này.
Francis Cugler

-1

Bạn đang làm công việc khó khăn ở đây. Vì main (bằng cách nào đó) có thể được khai báo là số nguyên. Bạn đã sử dụng toán tử danh sách để in tin nhắn và sau đó gán 195 cho nó. Như đã nói bởi ai đó bên dưới, rằng nó không thoải mái với C ++, là sự thật. Nhưng vì trình biên dịch không tìm thấy bất kỳ tên nào do người dùng xác định, main, nên nó không có gì đáng phàn nàn. Hãy nhớ rằng main không phải là chức năng do hệ thống xác định, chức năng do người dùng định nghĩa và thứ mà từ đó chương trình bắt đầu thực thi là Mô-đun chính, không phải main (), cụ thể. Một lần nữa hàm main () được gọi bởi hàm khởi động được thực thi bởi bộ nạp có chủ đích. Sau đó, tất cả các biến của bạn được khởi tạo và trong khi khởi tạo, nó sẽ xuất ra như vậy. Đó là nó. Chương trình không có main () là ok, nhưng không chuẩn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.