Là main () thực sự bắt đầu một chương trình C ++?


131

Phần $ 3,6,1 / 1 từ Tiêu chuẩn C ++ đọc,

Một chương trình sẽ chứa một hàm toàn cục gọi là main , là khởi đầu được chỉ định của chương trình.

Bây giờ hãy xem xét mã này,

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

Mã mẫu này thực hiện những gì tôi dự định làm, tức là in bình phương số nguyên từ 0 đến 9, trước khi nhập vào main()hàm được coi là "bắt đầu" của chương trình.

Tôi cũng đã biên dịch nó với -pedantictùy chọn, GCC 4.5.0. Nó không có lỗi, thậm chí không cảnh báo!

Vì vậy, câu hỏi của tôi là,

Là mã này thực sự phù hợp tiêu chuẩn?

Nếu đó là tiêu chuẩn phù hợp, thì nó không làm mất hiệu lực những gì Tiêu chuẩn nói? main()không bắt đầu chương trình này! user_main()thực hiện trước main().

Tôi hiểu rằng để khởi tạo biến toàn cục main_ret, use_main()trước tiên thực hiện nhưng đó là một điều hoàn toàn khác; vấn đề là, nó làm mất hiệu lực tuyên bố được trích dẫn $ 3,6,1 / 1 từ Tiêu chuẩn, vì main()KHÔNG phải là bắt đầu của chương trình; nó là trong thực tế cuối của này chương trình!


BIÊN TẬP:

Làm thế nào để bạn xác định từ 'bắt đầu'?

Nó nắm rõ định nghĩa của cụm từ "bắt đầu chương trình" . Vì vậy, làm thế nào chính xác để bạn xác định nó?

Câu trả lời:


85

Không, C ++ thực hiện rất nhiều việc để "thiết lập môi trường" trước lệnh gọi của main; tuy nhiên, chính là phần bắt đầu chính thức của phần "người dùng chỉ định" của chương trình C ++.

Một số thiết lập môi trường không thể kiểm soát được (như mã ban đầu để thiết lập std :: cout; tuy nhiên, một số môi trường có thể được kiểm soát như các khối toàn cầu tĩnh (để khởi tạo các biến toàn cục tĩnh). Lưu ý rằng vì bạn không có đầy đủ kiểm soát trước chính, bạn không có toàn quyền kiểm soát thứ tự các khối tĩnh được khởi tạo.

Sau khi chính, mã của bạn về mặt khái niệm "hoàn toàn kiểm soát" chương trình, theo nghĩa là bạn có thể vừa xác định các hướng dẫn sẽ được thực hiện và thứ tự thực hiện chúng. Đa luồng có thể sắp xếp lại thứ tự thực thi mã; nhưng, bạn vẫn kiểm soát được C ++ vì bạn đã chỉ định có các phần thực thi mã (có thể) không theo thứ tự.


9
+1 cho điều này "Lưu ý rằng vì bạn không có toàn quyền kiểm soát trước chính, nên bạn không có toàn quyền kiểm soát thứ tự các khối tĩnh được khởi tạo. Sau khi chính, mã của bạn về mặt khái niệm" hoàn toàn kiểm soát " theo nghĩa là bạn có thể chỉ định cả hai hướng dẫn sẽ được thực hiện và thứ tự thực hiện chúng " . Điều này cũng khiến tôi đánh dấu câu trả lời này là câu trả lời được chấp nhận ... Tôi nghĩ đây là những điểm rất quan trọng, đủ để biện minh main()"bắt đầu chương trình"
Nawaz

13
@Nawaz: lưu ý rằng trên hết không có quyền kiểm soát hoàn toàn đối với thứ tự khởi tạo, bạn không có quyền kiểm soát đối với các lỗi khởi tạo: bạn không thể bắt ngoại lệ ở phạm vi toàn cầu.
André Caron

@Nawaz: Khối toàn cầu tĩnh là gì? bạn sẽ giải thích nó bằng cách sử dụng ví dụ đơn giản? Cảm ơn
Kẻ hủy diệt

@meet: Các đối tượng được khai báo ở cấp không gian tên có staticthời lượng lưu trữ và do đó, các đối tượng thuộc các đơn vị dịch khác nhau có thể được khởi tạo theo bất kỳ thứ tự nào (vì thứ tự không được xác định theo tiêu chuẩn). Tôi không chắc nếu câu trả lời cho câu hỏi của bạn, mặc dù đó là những gì tôi có thể nói trong bối cảnh của chủ đề này.
Nawaz

88

Bạn đang đọc câu không chính xác.

Một chương trình sẽ chứa một hàm toàn cục gọi là main, là khởi đầu được chỉ định của chương trình.

Tiêu chuẩn là xác định từ "bắt đầu" cho các mục đích còn lại của tiêu chuẩn. Nó không nói rằng không có mã thực thi trước mainđược gọi. Nó nói rằng sự khởi đầu của chương trình được coi là tại chức năng main.

Chương trình của bạn tuân thủ. Chương trình của bạn chưa "bắt đầu" cho đến khi chính được bắt đầu. Hàm tạo được gọi trước khi chương trình của bạn "bắt đầu" theo định nghĩa của "bắt đầu" trong tiêu chuẩn, nhưng điều đó hầu như không quan trọng. A LOT của mã được thực thi trước mainđược bao giờ gọi trong mọi chương trình, không chỉ là ví dụ này.

Đối với mục đích thảo luận, mã nhà xây dựng của bạn được thực thi trước khi bắt đầu 'chương trình' và điều đó hoàn toàn tuân thủ tiêu chuẩn.


3
Xin lỗi, nhưng tôi không đồng ý với cách giải thích của bạn về điều khoản đó.
Các cuộc đua Lightness trong Orbit

Tôi nghĩ Adam Davis đã đúng, "chính" giống như một số hạn chế về mã hóa.
laike9m

@LightnessRacesinOrbit Tôi chưa bao giờ theo dõi, nhưng với tôi câu đó có thể được rút ngắn một cách hợp lý thành "một chức năng toàn cầu được gọi là chính là sự khởi đầu được chỉ định của chương trình" (nhấn mạnh thêm). Giải thích của bạn về câu đó là gì?
Adam Davis

1
@AdamDavis: Tôi không nhớ mối quan tâm của mình là gì. Tôi không thể nghĩ về một cái bây giờ.
Các cuộc đua nhẹ nhàng trong quỹ đạo

23

Chương trình của bạn sẽ không liên kết và do đó không chạy trừ khi có chính. Tuy nhiên main () không gây ra sự bắt đầu thực thi chương trình vì các đối tượng ở cấp độ tệp có các hàm tạo chạy trước và có thể viết toàn bộ chương trình chạy trọn đời trước khi đạt tới main () và để chính nó có Một cơ thể trống rỗng.

Trong thực tế để thực thi điều này, bạn sẽ phải có một đối tượng được xây dựng trước chính và hàm tạo của nó để gọi tất cả các luồng của chương trình.

Nhìn vào cái này

class Foo
{
public:
   Foo();

 // other stuff
};

Foo foo;

int main()
{
}

Luồng chương trình của bạn có hiệu quả bắt nguồn từ Foo::Foo()


13
+1. Nhưng lưu ý rằng nếu bạn có nhiều đối tượng toàn cầu trong các đơn vị dịch thuật khác nhau, điều này sẽ khiến bạn gặp rắc rối nhanh chóng vì thứ tự các hàm tạo được gọi là không xác định. Bạn có thể thoát khỏi singletons và lười khởi tạo, nhưng trong một môi trường đa luồng, mọi thứ trở nên rất xấu xí nhanh chóng. Trong một từ, đừng làm điều này trong mã thực.
Alexandre C.

3
Trong khi bạn có thể cung cấp cho phần chính () một phần chính xác trong mã của mình và cho phép nó chạy thực thi, khái niệm về các đối tượng bên ngoài khởi động là thứ mà rất nhiều thư viện LD_PRELOAD dựa vào.
CashCow

2
@Alex: Tiêu chuẩn nói không xác định, nhưng là một thứ tự liên kết vật chất thực tế (thông thường, tùy thuộc vào trình biên dịch) tạo ra thứ tự khởi tạo.
ThomasMcLeod

1
@Thomas: Tôi chắc chắn sẽ không cố gắng dựa vào điều đó. Tôi cũng chắc chắn sẽ không cố gắng tự điều khiển hệ thống xây dựng.
Alexandre C.

1
@Alex: không còn quá quan trọng nữa, nhưng trước đây chúng ta sẽ sử dụng thứ tự liên kết để kiểm soát hình ảnh bản dựng để giảm phân trang bộ nhớ vật lý. Có những lý do phụ khác mà bạn có thể muốn kiểm soát thứ tự khởi tạo ngay cả khi nó không ảnh hưởng đến ngữ nghĩa chương trình, chẳng hạn như kiểm tra so sánh hiệu suất khởi động.
ThomasMcLeod

15

Bạn cũng đã gắn thẻ câu hỏi là "C", sau đó, nói đúng về C, việc khởi tạo của bạn sẽ thất bại theo mục 6.7.8 "Khởi tạo" của tiêu chuẩn ISO C99.

Liên quan nhất trong trường hợp này dường như là ràng buộc # 4 có nội dung:

Tất cả các biểu thức trong bộ khởi tạo cho một đối tượng có thời lượng lưu trữ tĩnh phải là biểu thức không đổi hoặc chuỗi ký tự.

Vì vậy, câu trả lời cho câu hỏi của bạn là mã không tuân thủ tiêu chuẩn C.

Bạn có thể muốn xóa thẻ "C" nếu bạn chỉ quan tâm đến tiêu chuẩn C ++.


4
@ Remo.D bạn có thể cho chúng tôi biết những gì trong phần đó. Không phải tất cả chúng ta đều có tiêu chuẩn C :).
UmmaGumma

2
Vì bạn rất kén chọn: Than ôi, ANSI C đã lỗi thời từ năm 1989. ISO C90 hoặc C99 là các tiêu chuẩn có liên quan để trích dẫn.
Lundin

@Lundin: Không ai đủ kén chọn cả :) Tôi đã đọc ISO C99 nhưng tôi khá tự tin nó cũng áp dụng cho C90.
Remo.D

@ Ảnh chụp. Bạn nói đúng, thêm câu mà tôi nghĩ là có liên quan nhất ở đây.
Remo.D

3
@Remo: +1 để cung cấp thông tin không hợp lệ C; tôi không biết điều đó Xem đây là cách mọi người học, đôi khi theo kế hoạch, đôi khi tình cờ!
Nawaz

10

Phần 3.6 nói chung rất rõ ràng về sự tương tác mainvà khởi tạo động. "Bắt đầu chương trình được chỉ định" không được sử dụng ở bất kỳ nơi nào khác và chỉ là mô tả về mục đích chung của main(). Không có nghĩa gì để giải thích rằng một cụm từ theo cách quy phạm mâu thuẫn với các yêu cầu chi tiết và rõ ràng hơn trong Tiêu chuẩn.


9

Trình biên dịch thường phải thêm mã trước main () để được phù hợp tiêu chuẩn. Bởi vì tiêu chuẩn quy định rằng việc vô hiệu hóa toàn cầu / thống kê phải được thực hiện trước khi chương trình được thực thi. Và như đã đề cập, điều tương tự cũng xảy ra với các hàm tạo của các đối tượng được đặt ở phạm vi tệp (toàn cục).

Do đó, câu hỏi ban đầu cũng có liên quan đến C, bởi vì trong chương trình C, bạn vẫn sẽ phải khởi tạo toàn cầu / khởi tạo tĩnh trước khi chương trình có thể được khởi động.

Các tiêu chuẩn cho rằng các biến này được khởi tạo thông qua "ma thuật", vì chúng không cho biết chúng nên được đặt như thế nào trước khi khởi tạo chương trình. Tôi nghĩ rằng họ coi đó là một cái gì đó nằm ngoài phạm vi của một tiêu chuẩn ngôn ngữ lập trình.

Chỉnh sửa: Xem ví dụ ISO 9899: 1999 5.1.2:

Tất cả các đối tượng có thời lượng lưu trữ tĩnh sẽ được khởi tạo (được đặt thành giá trị ban đầu của chúng) trước khi khởi động chương trình. Cách thức và thời gian khởi tạo như vậy là không xác định.

Lý thuyết đằng sau việc "phép thuật" này được thực hiện bắt nguồn từ sự ra đời của C, khi đó là ngôn ngữ lập trình chỉ được sử dụng cho HĐH UNIX, trên các máy tính chạy RAM. Về lý thuyết, chương trình sẽ có thể tải tất cả dữ liệu được khởi tạo trước từ tệp thực thi vào RAM, cùng lúc với chính chương trình được tải lên RAM.

Kể từ đó, máy tính và HĐH đã phát triển và C được sử dụng ở một khu vực rộng hơn nhiều so với dự đoán ban đầu. Một hệ điều hành PC hiện đại có địa chỉ ảo, v.v. và tất cả các hệ thống nhúng thực thi mã từ ROM, không phải RAM. Vì vậy, có nhiều tình huống mà RAM không thể được đặt "tự động".

Ngoài ra, tiêu chuẩn này quá trừu tượng để biết bất cứ điều gì về ngăn xếp và xử lý bộ nhớ, v.v. Những điều này cũng phải được thực hiện trước khi chương trình được bắt đầu.

Do đó, khá nhiều chương trình C / C ++ có một số mã init / "copy-down" được thực thi trước khi main được gọi, để tuân thủ các quy tắc khởi tạo của các tiêu chuẩn.

Ví dụ, các hệ thống nhúng thường có một tùy chọn gọi là "khởi động không tuân thủ ISO" trong đó toàn bộ giai đoạn khởi tạo bị bỏ qua vì lý do hiệu năng và sau đó mã thực sự bắt đầu trực tiếp từ chính. Nhưng các hệ thống như vậy không tuân thủ các tiêu chuẩn, vì bạn không thể dựa vào các giá trị init của các biến toàn cục / tĩnh.


4

"Chương trình" của bạn chỉ đơn giản trả về một giá trị từ một biến toàn cục. Mọi thứ khác là mã khởi tạo. Do đó, các tiêu chuẩn giữ - bạn chỉ cần có một chương trình rất nhỏ và khởi tạo phức tạp hơn.



2

Có vẻ như một ngữ nghĩa tiếng Anh ngụy biện. OP trước tiên gọi khối mã của mình là "mã" và sau là "chương trình". Người dùng viết mã, và sau đó trình biên dịch viết chương trình.


1

main được gọi sau khi khởi tạo tất cả các biến toàn cục.

Điều mà tiêu chuẩn không chỉ định là thứ tự khởi tạo của tất cả các biến toàn cục của tất cả các mô-đun và thư viện được liên kết tĩnh.


0

Có, chính là "điểm vào" của mọi chương trình C ++, ngoại trừ các phần mở rộng dành riêng cho việc triển khai. Mặc dù vậy, một số điều xảy ra trước chính, đáng chú ý là khởi tạo toàn cầu, chẳng hạn như đối với main_ret.

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.