20

次のコードを検討してください (これは、この議論の結果として生まれました)。

#include <stdio.h>

void foo(int (*p)[]) {          // Argument has incomplete array type
    printf("%d\n", (*p)[1]);
    printf("%d\n", p[0][1]);    // Line 5
}

int main(void) {
    int a[] = { 5, 6, 7 };
    foo(&a);                    // Line 10
}

GCC 4.3.4次のエラー メッセージが表示されます。

prog.c: In function ‘foo’:
prog.c:5: error: invalid use of array with unspecified bounds

GCC 4.1.2 の同じエラー メッセージで-std=c99、 、-Wall、の不変のよう-Wextraです。

したがって、式 には不満がありますp[0]が、 には満足し*pています。ただし、これらは (理論的には) 同等である必要があります。5 行目をコメント アウトすると、コードがコンパイルされ、「期待どおり」の処理が実行されます ( が表示されます6)。

おそらく、次のいずれかが当てはまります。

  1. 私の C 標準の理解は正しくなく、これらの式等価ではありません。
  2. GCC にはバグがあります。

私は(1)にお金を置きます。

質問:誰でもこの動作について詳しく説明できますか?

明確化:これは、関数定義で配列サイズを指定することで「解決」できることを認識しています。それは私が興味を持っていることではありません。


「ボーナス」ポイントについて: MSVC 2010 が次のメッセージで行 10 を拒否した場合、MSVC 2010 がエラーであることを誰か確認できますか?

1><snip>\prog.c(10): warning C4048: different array subscripts : 'int (*)[]' and 'int (*)[3]'
4

4 に答える 4

15

n1570 のセクション 6.5.2.1、配列添字:

制約

式の 1 つは型「完全なオブジェクト型へのポインター」を持ち、もう 1 つの式は整数型を持ち、結果は型「型」を持ちます。

そのため、標準ではp[0]ifpが不完全な型へのポインタであるという式を禁止しています。間接演算子にはそのような制限はありません*

ただし、標準の古いバージョン/ドラフト (n1256 および C99) では、その段落に「完全」という言葉がありません。標準的な手順には一切関与していないので、破壊的変更なのか脱字の修正なのか推測するしかありません。コンパイラの動作は後者を示唆しています。p[i]これは、標準では と同一であり、後者の式は不完全な型へ*(p + i)のポインターには意味がないという事実によって強化されます。 .p[0]p

于 2012-04-17T01:45:37.007 に答える
5

私のCは少しさびていますが、私の読書は、あなたがint (*p)[]これを持っているときです:

(*p)[n]

p「 intの配列を取得するために逆参照してから、n番目のものを取得します」と言います。これは当然、明確に定義されているようです。これに対して:

p[n][m]

「p の n 番目の配列を取得し、その配列の m 番目の要素を取得する」と言います。これはまったく明確に定義されていないようです。n 番目の配列の開始位置を見つけるには、配列の大きさを知る必要があります。

これは、配列の大きさに関係なく 0 番目の配列を簡単に見つけることができるため、n = 0 という特定の特殊なケースで機能する可能性があります。GCC がこの特殊なケースを認識していないことがわかりました。言語仕様は詳しく知らないので「バグ」かどうかはわかりませんが、言語設計の個人的な好みは、が静的に知られてp[n][m]いるときに機能するかどうかではなく、機能するかどうかです。 n0 であり、それ以外ではありません。

本当に*p <===> p[0]言語仕様からの決定的なルールですか、それとも単なる観察ですか? プログラミングをしているとき、逆参照とゼロによるインデックス付けを同じ操作とは考えていません。

于 2012-04-17T01:55:13.083 に答える
4

あなたの「ボーナスポイント」の質問(おそらくこれを別の質問として尋ねたはずです)については、MSVC10が間違っています。MSVC は C89 のみを実装しているため、その標準を使用していることに注意してください。

関数呼び出しについては、C89 §3.3.2.2 で次のように説明されています。

各引数は、その値が対応するパラメータの型の修飾されていないバージョンを持つオブジェクトに割り当てられるような型を持つものとします。

割り当ての制約は、C89 §3.3.16 にあります。

次のいずれかが成り立ちます: ... 両方のオペランドが互換性のある型の修飾または非修飾バージョンへのポインターであり、左が指す型は右が指す型のすべての修飾子を持ちます。

したがって、2 つのポインターが互換性のある型を指している場合、2 つのポインターを割り当てることができます (したがって、ポインター引数を使用してポインター パラメーターを持つ関数を呼び出すことができます)。

さまざまな配列型の互換性は、C89 §3.5.4.2 で定義されています。

2 つの配列型に互換性を持たせるには、両方に互換性のある要素型が必要であり、両方のサイズ指定子が存在する場合、それらは同じ値を持つ必要があります。

2 つの配列タイプの場合int []int [3]この条件は明らかに成り立ちます。したがって、関数呼び出しは正当です。

于 2012-04-17T04:26:23.363 に答える
0
void foo(int (*p)[])
{
    printf("%d\n", (*p)[1]);
    printf("%d\n", p[0][1]);    // Line 5
}

ここで、pは不特定数のints の配列へのポインタです。 *pその配列にアクセスするので、配列(*p)[1]の 2 番目の要素です。

p[n]は、ポイント先の配列のサイズの p と n 倍を追加しますが、これは不明です。を考える前から[1]、壊れています。ゼロ回何であっても 0 であることは事実ですが、コンパイラーは、ゼロになるとすぐに短絡することなく、すべての項の有効性を明らかにチェックしています。そう...

したがって、式 p[0] には不満がありますが、*p には満足しています。ただし、これらは (理論的には) 同等である必要があります。

説明したように、それらは明らかに同等ではありません... p[0]asp + 0 * sizeof *pと考えてください。理由は明らかです....

「ボーナス」ポイントについて: MSVC 2010 が次のメッセージで行 10 を拒否した場合、MSVC 2010 がエラーであることを誰か確認できますか? 1>\prog.c(10): 警告 C4048: 配列添字が異なります: 'int ( )[]' と 'int ( )[3]'

Visual C++ (およびその他のコンパイラ) は、良い習慣ではないと考えられること、経験的に誤りであることが多いことがわかっていること、またはコンパイラの作成者が不合理な不信感を抱いていたことについて、自由に警告することができます。標準に関して完全に合法です....よく知られている例には、「符号付きと符号なしの比較」および「条件内の割り当て(余分な括弧で囲むことを提案します)」が含まれます

于 2012-04-17T04:43:28.523 に答える