main関数の代わりにmainと呼ばれるグローバル変数を持つプログラムはどのように機能しますか?


97

次のプログラムを検討してください:

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

Windows 7 OSでg ++ 4.8.1(mingw64)を使用すると、プログラムは正常にコンパイルおよび実行され、印刷されます。

C ++は素晴らしいです!

コンソールに。main関数ではなくグローバル変数のように見えます。このプログラムを関数なしでどのように実行できますmain()か?このコードはC ++標準に準拠していますか?プログラムの動作は明確に定義されていますか?私も-pedantic-errorsオプションを使用しましたが、プログラムはまだコンパイルして実行されます。


11
@πάνταῥεῖ:なぜ言語弁護士タグが必要なのですか?
デストラクタ

14
195は命令のオペコードでありRET、Cの呼び出し規約では、呼び出し元がスタックをクリアすることに注意してください。
ブライアン

2
@PravasiMeet「その後、どのようにこのプログラムを実行」 - (さえずに変数の初期化コードが実行されなければならないとは思わないmain()?。実際には、彼らは完全に無関係です機能)
常磁性クロワッサン

4
私は、プログラムがそのままsegfaultする(64ビットlinux、g ++ 5.1 / clang 3.6)ことを発見した人の1人です。ただし、プログラムの法的な形式に問題はありますが、これをint main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(を含めて<cstdlib>)修正することで修正できます。
Mike Kinghan、2015

11
@ブライアンそのようなステートメントを作成するときは、アーキテクチャについて言及する必要があります。世界中がVAXではありません。またはx86。または何でも。
dmckee ---元モデレーターの子猫2015

回答:


84

何が起こっているのかという問題の詳細に入る前に、プログラムが欠陥レポート1886:main()の言語リンケージに従って不正な形式であることを指摘することが重要です。

[...]グローバルスコープで変数mainを宣言するプログラム、または(任意の名前空間で)C言語リンケージを使用して名前mainを宣言するプログラムは、不正な形式です。[...]

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年後半まで解決策が提案されていなかったため、このケースはごく最近明確に不正であり、診断が必要です。

これより前は、セクション[basic.start.main]の C ++標準ドラフトのshall要件に違反しているため、これは未定義の動作のよう3.6.1 です

プログラムには、プログラムの指定された開始点である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。コンマ演算子の結果は、右側のオペランド195ですmain。この場合、変数に割り当てられているprvalue です。

sergejがcout静的な初期化中に呼び出される生成されたアセンブリショーを指摘しているのがわかります。議論のためのより興味深いポイントがライブゴッドボルトセッション参照してくださいがこれは次のようになります:

main:
.zero   4

以降:

movl    $195, main(%rip)

考えられるシナリオは、プログラムがmain有効なコードが存在することを期待してシンボルにジャンプし、場合によってはseg-faultになることです。そのmainため、コードの実行を許可するセグメント内にいると仮定すると、変数に有効なマシンコードを格納すると、実行可能なプログラムにつながる可能性があると予想されます。この1984年のIOCCCエントリまさにそれを行っていることがわかります。

これを使用してCでgccにこれを実行させることができるようです(実際に見る):

const int main = 195 ;

変数mainが実行可能な場所に配置されていないために変数がconstでない場合、セグメンテーションフォールトが発生します。このコメントへのハットヒント。

また、この質問のC固有のバージョンに対するFUZxxlの回答も参照してください。


実装が警告も出さない理由。(-Wallと-Wextraを使用しても、1つの警告が表示されません)。どうして?この質問に対する@Mark Bの回答についてどう思いますか?
デストラクタ

私見、main予約された識別子(3.6.1 / 3)ではないため、コンパイラーは警告を出すべきではありません。この場合、VS2013のこのケースの処理(Francis Cuglerの回答を参照)は、gccとclangよりも処理が正しいと思います。
cdmh 2015

@PravasiMeet以前のバージョンのgccが診断を提供しなかった理由について、回答を更新しました。
Shafik Yaghmour 2015

2
...そして確かに、g ++ 5.2でLinux / x86-64でOPのプログラムをテストすると(プログラムを受け入れます-「最新バージョン」について冗談ではなかったと思います)、期待したとおりにクラッシュします。だろう。
zwol

1
@ウォルターこれらは前者がはるかに狭い質問をしている重複であるとは思わない。ほとんどのSOの質問を古いバージョンの質問に要約できるので、SOはあまり役に立たないので、重複についてより還元主義的な見方をするSOユーザーのグループが明らかに存在します。
Shafik Yaghmour、2015年

20

3.6.1 / 1以降:

プログラムには、プログラムの指定された開始点であるmainと呼ばれるグローバル関数が含まれます。メイン機能を定義するために自立環境のプログラムが必要かどうかは、実装で定義されます。

このことから、g​​ ++はメイン関数なしでプログラムを(おそらく「自立」節として)許可するように思われます。

次に3.6.1 / 3から:

関数mainはプログラム内では使用しない(3.2)。mainのリンク(3.5)は実装定義です。mainをインラインまたは静的であると宣言するプログラムは正しくありません。mainという名前は、他に予約されていません。

したがって、ここでは、という名前の整数変数を使用してもまったく問題ないことがわかりますmain

最後に、なぜ出力が出力されるのか疑問に思っている場合、の初期化でint mainは、カンマ演算子を使用coutして静的initで実行し、実際の整数値を指定して初期化を行います。


7
それはあなたが名前を変更する場合にはリンクが失敗したことに注意することは興味深いmain何か他のものに:(.text+0x20): undefined reference to メイン`
フレッド・ラーソン

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が初期化)バイトを宣言mainmainの名前である可変[!]

mainシンボルは、プログラムの開始として解釈されます。動作はプラットフォームによって異なります。


1
よう注意ブライアンは指摘するが 195ためのオペコードであるretいくつかのアーキテクチャ上。したがって、ゼロ指示と言っても正確ではない可能性があります。
Shafik Yaghmour、2015年

@ShafikYaghmourコメントありがとうございます。アセンブラディレクティブにめちゃくちゃになりました。
sergej

8

それは不正なプログラムです。テスト環境cygwin64 / g ++ 4.9.3でクラッシュします。

標準から:

3.6.1メイン関数 [basic.start.main]

1プログラムには、プログラムの指定された開始点であるmainと呼ばれるグローバル関数が含まれます。


私が引用した欠陥レポートの前に、これは単なる未定義の動作であったと思います。
Shafik Yaghmour

@ShafikYaghmour、それは標準が使用するすべての場所で適用される一般原則ですか?
R Sahu

「はい」と言いたいのですが、違いの説明がよくわかりません。このディスカッションでわかることから、不正なNDRと未定義の動作はどちらも診断を必要としないため、おそらく同義です。これは不正な形式を意味するように思われ、UBは明確ですが確実ではありません。
Shafik Yaghmour、2015年

3
C99セクション4(「適合性」)により、これが明確になります。「制約の外に現れる 'shall'または 'shall not'要件に違反した場合の動作は未定義です。」C ++ 98またはC ++ 11で同等の表現を見つけることはできませんが、委員会がそこにあることを意図していると強く思います。(CおよびC ++委員会は、実際に座り、2つの標準間のすべての用語の違いを解決する必要があります。)
zwol

7

これが機能すると私が思う理由は、コンパイラmain()関数をコンパイルしていることを知らないため、割り当ての副作用を伴うグローバル整数をコンパイルするためです。

オブジェクトフォーマットこのこと翻訳単位がにコンパイルさは区別することができない関数記号変数記号

したがって、リンカは(変数の)メインシンボルにうまくリンクし、関数呼び出しのように扱います。しかし、ランタイムシステムがグローバル変数の初期化コードを実行するまでは。

サンプルを実行すると印刷されましたが、セグメンテーション違反が発生しましたランタイムシステムint変数関数のように実行しようとしたときだと思います


4

VS2013を使用してWin7 64ビットOSでこれを試しましたが、正しくコンパイルされますが、アプリケーションをビルドしようとすると、出力ウィンドウからこのメッセージが表示されます。

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、2015年

-1

あなたはここでトリッキーな仕事をしています。main(どういうわけか)が整数であると宣言できたので。リスト演算子を使用してメッセージを出力し、それに195を割り当てました。以下の誰かが言ったように、それはC ++では快適ではないということは本当です。しかし、コンパイラはユーザー定義の名前mainを見つけられなかったので、問題はありませんでした。mainはシステム定義の関数ではなく、そのユーザー定義の関数とプログラムが実行を開始するものは、特にMain()ではなくMain Moduleであることを覚えておいてください。再度main()は、ローダーによって意図的に実行される起動関数によって呼び出されます。次に、すべての変数が初期化され、初期化中にそのように出力されます。それでおしまい。main()のないプログラムは問題ありませんが、標準ではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.