9

(類似しているが重複していないこの質問を見つけました: Cプログラミング言語でヘッダーファイルの有効性を確認する方法

関数の実装と、一致しないプロトタイプ (同じ名前で型が異なる) がヘッダー ファイルにあります。ヘッダー ファイルは、関数を使用する C ファイルによってインクルードされますが、関数を定義するファイルには含まれません。

最小限のテスト ケースを次に示します。

header.h:

void foo(int bar);

File1.c:

#include "header.h"
int main (int argc, char * argv[])
{
    int x = 1;
    foo(x);
    return 0;
}

ファイル 2.c:

#include <stdio.h>

typedef struct {
    int x;
    int y;
} t_struct;

void foo (t_struct *p_bar)
{
    printf("%x %x\n", p_bar->x, p_bar->y);
}

これをVS 2010でエラーや警告なしでコンパイルできますが、当然のことながら、実行するとセグメンテーション違反が発生します。

  • コンパイラはそれで問題ありません(これは私が理解しています)
  • リンカーはそれをキャッチしませんでした (これには少し驚きました)
  • 静的解析ツール (Coverity) はそれをキャッチしませんでした (これには非常に驚きました)。

この種のエラーをキャッチするにはどうすればよいですか?

[編集: 私も参加すると、コンパイラーが文句を言うことに気づき#include "header.h"ました。file2.cしかし、私は膨大なコード ベースを持っており、関数がプロトタイプ化されているすべてのヘッダーが実装ファイルに含まれていることを保証することが常に可能または適切であるとは限りません。]

4

6 に答える 6

5

-Werror-implicit-function-declaration-Wmissing-prototypesまたはサポートされているコンパイラのいずれかで同等のもの。宣言がグローバルの定義の前にない場合、エラーまたはエラーが発生します。

厳密な C99 モードでプログラムをコンパイルすると、これらのメッセージも生成されます。GCC、ICC、および Clang はすべてこの機能をサポートしています (VS 2005 または 2008 が私が C に使用した最新のものであるため、MS の C コンパイラとその現在のステータスについてはわかりません)。

于 2013-02-28T14:35:47.037 に答える
5

と の両方に同じヘッダー ファイルを含めfile1.cますfile2.c。これにより、競合するプロトタイプをほとんど防ぐことができます。

そうしないと、コンパイル時に関数のソース コードがコンパイラに表示されないため、コンパイラはこのような間違いを検出できませんfile1.c。むしろ、与えられた署名のみを信頼できます。

少なくとも理論的には、追加のメタデータがオブジェクト ファイルに保存されている場合、リンカはそのような不一致を検出できる可能性がありますが、これが実際に可能かどうかはわかりません。

于 2013-02-28T14:18:43.023 に答える
2

http://frama-c.comで入手可能な Frama-C 静的解析プラットフォームを使用できます。

あなたの例では、次のようになります。

$ frama-c 1.c 2.c
[kernel] preprocessing with "gcc -C -E -I.  1.c"
[kernel] preprocessing with "gcc -C -E -I.  2.c"
[kernel] user error: Incompatible declaration for foo:
                     different type constructors: int vs. t_struct *
                     First declaration was at  header.h:1
                     Current declaration is at 2.c:8
[kernel] Frama-C aborted: invalid user input.

お役に立てれば!

于 2013-03-30T17:39:39.867 に答える
1

すべてのファイルで定義されたすべての (非静的) 関数は、対応するファイルにfoo.cプロトタイプを持っている必要があり、 . (は唯一の例外です。) には、 で定義されていない関数のプロトタイプを含めることはできません。foo.hfoo.c#include "foo.h"mainfoo.hfoo.c

すべての関数は、 1 回だけプロトタイプを作成する必要があります。

プロトタイプが含まれていない場合、.h対応するファイルがないファイルを持つことができます。対応するファイルがない.c唯一のファイルは、 ..c.hmain

あなたはすでにこれを知っていますが、あなたの問題は、このルールが守られていない巨大なコード ベースがあることです。

では、どうやってここからあそこに行くのですか?これが私がおそらくそれを行う方法です。

ステップ 1 (コード ベースで 1 回のパスが必要):

  • ファイルごとに、ファイルがまだ存在しない場合foo.cは作成します。の上部近くにfoo.h追加します。ファイルが存在する場所に関する規則がある場合(同じディレクトリまたは並列ディレクトリ内のいずれかである場合は、それに従います。そうでない場合は、そのような規則を導入してみてください)。"#include "foo.h"foo.c.h.cincludesrc
  • の各関数についてfoo.c、そのプロトタイプがfoo.h存在しない場合は にコピーします。コピー アンド ペーストを使用して、すべてが一貫していることを確認します。(パラメーター名は、プロトタイプではオプションであり、定義では必須です。両方の場所で名前を保持することをお勧めします。)
  • 完全なビルドを実行し、表示される問題を修正します。

これですべての問題が解決されるわけではありません。一部の機能については、複数のプロトタイプを使用できます。しかし、2 つのヘッダーが同じ関数に対して一貫性のないプロトタイプを持っていて両方のヘッダーが同じ翻訳単位に含まれているというケースはすべて見つかります。

すべてがきれいにビルドされると、少なくとも最初に作成したものと同じくらい正確なシステムが完成するはずです。

ステップ2:

  • ファイルごとfoo.hに、 で定義されていない関数のプロトタイプをすべて削除しfoo.cます。
  • 完全なビルドを実行し、表示される問題を修正します。bar.cで定義されている関数を呼び出すfoo.c場合はbar.c#include "foo.h".

これらのステップの両方で、「表示された問題を修正する」フェーズは、長くて退屈なものになる可能性があります。

これらすべてを一度に行う余裕がない場合は、多くのことを段階的に行うことができます。1 つまたはいくつかの.cファイルから始めて、それらのファイルをクリーンアップし、.h他の場所で宣言されている余分なプロトタイプを削除します。

呼び出しで間違ったプロトタイプが使用されている場合はいつでも、その呼び出しが実行される状況と、アプリケーションの誤動作の原因を突き止めるようにしてください。バグ レポートを作成し、回帰テスト スイートにテストを追加します (1 つありませんか?)。あなたが行ったすべての作業のおかげで、テストに合格したことを経営陣に示すことができます。あなたは本当にただいじっていたわけではありません。

C を解析できる自動化ツールが役立つ可能性があります。Ira Baxter からいくつかの提案があります。ctagsも役立つ場合があります。コードがどのようにフォーマットされているかによって、完全な C パーサーを必要としないいくつかのツールを組み合わせることができます。たとえば、grepsed、またはperlを使用してファイルから関数定義のリストを抽出し、foo.cそのリストを手動で編集して誤検出を削除できます。

于 2013-02-28T15:58:36.080 に答える
1

これは、関数名がシンボリック オブジェクト名にマップされる方法 (実際の署名を考慮せずに直接) のため、C コンパイラでは不可能なようです。

しかし、関数シグネチャに依存する名前マングリングを使用するため、これは C++ で可能です。したがって、C++ ではvoid foo(int)void foo(t_struct*)リンケージ段階で異なる名前が付けられ、リンカはそれについてエラーを発生させます。

もちろん、巨大な C コードベースを C++ に切り替えるのは簡単ではありません。しかし、いくつかの比較的単純な回避策を使用できます。たとえば、単一の.cppファイルをプロジェクトに追加し、すべての C ファイルをその中に含めます (実際にはスクリプトで生成します)。

あなたの例と私がプロジェクトに追加したVS2010を取りますTestCpp.cpp:

#include "stdafx.h"

namespace xxx
{
#include "File1.c"
#include "File2.c"
}

結果はリンカー エラー LNK2019 です。

TestCpp.obj : error LNK2019: unresolved external symbol "void __cdecl xxx::foo(int)" (?foo@xxx@@YAXH@Z) referenced in function "int __cdecl xxx::main(int,char * * const)" (?main@xxx@@YAHHQAPAD@Z)
W:\TestProjects\GenericTest\Debug\GenericTest.exe : fatal error LNK1120: 1 unresolved externals

もちろん、これは巨大なコードベースではそれほど簡単ではありません。コードベースを変更しないと修正できないコンパイル エラーにつながる他の問題が発生する可能性があります。.cppファイルの内容を条件付きで保護#ifdefし、通常のビルドではなく定期的なチェックにのみ使用することで、部分的に軽減できます。

于 2013-02-28T16:03:24.823 に答える
0

明らかに(「私は巨大なコードベースを持っています」)これを手動で行うことはできません。

必要なのは、コンパイラが参照するソース ファイルを読み取り、すべての関数プロトタイプと定義を収集し、すべての定義/プロトタイプが一致することを検証できる自動化ツールです。そのようなツールが転がっているのを見つけるとは思えません。

もちろん、この一致は署名をかなりチェックします。これには、署名を比較するためのコンパイラのフロントエンドのようなものが必要です。

検討

     typedef int T;
     void foo(T x);

1 つのコンパイル単位で、および

      typedef float T;
      void foo(T x);

別の。署名の「行」が等しいかどうかだけを比較することはできません。チェック時に型を解決できるものが必要です。

CのGCC方言を使用している場合、GCCXMLが役立つ場合があります。ソース ファイルから最上位の宣言を XML チャンクとして抽出します。ただし、typedefs が解決されるかどうかはわかりません。定義を中央の場所 (データベース) に集めてそれらを比較するには、明らかに (かなりの) サポートを構築する必要があります。XML 文書を同等のものと比較することは、少なくともかなり単純であり、通常の方法でフォーマットされていれば非常に簡単です。これはおそらく最も簡単な賭けです。

それがうまくいかない場合は、カスタマイズできる完全な C フロントエンドを備えたものが必要です。GCC が利用可能であることは有名ですが、カスタマイズが難しいことは有名です。Clang が利用可能であり、このために使用される可能性がありますが、AFAIK は GCC 方言でのみ機能します。

当社の DMS ソフトウェア リエンジニアリング ツールキットには、C の多くの方言 (GCC、MS、GreenHills など) 用の C フロント エンド (完全な前処理機能を備えた) があり、完全な型情報を含むシンボル テーブルを構築します。DMS を使用すると、(アプリケーションの実際の規模に応じて) すべてのコンパイル ユニットを単純に処理し、各コンパイル ユニットのシンボル テーブルだけを構築できる場合があります。シンボル テーブル エントリが「一致」すること (同等の typedef の使用を含むコンパイラ規則に従って互換性があること) をチェックすることは、C フロント エンドに組み込まれています。必要なことは、読み取りを調整し、さまざまなコンパイル ユニットにわたってグローバル スコープですべてのシンボル テーブル エントリの一致ロジックを呼び出すことだけです。

これを GCC/Clang/DMS のいずれで行う場合でも、カスタム ツールをまとめるのはかなりの作業です。そのため、そのようなカスタム ツールを構築するためのエネルギーと比較して、サプライズを減らすために必要なことがどれほど重要であるかを判断しました。

于 2013-02-28T15:24:57.160 に答える