15

私はいくつかのレガシー C コードに取り組んでいます。元のコードは 90 年代半ばに書かれ、その時代の Solaris と Sun の C コンパイラをターゲットにしていました。現在のバージョンは GCC 4 でコンパイルされ (ただし、多くの警告が表示されます)、動作しているように見えますが、整理しようとしています。 64 ビット プラットフォーム、およびビルド対象以外のコンパイラに適応させます。

この点に関する私の主な活動の 1 つは、すべての関数に完全なプロトタイプ (多くはありませんでした) があることを確認することでした。そのコンテキストで、関数よりも少ない引数で関数 (以前はプロトタイプ化されていなかった) を呼び出すコードを発見しました。定義は宣言します。関数の実装では、欠落している引数の値が使用されます。

例:

impl.c:

int foo(int one, int two) {
  if (two) {
      return one;
  } else {
      return one + 1;
  }
}

client1.c:

extern foo();
int bar() {
  /* only one argument(!): */
  return foo(42);
}

client2.c:

extern int foo();
int (*foop)() = foo;
int baz() {
  /* calls the same function as does bar(), but with two arguments: */
  return (*foop)(17, 23);
}

質問: 欠落している引数が定義されている関数呼び出しの結果は? その場合、関数は指定されていない引数に対してどのような値を受け取りますか? それ以外の場合は、ca の Sun C コンパイラになります。1996 (VMS ではなく Solaris の場合) は、影響を受ける呼び出しに特定の引数値を追加することでエミュレートできる予測可能な実装固有の動作を示しましたか?

4

4 に答える 4

5

編集:非常に簡潔で具体的で正確な答えを与えるパラメータ動作のないスタックスレッドC関数を見つけました。回答の最後にあるPMGのコメントは、UBに関するものです。以下は私の最初の考えであり、同じ線に沿っており、動作がUBである理由を説明していると思います..

質問: 欠落している引数が定義されている関数呼び出しの結果は?

私はノーと言います...理由は、関数が2番目のパラメーターを持っているかのように動作すると思うからですが、以下で説明するように、2番目のパラメーターはジャンクである可能性があります。

その場合、関数は指定されていない引数に対してどのような値を受け取りますか?

受け取った値は未定義だと思います。これが、UB を持つことができる理由です。

私が知っているパラメーターの受け渡しには2つの一般的な方法があります...(ウィキペディアには呼び出し規約に関する良いページがあります)

  1. レジ渡し。つまり、プラットフォームの ABI (Application Binary Interface) は、レジスタ x と y はたとえばパラメーターを渡すためのものであり、それ以上のものはスタック経由で渡されると言うでしょう...
  2. すべてがスタック経由で渡されます...

したがって、1 つのモジュールに「...指定されていない (ただし変数ではない) パラメーターの数...」(extern def) を含む関数の定義を与えると、与えた数のパラメーターを配置しません (この場合、 1) 実際の関数がパラメーター値を取得するために参照するレジスターまたはスタックの場所。したがって、見逃されている 2 番目のパラメーターの 2 番目の領域には、本質的にランダムなジャンクが含まれています。

編集:私が見つけた他のスタックスレッドに基づいて、externが「指定されていない(ただし変数ではない)パラメーターの数」を持つ宣言された関数にパラメーターを持たない関数を宣言したと言うように上記を修正しました。

プログラムが関数にジャンプすると、その関数はパラメーター受け渡しメカニズムが正しく守られていると想定するため、レジスタまたはスタックを調べて、見つかった値を使用します...それらが正しいと仮定します。

それ以外の場合は、ca の Sun C コンパイラになります。1996 (VMS ではなく、Solaris の場合) >> 予測可能な実装固有の動作を示した

コンパイラのドキュメントを確認する必要があります。私はそれを疑っています... extern定義は完全に信頼されるので、パラメーターを渡すメカニズムに応じて、レジスタまたはスタックが正しく初期化されるとは思えません...

于 2013-07-11T22:09:54.737 に答える
4

引数の数または型 (デフォルトの引数昇格後) が実際の関数定義で使用されているものと一致しない場合、動作は未定義です。

実際に何が起こるかは、実装によって異なります。欠落しているパラメータの値は、意味のある定義はされません (欠落している引数にアクセスしようとしてセグメンテーション違反が発生しないと仮定すると)。

プログラムがそのような誤った呼び出しに耐えられるかどうかは、呼び出し規約にも依存します。呼び出し元がパラメーターをスタックに配置し、そこから削除する責任がある「古典的な」C 呼び出し規則では、このようなエラーが発生してもクラッシュしにくくなります。CPU レジスタを使用して引数を渡す呼び出しについても同じことが言えます。一方、関数自体がスタックのクリーニングを担当する呼び出し規則は、ほとんどすぐにクラッシュします。

于 2013-07-11T22:50:12.453 に答える
1

このコードはコンパイルされ、Sun SPARC アーキテクチャ上で実行されたと思います。この古代の SPARC Web ページによると、「レジスターは、プロシージャーに渡される最初の 6 つのパラメーターに使用されます。%o0%o5

呼び出しサイトで 2 番目のパラメーターが指定されていない 2 つのパラメーターを期待する関数を使用した例では%01、呼び出しが行われたときにレジスターが常に適切な値を持っている可能性があります。

%o1元の実行可能ファイルにアクセスでき、誤った呼び出しサイトのコードを逆アセンブルできる場合は、呼び出しが行われたときにどのような値があったかを推測できる可能性があります。または、QEMU などの SPARC エミュレーターで元の実行可能ファイルを実行してみてください。いずれにせよ、これは簡単な作業ではありません。

于 2013-07-11T23:02:15.270 に答える