web-dev-qa-db-ja.com

main()関数を使用せずにプログラムを書くことは可能ですか?

私はインタビューでこの質問をし続けています:

main() functionを使用せずにプログラムを作成しますか?

友人の一人がマクロを使用してコードを見せてくれましたが、理解できませんでした。

質問は次のとおりです。

main()?なしでプログラムを書いてコンパイルすることは本当に可能ですか?

32
Expert Novice

標準C++内ではmain関数が必要であるため、この質問は標準C++には意味がありません。

標準C++の外部では、たとえばWindows固有のプログラムを記述し、Microsoftのカスタムスタートアップ関数(wMain、winMain、wWinmain)のいずれかを使用できます。 Windowsでは、プログラムをDLLとして記述し、rundll32を使用して実行することもできます。

それとは別に、独自の小さなランタイムライブラリを作成できます。かつてそれは一般的なスポーツでした。

最後に、標準のODRルールに従ってmainが「使用」されていないため、どのプログラムも対象となるため、賢く取得してレトルトできます。ああ!インタビュアーが異常なユーモアのセンスを持っていなければ(そして、もし持っていたら質問をしなかったでしょうが)、彼らはそれが良い答えだとは思わないでしょう。

17

開始点がmain()である必要のない_freestanding environment_(組み込み環境OSカーネルなど)でプログラムを作成している場合を除き、できません。 C++標準に従って、main()は_hosted environment_内のプログラムの開始点です。

次のように:

C++ 03標準.6.1メイン関数

1プログラムには、プログラムの指定された開始であるmainというグローバル関数が含まれます。独立した環境のプログラムがメイン関数を定義するために必要かどうかは、実装で定義されます。 [注:独立した環境では、起動と終了は実装定義です。スタートアップには、静的ストレージ期間を持つ名前空間スコープのオブジェクトのコンストラクターの実行が含まれます。終了には、静的ストレージ期間を持つオブジェクトのデストラクタの実行が含まれます。


_freestanding Environment_とは&_Hosted Environment_とは?
C++標準には、2種類の準拠する実装が定義されています。 hostedおよびfreestanding

freestanding実装は、オペレーティングシステムの利点なしで実行されるプログラム用に設計されたものです。
例:OSカーネルまたは組み込み環境は、独立した環境です。

オペレーティングシステムの機能を使用するプログラムは、通常_hosted implementation_にあります。

C++ 03標準からセクション1.4/7:

自立型の実装とは、オペレーティングシステムの恩恵を受けずに実行できる実装であり、特定の言語サポートライブラリを含む実装定義のライブラリセットを備えています。

さらに、
セクション:17.4.1.3.2独立した実装引用符:

独立した実装には、実装定義のヘッダーセットがあります。このセットには、表に示すように、少なくとも次のヘッダーが含まれます。

_18.1 Types <cstddef>   
18.2 Implementation properties <limits>   
18.3 Start and termination <cstdlib> 
18.4 Dynamic memory management <new> 
18.5 Type identification <typeinfo> 
18.6 Exception handling <exception> 
18.7 Other runtime support <cstdarg>
_
27
Alok Save

visibleメイン関数なしのサンプルプログラム。

/* 
    7050925.c 
    $ gcc -o 7050925 7050925.c
*/

#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
        printf("How mainless!\n");
}

From: http://learnhacking.in/c-program-without-main-function/

15
miku

mainは、コードの実行を開始するエントリポイントを意味します。 mainは最初に実行する関数ではありませんが。 mainの前に実行され、コードを実行するための環境を準備するコードがいくつかあります。次に、このコードはmainを呼び出します。スタートアップファイルcrt0.cのコードを再コンパイルし、main関数の名前を変更することにより、main関数の名前を変更できます。または、次のことを実行できます。

#include <stdio.h>

extern void _exit (register int code);

_start()
{
  int retval;
  retval = my_main ();
  _exit(retval);
}

int my_main(void)
{
  printf("Hello\n");
  return 0;
}

以下を使用してコードをコンパイルします。

gcc -o no_main no_main.c -nostartfiles

-nostartfilesにはデフォルトの起動ファイルは含まれません。 _startを使用してメインエントリファイルをポイントします。

mainは、ユーザーコードの定義済みエントリポイントにすぎません。したがって、任意の名前を付けることができますが、一日の終わりにはエントリポイントが必要です。 C/C++およびその他の言語では、名前はmainとして選択されます。別の言語を作成するか、これらの言語コンパイラーのソースをハックすると、mainの名前をpainに変更できますが、標準に違反するため、苦痛をもたらします。

ただし、エントリ関数名の操作は、カーネルコード、カーネルで実行する最初の関数、または組み込みシステム用に記述されたコードに役立ちます。

11
phoxis

独立した実装用に作成されたプログラムを参照する場合があります。 C++標準では、2種類の実装が定義されています。 1つはhosted実装です。これらの実装用に作成されたプログラムには、main関数が必要です。しかし、それ以外の場合、独立した実装が必要としない場合、main関数は必要ありません。これは、オペレーティングシステムカーネルまたはオペレーティングシステムで実行されない組み込みシステムプログラムに役立ちます。

はい

$ cat > hwa.S
write = 0x04
exit  = 0xfc
.text
_start:
        movl    $1, %ebx
        lea     str, %ecx
        movl    $len, %edx
        movl    $write, %eax
        int     $0x80
        xorl    %ebx, %ebx
        movl    $exit, %eax
        int     $0x80
.data
str:    .ascii "Hello, world!\n"
len = . -str
.globl  _start
$ as -o hwa.o hwa.S
$ ld hwa.o
$ ./a.out
Hello, world!

実行可能ファイルを実際に実行するカーネルは、内部シンボルについては何も知らず、実行可能イメージヘッダーのバイナリで指定されたエントリポイントに転送するだけです。

メインが必要な理由は、通常「メインプログラム」が実際には単なる別のモジュールだからです。エントリポイントは、Cとアセンブリの組み合わせで記述されたライブラリ提供のスタートアップコードにあり、そのライブラリコードはたまたまmainを呼び出すため、通常は提供する必要があります。ただし、リンカを直接実行してください。

Cモジュールを含めるには1...

Mac:~/so$ cat > nomain.S
.text
.globl start
start:
        call   _notmain
Mac:~/so$ as -o nomain.o nomain.S
Mac:~/so$ cat > notmain.c
#include <unistd.h>

void notmain(void) {
  write(1, "hi\n", 3);
  _exit(0);
}
Mac:~/so$ cc -c notmain.c
Mac:~/so$ ld -w nomain.o notmain.o -lc
Mac:~/so$ ./a.out
hi

1.また、ここでx86-64に切り替えています。
5
DigitalRoss

はい、メインなしでコンパイルすることは可能ですが、リンクフェーズを渡すことはできません。

 g++ -c noMain.cpp -o noMain.o
5
Mahesh

mainを使用しない」ことは、main内でロジックが許可されていないが、main自体が存在することも意味します。私はこの問題が解決されたと想像できますが、ここでは解決されていないため、これは別の可能な答えです:

struct MainSub
{
   MainSub()
   {
      // do some stuff
   }
};

MainSub mainSub;

int main(int argc, char *argv[]) { return 0; }

ここで何が起こるかというと、使用できないMainSubが実行される前にmainのコンストラクターの内容が実行され、そこにプログラムのロジックを配置できるということです。もちろんこれにはCではなくC++が必要です(質問からも明らかではありません)。

3
eran

G ++を使用している限り、リンカオプション_-e_を使用してエントリポイントを変更できるため、次のコードとコンパイルコマンドを使用すると、main()関数なしでプログラムを作成できます。

_#import <iostream>

class NoMain
{
public:
    NoMain()
    {
        std::cout << "Hello World!" << std::endl;
        exit(0);
    }
} mainClass;
_

ファイル名を_noname.cpp_として指定しました。コンパイルオプションは次のとおりです。

_g++ nomain.cpp -Wl,-e,_mainClass -v
_

実を言うと、コードがうまく機能する理由を完全には理解していませんでした。グローバル変数mainClassのアドレスは、NoMainクラスのコンストラクターと同じだと思います。ただし、推測が正しくない可能性があると判断できる理由もいくつかあります。

2
Joohae Kim

マクロ参照はメイン関数の名前変更であると思いますが、以下は私のコードではなく、これを示しています。コンパイラーはまだメイン関数を見ていますが、技術的にはソースの観点からメインはありません。ここにあります http://www.exforsys.com/forum/c-and-c/96849-without-main-function-how-post412181.html#post412181

#include<stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
  printf(" hello ");
}
2
eon

特定の言語標準を無視して、ほとんどのリンクローダーは、バイナリがメモリにロードされるときに実行する必要のある関数名(エントリポイント)を宣言する手段を提供します。

古い学校のc言語の場合、デフォルトは、いわゆるcrt(cランタイム?)で定義された 'start'または '_start'のようなもので、メモリヒープの準備、静的変数の初期化など、cの標準機能に必要ないくつかの家計の仕事を行います領域、コマンドラインをargc/argvなどに解析します。

これらの家庭用のものを必要とする標準関数(たとえば、malloc()、free()、printf()、クラス定義にカスタムコンストラクターがあるなど)を使用しないように十分に注意する場合、おそらくエントリポイント関数をオーバーライドできます。標準的なcランタイムではなくo/sが提供する機能を使用する場合、制限はありますが不可能ではありません。

たとえば、記述子1でwrite()関数を使用して単純なhelloworldを作成できます。

1
shr

CまたはC++コードが実行されると、既知の開始アドレスで実行され、ここでのコードはランタイム環境を初期化し、スタックポインターを初期化し、データの初期化を実行し、静的コンストラクターを呼び出しますthen jumps to main( )。

これを行うコードは、ビルド時にリンカによってコードとリンクされます。 GCCでは、通常はcrt0.sにありますが、市販のコンパイラーでは、このコードが使用可能になることはほとんどありません。

最後に、どこかで開始する必要があり、main()はその場所の単なるシンボル名です。開発者がそれを何と呼ぶか​​がわかるように言語標準で指定されています。そうしないと、コードがツールチェーン間で移植できなくなります。

OSがない、またはプロセスローダーの意味で少なくともOSがない「ベアメタル」システムのコードを記述している場合(組み込みシステムには、多くの場合、RTOS after main())、その後、通常は実行時の起動コードを完全に制御できるので、理論的にはCコードのエントリポイントを好きなように呼び出すことができます。 。

いくつかのRTOS VxWorksなどの環境、およびほとんどのアプリケーションフレームワークは一般にmain())またはその同等物を独自のライブラリコード内に含むため、ユーザーアプリケーションコードの前に実行されます。たとえば、VxWorksアプリケーションはusrAppInit()から起動し、Win32アプリケーションはWinMain()から起動します。

1
Clifford

これは古い質問であることに気づきましたが、私はこれを見つけて共有しなければなりませんでした。おそらくすべてのリンカーで動作するわけではありませんが、少なくともld(バージョン2.24.51.20140918を実行しています)をだまして、main -functionがあると考えさせることは可能です。 :

_int main[] {};
_

または単に

_int main;
_

その後、前述のトリックのいずれかを適用して、プログラムにコードを実行させることができます。コンストラクターを使用して:

_struct Main
{
    Main()
    {
        cout << "Hello World!\n";
        exit(0);
    }
} main_;
_

exit(0)は、配列が「呼び出される」のを防ぐためのものです。楽しい :-)

1
JorenHeit

はい、main()なしでプログラムを書くことができます。

ただし、main()を間接的に使用します。

次のプログラムはあなたが理解するのに役立ちます。

#include<stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,r,e)
int begin()
{
printf(” you are inside main() which is hidden“);
}

「##」演算子は、トークン貼り付けまたはトークンマージ演算子と呼ばれます。つまり、2つ以上の文字をマージできます。

プログラムの2行目

decode(s、t、u、m、p、e、d)m ## s ## u ## tを定義する

ここでプリプロセッサは何をしていますか。マクロdecode(s、t、u、m、p、e、d)は「msut」として展開されています(##演算子はm、s、u&tをmsutにマージします)。ロジックは、引数として(s、t、u、m、p、e、d)を渡すと、4番目、1番目、3番目、および2番目の文字(トークン)をマージします

次に、プログラムの3行目を見てください。

デコードの開始を定義(a、n、i、m、a、r、e)

ここで、プリプロセッサはマクロ「begin」を展開decode(a、n、i、m、a、r、e)に置き換えます。前の行のマクロ定義によれば、4番目、1番目、3番目、および2番目の文字がマージされるように引数を展開する必要があります。引数(a、n、i、m、a、r、e)では、4番目、1番目、3番目、2番目の文字は「m」、「a」、「i」、「n」です。

そのため、プログラムがコンパイラに渡される前に、プリプロセッサによってbegin()によってbeginが置き換えられます。それでおしまい…

クラスを作成し、そのクラスのコンストラクターで名前を出力し、そのクラスのGLOBAL OBJECTを宣言します。そのため、クラスのコンストラクターはmainの前に実行されます。したがって、メインを空のままにして、名前を印刷することができます。

class MyClass
{
   myClass()
   {
       cout << "printing my name..." <<endl;
   }
};

MyClass gObj; // this will trigger the constructor.

int main()
{
   // nothing here...
}
0
John Gummadi

関数mainは、プログラムが実行を開始するアドレスのデフォルトラベルのみです。技術的にはもちろん可能ですが、環境で実行を開始する関数の名前を設定する必要があります。

0
Dawid Królak

それは彼らの意味に依存します。

意味ですか:

Main()関数を使用しないプログラムを作成します。

その後、一般的に言えばいいえ。
しかし、チートする方法があります。

  • プリプロセッサを使用して、わかりやすいようにmain()を非表示にできます。
  • ほとんどのコンパイラでは、コードへのエントリポイントを指定できます。
    デフォルトでは、main(int、char * [])

またはそれらは意味しました:

Mainを使用せずにコードを実行するプログラムを作成します(コードを実行するため)。

これは比較的簡単なトリックです。グローバル名前空間のすべてのオブジェクトは、main()に入る前にコンストラクターを実行し、main()が終了した後に破棄します。したがって、必要なのは、必要なコードを実行するコンストラクターでクラスを定義し、グローバル名前空間にオブジェクトを作成することだけです。

注:コンパイラーは、これらのオブジェクトを遅延ロード用に最適化できますが(通常はできません)、安全にするために、メイン関数と同じファイルにグローバルを置くだけです(空の場合もあります)。

0
Martin York

.dataセクションをコンパイルしてコードで埋めることは可能でしょうか?

0
Gigamegs

1)メインを定義するマクロを使用する

#include<stdio.h>
#define fun main
int fun(void)
{
printf("stackoverfow");
return 0;
}

出力:

スタックオーバーフロー

2)トークン貼り付け演算子の使用上記のソリューションには、Wordの「メイン」が含まれています。 mainを記述することさえ許可されていない場合は、トークン貼り付け演算子を使用します(詳細はこちらを参照)

#include<stdio.h>
#define fun m##a##i##n
int fun()
{
printf("stackoverflow");
return 0;
}
0
anshuman singh