17

Cプログラムのエントリポイント(メイン)を回避することは可能ですか?以下のコードでは、以下のプログラムでfunc()viaを呼び出さずに呼び出しを呼び出すことは可能ですか?main()はいの場合、それをどのように行うのか、いつ必要になるのか、そしてなぜそのような規定が与えられるのですか?

int func(void)
{
     printf("This is func \n");
     return 0;
}

int main(void)
{
     printf("This is main \n");
     return 0;
}
4

7 に答える 7

23

-egccを使用している場合は、コマンドラインパラメーターを使用して別のエントリポイントを指定できるというスレッドを見つけました。funcしたがって、エントリポイントとして使用でき、main未使用のままになります。

これでは、実際にはの代わりに別のルーチンを呼び出すことができないことに注意してくださいmain。代わりに_start、libcスタートアップルーチンであるの代わりに別のルーチンを呼び出すことができます。セットアップを実行してから、呼び出しますmain。したがって、これを行うと、ランタイムライブラリに組み込まれている初期化コードの一部が失われます。これには、コマンドライン引数の解析などが含まれる場合があります。使用する前に、このパラメーターをよく読んでください。

別のコンパイラを使用している場合は、このためのパラメータがある場合とない場合があります。

于 2010-07-31T18:07:30.833 に答える
19

ROMから直接実行する組み込みシステムのファームウェアを構築する場合main()、コードレビューアにコードの特殊な性質を強調するために、エントリポイントに名前を付けることは避けます。main()このような場合、カスタマイズされたバージョンのCランタイムスタートアップモジュールを提供しているので、への呼び出しを。などの別の名前に簡単に置き換えることができBootLoader()ます。

私(または私のベンダー)は、RAMが正しく動作を開始するために初期化コードを要求することは珍しくないため、ほとんどの場合、これらのシステムでCランタイムの起動をカスタマイズする必要があります。たとえば、一般的なDRAMチップは、制御ハードウェアの驚くべき量の構成を必要とし、多くの場合、有用になるまでにかなりの(数千のバスクロックサイクル)遅延を必要とします。それが完了するまで、呼び出しスタックを配置する場所さえない可能性があるため、スタートアップコードは関数を呼び出すことができない可能性があります。RAMデバイスが電源投入時に動作している場合でも、ほとんどの場合、Cランタイムに初期化を安全に開始させる前に初期化を必要とするチップセレクトハードウェアまたはFPGAがある程度あります。

Cで記述されたプログラムがロードされて起動すると、main()呼び出される環境を存在させるために何らかのコンポーネントが関与します。Unix、Linux、Windows、およびその他の対話型環境では、その努力の多くは、プログラムをロードするOSコンポーネントの自然な結果です。ただし、これらの環境でも、呼び出す前に実行する初期化作業がある程度ありますmain()。コードが実際にC++である場合、すべてのグローバルオブジェクトインスタンスのコンストラクターの呼び出しを含むかなりの量の作業が発生する可能性があります。

これらすべての詳細は、リンカとその構成ファイルおよび制御ファイルによって処理されます。リンカld(1)には、出力に含めるセグメント、アドレス、および順序を正確に指示する非常に複雑な制御ファイルがあります。ツールチェーンに暗黙的に使用しているリンカー制御ファイルを見つけてそれを読むことは、リンカー自体のリファレンスマニュアルや、実行可能ファイルを実行するために従わなければならないABI標準と同様に有益です。

編集:より一般的なコンテキストで尋ねられる質問に、より直接的に答えるには:「mainの代わりにfooを呼び出すことはできますか?」答えは「たぶん、しかしトリッキーであることによってのみ」です。

Windowsでは、実行可能ファイルとDLLはほぼ同じ形式のファイルです。実行時に名前が付けられた任意のDLLをロードし、その中の任意の関数を見つけて呼び出すプログラムを作成することができます。そのようなプログラムの1つは、実際には標準のWindowsディストリビューションの一部として出荷されますrundll32.exe

.EXEファイルは.DLLファイルを処理するのと同じAPIでロードおよび検査できるため、原則として、.EXEに関数を指定するEXPORTSセクションがある場合はfoo、同様のユーティリティを記述してロードおよび呼び出すことができます。mainもちろん、それが自然なエントリポイントになるため、特別なことをする必要はありません。もちろん、ユーティリティで初期化されたCランタイムは、実行可能ファイルにリンクされたCランタイムとは異なる場合があります。(Googleは「DLLHell」を意味します。)その場合、ユーティリティはよりスマートである必要があるかもしれません。たとえば、デバッガーとして機能し、ブレークポイントをでEXEをロードし、mainそのブレークポイントまで実行してから、PCをポイントまたはポイントに変更してfooそこから続行することができます。

.soファイルもいくつかの点で真の実行可能ファイルに類似しているため、Linuxでも同様のトリックが発生する可能性があります。確かに、デバッガーのように動作するアプローチを機能させることができます。

于 2010-07-31T22:04:35.903 に答える
5

経験則では、システムによって提供されるローダーは常にメインで実行されます。十分な権限と能力があれば、理論的には別のことを行う別のローダーを作成できます。

于 2010-07-31T17:53:20.567 に答える
4

mainの名前をfuncに、funcの名前をmainに変更し、名前からfuncを呼び出します。

ソースにアクセスできる場合は、これを行うことができ、簡単です。

于 2011-01-24T15:59:38.887 に答える
3

GCCなどのオープンソースコンパイラまたは組み込みシステムを対象としたコンパイラを使用している場合は、必要な任意のエントリポイントで開始するようにCランタイムスタートアップ(CRT)を変更できます。GCCでは、このコードはcrt0.sにあります。通常、このコードは部分的または全体的にアセンブラーに含まれています。ほとんどの組み込みシステムコンパイラーの例では、デフォルトの起動コードが提供されます。

ただし、より簡単なアプローチは、コードにリンクする静的ライブラリでmain()を単に「非表示」にすることです。main()の実装が次のようになっている場合:

int main(void)
{
    func() ;
}

次に、ユーザーのエントリポイントがfunc()であるかのように、すべての意図と目的を調べます。これは、main()以外のエントリポイントを持つアプリケーションフレームワークが機能する数です。これは静的ライブラリにあるため、main()のユーザー定義はその静的ライブラリのバージョンを上書きすることに注意してください。

于 2010-07-31T18:08:20.410 に答える
1

解決策は、使用するコンパイラとリンカによって異なります。常に、それはアプリケーションの実際のエントリポイントではありません。 main実際のエントリポイントは、いくつかの初期化を行い、たとえばを呼び出しますmain。Visual Studioを使用してWindows用のプログラムを作成する場合は、リンカーの/ ENTRYスイッチを使用して、デフォルトのエントリポイントを上書きし、次の代わりにmainCRTStartup呼び出すことができます。func()main()

#ifdef NDEBUG
void mainCRTStartup()
{
    ExitProcess (func());
}
#endif

最も小さなアプリケーションを作成する場合は、これが標準的な方法です。この場合、C-Runtime関数の使用に制限があります。C-Runtime関数の代わりにWindowsAPI関数を使用する必要があります。たとえば、代わりに、または:に関してのみ実装されている場所printf("This is func \n")を使用する必要があります。OutputString(TEXT("This is func \n"))OutputStringWriteFileWriteConsole

static HANDLE g_hStdOutput = INVALID_HANDLE_VALUE;
static BOOL g_bConsoleOutput = TRUE;

BOOL InitializeStdOutput()
{
    g_hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
    if (g_hStdOutput == INVALID_HANDLE_VALUE)
        return FALSE;

    g_bConsoleOutput = (GetFileType (g_hStdOutput) & ~FILE_TYPE_REMOTE) != FILE_TYPE_DISK;
#ifdef UNICODE
    if (!g_bConsoleOutput && GetFileSize (g_hStdOutput, NULL) == 0) {
        DWORD n;

        WriteFile (g_hStdOutput, "\xFF\xFE", 2, &n, NULL);
    }
#endif

    return TRUE;
}

void Output (LPCTSTR pszString, UINT uStringLength)
{
    DWORD n;

    if (g_bConsoleOutput) {
#ifdef UNICODE
        WriteConsole (g_hStdOutput, pszString, uStringLength, &n, NULL);
#else
        CHAR szOemString[MAX_PATH];
        CharToOem (pszString, szOemString);
        WriteConsole (g_hStdOutput, szOemString, uStringLength, &n, NULL);
#endif
    }
    else
#ifdef UNICODE
        WriteFile (g_hStdOutput, pszString, uStringLength * sizeof (TCHAR), &n, NULL);
#else
    {
        //PSTR pszOemString = _alloca ((uStringLength + sizeof(DWORD)));
        CHAR szOemString[MAX_PATH];
        CharToOem (pszString, szOemString);
        WriteFile (g_hStdOutput, szOemString, uStringLength, &n, NULL);
    }
#endif
}

void OutputString (LPCTSTR pszString)
{
    Output (pszString, lstrlen (pszString));
}
于 2010-08-01T07:15:53.903 に答える
0

これは、実際にはバイナリを呼び出す方法に依存し、プラットフォームと環境にかなり固有のものになります。最も明白な答えは、単に「main」記号の名前を別の名前に変更して「func」「main」と呼ぶことですが、それはあなたがやろうとしていることではないと思います。

于 2010-07-31T17:50:06.143 に答える