0

コンパイル時に初期化される非常に大きな定数配列があります。

typedef enum {
VALUE_A, VALUE_B,...,VALUE_GGF
} VALUES;

const int arr[VALUE_GGF+1] = { VALUE_A, VALUE_B, ... ,VALUE_GGF};

配列が正しく初期化されていることを確認したいのですが、次のようになります。

if (arr[VALUE_GGF] != VALUE_GGF) {
    printf("Error occurred. arr[VALUE_GGF]=%d\n", arr[VALUE_GGF]);
    exit(1);
}

私の問題は、コンパイル時にこれを確認したいということです。このスレッドのCでのコンパイル時のアサートについて読みました:Cコンパイラがアサートします。ただし、そこで提供されるソリューションは、コンパイルエラーのサイズとして負の値を使用して配列を定義することを提案しています。

#define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file)
#define _impl_PASTE(a,b) a##b
#define _impl_CASSERT_LINE(predicate, line, file) \
   typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1];

と使用:

CASSERT(sizeof(struct foo) == 76, demo_c);

定数配列の値を確認する必要があり、Cでは定数配列の値を使用して配列を初期化できないため、提供されたソリューションは機能しません。

int main() {
   const int i = 8;
   int b[i];         //OK in C++
   int b[arr[0]];    //C2057 Error in VS2005

それを回避する方法はありますか?他のコンパイル時のアサート?

4

6 に答える 6

3

以下のコードでは、6 行目と 9 行目の固定長で宣言されたポインターへの余分な代入を参照してください。

WORKDAYS 列挙型のすべての値に対して 2 つの配列が初期化されていない場合、コンパイル時にエラーが発生します。Gcc は言う: test.c:6:67: 警告: 互換性のないポインター型からの初期化 [デフォルトで有効]

マネージャーが SATURDAY を稼働日の列挙に追加したと想像してください。追加のチェックがなければ、プログラムはコンパイルされますが、実行時にセグメンテーション違反でクラッシュします。

このアプローチの欠点は、余分なメモリを消費することです (これがコンパイラによって最適化されているかどうかはテストしていません)。それはまた少しハックであり、おそらく次の人のためにコードにいくつかのコメントが必要です...

テストされる配列は、配列サイズを宣言するべきではないことに注意してください。配列サイズを設定すると、データが予約されていることが保証されますが、有効なものが含まれていることは保証されません。

#include <stdio.h>

typedef enum { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, NOF_WORKDAYS_IN_WEEK } WORKDAYS;

const char * const workday_names[] = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" };
const char * const (*p_workday_name_test)[NOF_WORKDAYS_IN_WEEK] = &workday_names;

const int workday_efforts[] = { 12, 23, 40, 20, 5 };
const int (*p_workday_effort_test)[NOF_WORKDAYS_IN_WEEK] = &workday_efforts;

main ()
{
  WORKDAYS i;
  int total_effort = 0;

  printf("Always give 100 %% at work!\n");

  for(i = MONDAY; i < NOF_WORKDAYS_IN_WEEK; i++)
  {
    printf(" - %d %% %s\n",workday_efforts[i], workday_names[i]);
    total_effort += workday_efforts[i];
  }
  printf(" %d %% in total !\n", total_effort);
}

ちなみに、プログラムの出力は次のとおりです。

仕事は常に100%!
- 12% 月曜日
- 23% 火曜日
- 40% 水曜日
- 20% 木曜日
- 5% 金曜日
合計で 100% !

于 2012-08-13T15:57:39.647 に答える
1

問題は、C ++では、コンパイル時の定数式に次の制限があることです(5.19定数式)。

積分定数式には、リテラル(2.13)、列挙子、定数式(8.5)で初期化された積分型または列挙型の静的データ・メンバー、積分型または列挙型の非型テンプレート・パラメーター、および式のサイズのみを含めることができます。浮動リテラル(2.13.3)は、整数型または列挙型にキャストされている場合にのみ表示されます。整数型または列挙型への型変換のみを使用できます。特に、sizeof式を除いて、関数、クラスオブジェクト、ポインタ、または参照は使用されません。また、代入、インクリメント、デクリメント、関数呼び出し、またはコンマ演算子は使用されません。

配列のインデックス式は、実際には偽装された単なるポインタ演算であり(arr[0]実際にはarr + 0)、定数データへのポインタであっても、定数式でポインタを使用することはできません。したがって、配列の内容をチェックするためのコンパイル時のアサーションでは運が悪いと思います。

Cは、コンパイル時にこれらの種類の式を使用できるC++よりもさらに制限されています。

しかし、C ++の複雑さを考えると、誰かが独創的な解決策を思い付く可能性があります。

于 2009-11-08T08:56:28.880 に答える
1

アサーションをプロパティとして表現して静的アナライザーでチェックし、アナライザーにチェックさせることができます。これには、やりたいことのいくつかのプロパティがあります。

  • プロパティはソースコードに書かれており、

  • 生成されたバイナリ コードを汚染しません。

ただし、チェックのためにプログラムで別のツールを実行する必要があるため、コンパイル時のアサーションとは異なります。おそらくそれは、実行しようとしていたコンパイラの健全性チェックです。この場合、静的アナライザはコンパイラが何をするかをチェックせず、コンパイラが何をすべきかだけをチェックするため、これは役に立ちません。追加: QA の場合、静的に検証できる「正式な」アサーションを書くことは、最近大流行しています。以下のアプローチは、聞いたことがあるかもしれない .NET コントラクトに非常に似ていますが、C 用です。

  • 静的アナライザーについてあまり考えないかもしれませんが、それらが不正確になるのはループと関数呼び出しです。これらのいずれかが発生する前に、初期化時に何が起こっているかを明確に把握する方が簡単です。

  • 一部のアナライザーは、自分自身を「正しい」と宣伝します。つまり、記述したプロパティがその機能の範囲外である場合、沈黙を保ちません。この場合、彼らはそれを証明できないと不平を言います。これが発生した場合、問題はアレイではなくアナライザーにあると確信した後は、別の方法を探して、現在の場所に残されます。

私がよく知っているアナライザーの例を挙げると、

const int t[3] = {1, 2, 3};
int x;

int main(){

  //@ assert t[2] == 3 ;

  /* more code doing stuff */
}

アナライザーを実行します。

$ frama-c -val t.i
...
t.i:7: Warning: Assertion got status valid.
Values of globals at initialization 
t[0] ∈ {1; }
 [1] ∈ {2; }
 [2] ∈ {3; }
x ∈ {0; }
...

アナライザーのログには、次の情報が表示されます。

  • グローバルの初期値が何であると考えるかのバージョン、
  • //@コメントに書いたアサーションの解釈。ここでは、アサーションを 1 回実行して、有効であることがわかります。

この種のツール ビルド スクリプトを使用する人は、ログから関心のある情報を自動的に抽出します。ただし、否定的な注意として、テストが最終的に忘れられるのではないかと心配している場合は、コードの変更後に必須の静的アナライザー パスが忘れられることも心配する必要があることを指摘する必要があります。

于 2009-11-08T10:07:20.793 に答える
0

コンパイル時にこれを検証する必要があると感じる理由は想像できませんが、使用できる奇妙な/冗長なハックが1つあります。

typedef enum {
    VALUE_A, VALUE_B,...,VALUE_GGF
} VALUES;
struct {
    static const VALUES elem0 = VALUE_A;
    static const VALUES elem1 = VALUE_B;
    static const VALUES elem2 = VALUE_C;
    ...
    static const VALUES elem4920 = VALUE_GGF;
    const int operator[](int offset) {return *(&elem0+offset);}
} arr;
void func() {
    static_assert(arr.elem0 == VALUE_A, "arr has been corrupted!");
    static_assert(arr.elem4920 == VALUE_GFF, "arr has been corrupted!");
}

これらはすべてコンパイル時に機能します。ただし、非常にハックで悪い形式です。

于 2011-08-12T20:48:05.130 に答える
0

バッチ ファイルを使用してアプリケーションをコンパイルおよびパックしているので、簡単な解決策は、すべての配列を実行して内容が正しいことを確認する別の単純なプログラムをコンパイルすることだと思います。バッチ ファイルを使用してテスト プログラムを実行し、テストの実行が失敗した場合は残りのプログラムのコンパイルを停止できます。

于 2009-11-10T13:49:04.543 に答える