2

構造体の配列を出力するための疑似スーパー構造体を作成しようとしていました。私の基本的な構造は次のとおりです。

/* Type 10 Count */
typedef struct _T10CNT
{
    int _cnt[20];
} T10CNT;

...

/* Type 20 Count */
typedef struct _T20CNT
{
    long _cnt[20];
} T20CNT;
...

上記の構造体の配列を出力するために、以下の構造体を作成しました。以下のコード スニペットのコンパイル中に void ポインター エラーを逆参照しました。

typedef struct _CMNCNT
{
    long  _cnt[3];
} CMNCNT;

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    int ii;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];
        fprintf(stout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

T10CNT struct_array[10];
...
printCommonStatistics(struct_array, NELEM(struct_array), sizeof(struct_array[0]);
...

私の意図は、すべての配列を印刷する共通の機能を持つことです。正しい使い方を教えてください。

事前に助けに感謝します。

編集: パラメータ名が cmncnt から cmncntin に変更されました。すみません、タイプミスでした。

ありがとう、マシュー・リジュ

4

10 に答える 10

5

あなたのデザインは失敗するだろうと思いますが、私が見る他の答えがそのより深い理由を完全に扱っているとは確信していません.

C を使用してジェネリック型を処理しようとしているように見えますが、これは常に毛むくじゃらになります。気を付ければできますが、簡単ではありません。この場合、価値があるかどうかは疑問です。

より深い理由: 単なる構文上の問題 (または構文上の問題よりもわずかに多い問題) を乗り越えたと仮定しましょう。あなたのコードは、 T10CNT が 20intを含み、 T20CNT が 20 を含むことを示していますlong。Win64 以外の最新の 64 ビット マシンでは、sizeof(long) != sizeof(int). intしたがって、印刷関数内のコードは、逆参照配列と配列を区別する必要がありlongます。C++ には、配列をポリモーフィックに扱ってはならないという規則があり、このようなことが理由です。CMNCNT タイプには 3 つのlong値が含まれます。配列の基本型は T20CNT と一致しますが、T10CNT 構造体と T20CNT 構造体の両方と数が異なります。

スタイルの推奨事項: 名前の先頭にアンダースコアを付けないことを強くお勧めします。一般に、アンダースコアで始まる名前は、実装で使用するため、およびマクロとして使用するために予約されています。マクロはスコープを尊重しません。実装がマクロ _cnt を定義すると、コードが破壊されます。予約されている名前にはニュアンスがあります。私はそれらのニュアンスに入るつもりはありません。「アンダースコアで始まる名前は予約されている」と考える方がはるかに簡単で、問題を回避できます。

スタイルの提案: print 関数は無条件に成功を返します。それは賢明ではありません。呼び出し元が成功または失敗をテストする必要がないように、関数は何も返さないようにする必要があります (関数は決して失敗しないため)。関数がステータスを返すことを観察する注意深いコーダーは、常に戻りステータスをテストし、エラー処理コードを用意します。そのコードは決して実行されないため、死んでいますが、それを判断するのは誰にとっても (またはコンパイラーにとっても) 困難です。

表面修正int: 一時的に、およびlongを同義語として扱うことができると想定できます。ただし、それらが同義語であると考える習慣から抜け出す必要があります。void *引数は、「この関数は不定型のポインターを取る」という正しい方法です。ただし、関数内では、void *インデックスを作成する前に を特定の型に変換する必要があります。

typedef struct _CMNCNT
{
    long    count[3];
} CMNCNT;

static void printCommonStatistics(const void *data, size_t nelem, size_t elemsize)
{
    int i;
    for (i = 0; i < nelem; i++)
    {
        const CMNCNT *cmncnt = (const CMNCNT *)((const char *)data + (i * elemsize));
        fprintf(stdout,"STATISTICS_INP: %ld\n", cmncnt->count[0]);
        fprintf(stdout,"STATISTICS_OUT: %ld\n", cmncnt->count[1]); 
        fprintf(stdout,"STATISTICS_ERR: %ld\n", cmncnt->count[2]);
    }
}

(私も呼ばれるファイル ストリームのアイデアが好きですstout提案: 実際のソース コードでカット アンド ペーストを使用します。より安全です!私は通常sed 's/^/ /' file.c、カット アンド ペースト用のコードを SO 回答に準備するために " " を使用します。 .)

そのキャストラインは何をしますか?質問してよかった...

  • 最初の操作は、 を;に変換するconst void *ことです。const char *これにより、アドレスに対してバイトサイズの操作を実行できます。標準 C の前の時代には、ユニバーサル アドレス指定メカニズムとして のchar *代わりに使用されていました。void *
  • 次の操作では、正しいバイト数を追加して、isize のオブジェクトの配列の th 要素の先頭に到達しますelemsize
  • 次に、2 番目のキャストは、コンパイラーに「私を信じてください。私が何をしているのか知っています」と「このアドレスを CMNCNT 構造体のアドレスとして扱います」と伝えます。

そこから、コードは十分に簡単です。CMNCNT 構造体にはlong値が含まれているため、以前%ldは に真実を伝えていたことに注意してくださいfprintf()

この関数のデータを変更するつもりはないので、const私が行ったように修飾子を使用することは悪い考えではありません。

に忠実である場合は、「array of 」と「array of 」構造型sizeof(long) != sizeof(int)を処理するために、2 つの別個のコード ブロック (別個の関数をお勧めします) が必要であることに注意してください。intlong

于 2008-11-15T16:01:04.813 に答える
2

ボイドのタイプは意図的に不完全なままです。このことから、void ポインターを逆参照することはできず、その sizeof を取得することもできません。つまり、添字演算子を配列のように使用することはできません。

void ポインターに何かを割り当てた瞬間に、元の型が指す型の型情報は失われるため、最初に元のポインター型にキャストし直した場合にのみ逆参照できます。

まず、最も重要なことT10CNT*は、関数に渡しますが、それを関数内で型キャスト (および逆参照) しようとCMNCNT*します。これは有効ではなく、未定義の動作です。

配列要素のタイプごとに関数 printCommonStatistics が必要です。したがって、 、 があり 、printCommonStatisticsIntこれらはすべて最初の引数が異なります (1 つは 、もう1 つは、など)。冗長なコードを避けるために、マクロを使用してそれらを作成することがあります。printCommonStatisticsLongprintCommonStatisticsCharint*long*

構造体自体を渡すことはお勧めできません。構造体に含まれる配列の異なるサイズごとに新しい関数を定義する必要があるためです (それらはすべて異なる型であるため)。したがって、含まれている配列を直接渡す方がよい ( struct_array[0]._cnt、インデックスごとに関数を呼び出す)

于 2008-11-14T13:36:43.777 に答える
1

次のように、関数宣言をchar*に変更します。

static int printCommonStatistics(char *cmncnt, int cmncnt_nelem, int cmncnt_elmsize)

voidタイプは特定のサイズを想定していませんが、charはバイトサイズを想定しています。

于 2008-11-14T05:54:52.603 に答える
1

あなたはこれを行うことはできません:

cmncnt->_cnt[0]

cmnctがvoidポインタの場合。

タイプを指定する必要があります。実装を再考する必要があるかもしれません。

于 2008-11-14T05:58:00.023 に答える
1

関数

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    char *cmncntinBytes;
    int ii;

    cmncntinBytes = (char *) cmncntin;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)(cmncntinBytes + ii*cmncnt_elmsize);  /* Ptr Line */
        fprintf(stdout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stdout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stdout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

私のために働きます。

問題は、「Ptr Line」とコメントされた行で、コードが整数へのポインターを追加することです。ポインターは char * であるため、メモリー sizeof(char) * ii * cmncnt_elemsize を進めます。これは、char が 1 バイトであるため、必要なサイズです。あなたのコードは sizeof(void) * ii * cmncnt_elemsize を進めようとしましたが、void にはサイズがないため、コンパイラはエラーを出しました。

T10CNT と T20CNT を変更して、それぞれに 1 つではなく両方とも int または long を使用するようにします。あなたは sizeof(int) == sizeof(long) に依存しています

于 2008-11-15T15:48:08.957 に答える
0

あなたの表現

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

cmncntin [ii * cmncnt_elmsize]のアドレスを取得し、そのポインターをタイプ(CMNCNT *)にキャストしようとします。cmncntinのタイプがvoid*であるため、cmncntin [ii*cmncnt_elmsize]のアドレスを取得できません。

Cの演算子の優先順位を調べ、必要に応じて括弧を挿入します。

于 2008-11-14T08:32:34.657 に答える
0

ポイント: 内部パディングは、これを台無しにする可能性があります。

struct { char c[6]; を検討してください。}; -- sizeof()=6 です。しかし、これらの配列がある場合、各要素は 8 バイトの配置にパディングされる可能性があります!

特定のアセンブリ操作では、整列されていないデータが適切に処理されません。(たとえば、int が 2 つのメモリ ワードにまたがる場合。) (はい、以前にこれに噛まれたことがあります。)

.

2 番目: 以前は可変サイズの配列を使用していました。(私は当時はばかだった...)タイプを変更していない場合は機能します。(または、タイプの共用体がある場合。)

例えば:

struct T { int sizeOfArray;  int data[1]; };

割り当てられた

T * t = (T *) malloc( sizeof(T) + sizeof(int)*(NUMBER-1) );
                      t->sizeOfArray = NUMBER;

(ただし、パディング/アライメントはまだあなたを台無しにする可能性があります。)

.

3 番目: 次の点を考慮してください。

   struct T {
     int sizeOfArray;
     enum FOO arrayType;
     union U { short s; int i; long l; float f; double d; } data [1];
    };

データを印刷する方法を知ることで問題を解決します。

.

4 番目: 構造体ではなく、int/long 配列を関数に渡すことができます。例えば:

void printCommonStatistics( int * data, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << data[i] << endl;
}

呼び出し:

_T10CNT  foo;
printCommonStatistics( foo._cnt, 20 );

または:

 int a[10], b[20], c[30];
printCommonStatistics( a, 10 );
printCommonStatistics( b, 20 );
printCommonStatistics( c, 30 );

これは、構造体にデータを隠すよりもはるかにうまく機能します。構造体の 1 つにメンバーを追加すると、構造体間でレイアウトが変更され、一貫性が失われる可能性があります。(つまり、構造体の先頭からの _cnt のアドレスは、_T20CNT ではなく _T10CNT で変わる可能性があります。楽しいデバッグ時間です。結合された _cnt ペイロードを持つ単一の構造体は、これを回避します。)

例えば:

struct FOO {
  union {
         int     bar  [10];
          long biff [20];
   } u;
}

.

5 番目: 構造体を使用する必要がある場合... C++、iostream、およびテンプレートを実装すると、はるかにクリーンになります。

例えば:

template<class TYPE> void printCommonStatistics( TYPE & mystruct, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << mystruct._cnt[i] << endl;
}      /* Assumes all mystruct's have a "_cnt" member. */

しかし、それはおそらくあなたが探しているものではありません...

于 2008-11-15T18:17:39.187 に答える
0

この行で:

CMNCNT *cmncnt = (CMNCNT *)&cmncnt[ii*cmncnt_elmsize];

cmncnt という名前の新しい変数を宣言しようとしていますが、この名前の変数が関数のパラメーターとして既に存在しています。これを解決するには、別の変数名を使用することをお勧めします。

また、void ポインターの代わりに CMNCNT へのポインターを関数に渡すこともできます。これは、コンパイラーがポインター演算を行い、キャストする必要がないためです。CMNCNT にキャストするだけの場合、void ポインターを渡す意味がわかりません。(ちなみに、これはデータ型のわかりやすい名前ではありません。)

于 2008-11-14T05:52:55.120 に答える
-1

CはJavaの私のカップではありませんが、あなたの問題は「void *cmncnt」がCMNCNT *cmncntでなければならないことだと思います。

C プログラマーの皆さん、遠慮なく訂正してください。Java プログラマーが優れた機能を持たないのはこのためだと教えてください。

于 2008-11-14T05:52:21.360 に答える
-1

このセリフは一種の拷問だと思いませんか?

CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];

もっと似たものはどうですか

CMNCNT *cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize));

またはさらに良いことに、cmncnt_elmsize = sizeof(CMNCNT) の場合

CMNCNT *cmncnt = ((CMNCNT *)cmncntin) + ii;

void * を逆参照しなくなったため、警告も取り除かれます。

ところで:なぜこのようにしているのかはよくわかりませんが、cmncnt_elmsize が sizeof(CMNCNT) ではない場合があり、実際に呼び出しごとに異なる場合は、この設計を再考することをお勧めします。それには正当な理由があると思いますが、私には非常に不安定に見えます。物事を設計するためのより良い方法があることはほぼ保証できます。

于 2008-11-14T14:49:19.763 に答える