0

関数の署名、呼び出し、定義の順序を知りたい

たとえば、コンピュータが 1 番目、2 番目、3 番目に見るのはどれですか

そう:

#include <iostream>
using namespace std;

void max(void);
void min(void);

int main() {

max();

min();

return;
}

void max() {

return;
}

void min() {

return;
}

ですから、私はこう思います。

コンピューターはメインに移動して関数呼び出しを調べます。次に、

関数の署名、そして最後に定義を見ます。

そうですよね?

感謝

4

2 に答える 2

1

ご存じのとおり、コンパイラはプログラムを (いくつかの中間ステップを介して) マシン コードに変換します。main()Windows 8 のデバッグ モードで Visual Studio 2012 でコンパイルされたときのマシン コードの逆アセンブリを次に示します。

int main() {
00C24400  push        ebp                         # Setup stack frame
00C24401  mov         ebp,esp  
00C24403  sub         esp,0C0h  
00C24409  push        ebx  
00C2440A  push        esi  
00C2440B  push        edi  
00C2440C  lea         edi,[ebp-0C0h]              # Fill with guard bytes
00C24412  mov         ecx,30h                     
00C24417  mov         eax,0CCCCCCCCh  
00C2441C  rep stos    dword ptr es:[edi]  

max();
00C2441E  call        max (0C21302h)              # Call max  

min();
00C24423  call        min (0C2126Ch)              # Call min

return 0;
00C24428  xor         eax,eax  
}
00C2442A  pop         edi                         # Restore stack frame    
00C2442B  pop         esi  
00C2442C  pop         ebx  
00C2442D  add         esp,0C0h  
00C24433  cmp         ebp,esp  
}
00C24435  call        __RTC_CheckEsp (0C212D5h)   # Check for memory corruption
00C2443A  mov         esp,ebp  
00C2443C  pop         ebp  
00C2443D  ret 

正確な詳細は、コンパイラごと、およびオペレーティング システムごとに異なります。min() または max() に引数または戻り値がある場合、それらはアーキテクチャに応じて渡されます。重要な点は、コンパイラが引数と戻り値が何であるかを既に解決しており、それらを渡すか受け入れるだけのマシン コードを作成していることです。

デバッグを支援したり、低レベルの呼び出しを行いたい場合は、さらに詳細を学ぶことができますが、出力されるマシン コードは非常に可変であることに注意してください。たとえば、同じシステムでリリース モード (つまり、最適化をオン) でコンパイルした同じコードを次に示します。

return 0;
01151270  xor         eax,eax  
}
01151272  ret 

ご覧のとおり、それを検出しmin()max()何もせずに完全に削除しました。セットアップおよび復元するスタック フレームがなくなったため、これはなくなり、eax を 0 に設定してから戻る単一の命令を残します (戻り値が eax レジスタにあるため)。

于 2012-10-01T02:12:43.860 に答える
1

そうですよね?

いいえ。

関数宣言と関数定義の違い、コンパイル、リンク、実行の違い、非仮想関数と仮想関数の違いを理解する必要があります。

関数宣言
これは関数宣言です: void max(void);. 関数が何をするかについてコンパイラに何も伝えません。関数の呼び出し方法と結果の解釈方法をコンパイラに指示します。コンパイラが関数の本体をコンパイルしているとき、それを関数 A と呼びます。コンパイラは、他の関数が何をするかを知る必要はありません。知る必要があるのは、関数 A が呼び出す関数をどうするかだけです。コンパイラは、C++ 関数呼び出しに対応するアセンブリ言語または中間言語でコードを生成する場合があります。または、コードが意味をなさないために C++ コードを拒否する可能性があります。

コードが意味をなすかどうかを判断することは、これらの関数宣言のもう 1 つの重要な目的です。これは、複数の関数が同じ名前を持つことができる C++ では特に重要です。maxコンパイラーは、それらの関数について知らなかった場合、どの関数を呼び出すべきかをどのように知るのでしょうか? C++ コードが何らかの関数を呼び出す場合、コンパイラはそれらの関数宣言の 1 つと最も一致するもの (おそらく型変換を含む) を 1 つ見つける必要があります。コンパイラが一致をまったく見つけられない場合、または複数の一致が見つかったが最適な一致として区別できない場合、コードは意味がありません。

コンパイラが最適な一致を見つけた場合、生成されたコードは、その関数への未定義の外部参照への呼び出しの形式になります。その関数が存在する場所は、コンパイラの仕事ではありません。

関数定義
それvoid max(void)は関数宣言でした。対応するvoid max() {...}のは、その関数の定義です。コンパイラが処理void max() {...}しているとき、他の関数が何を呼び出したかについて心配する必要はありません。処理について心配するだけvoid max() {...}です。この関数の本体は、コンパイルされたオブジェクト ファイルに挿入されるアセンブリまたは中間言語コードになります。コンパイラは、この生成されたコードへのエントリ ポイントのアドレスをそのようにマークします。

コンパイルとリンク
ここまで、コンパイラの機能について説明してきました。C++ コードに対応する低レベル コードのチャンクを生成します。その生成されたコードは、それらの外部参照のために、プライムタイムの準備ができていません. これらの未定義の外部参照を解決するのは、リンカーの仕事です。リンカーは、複数のオブジェクト ファイル、複数のライブラリから実行可能ファイルを構築するものです。これらのコードのチャンクを実行可能ファイルのどこに配置したかを追跡します。これらの未定義の外部参照はどうですか? リンカーがその参照を実行可能ファイルに既に配置している場合、リンカーは単にその参照のプレースホルダーを埋めます。リンカーがその参照の定義に遭遇していない場合、参照とプレースホルダーをまだ解決されていない参照のリストに入れます。リンカーがコードのチャンクを実行可能ファイルに追加するたびに、そのリストをチェックして、まだ解決されていない参照を修正できるかどうかを確認します。最後に、すべての参照が解決されるか、いくつかの未解決の参照が残ることになります。後者はエラーです。前者は、実行可能ファイルがあることを意味します。

実行コードが実行されるとき、これらの関数呼び出しは実際には、その邪悪なステートメント
に相当する機械語にラップされたスタック管理にすぎません。goto関数宣言を調べる必要はありません。それらは、コードが実行されるまでには存在しません。戻る?それgotoもね。

非仮想機能
と仮想機能 上記で述べたことは、非仮想機能に関するものです。仮想機能に対して実行時のディスパッチが発生します。その実行時のディスパッチは、関数宣言の検査とは何の関係もありません。これらの仮想関数は、おそらく別の質問の問題です。

最後にもう 1 つ:
喫煙 の習慣をusing namespace std;やめましょう。それは喫煙に似ていると考えてください。それは悪い習慣です。

于 2012-10-01T02:45:39.087 に答える