C配列の長さが0にできないのはなぜですか?


13

C11標準では、サイズと可変長の両方の配列は「ゼロより大きい値を持つ必要がある」としています。長さ0を許可しない理由は何ですか?

特に可変長配列の場合、時々ゼロのサイズを設定することは完全に理にかなっています。また、静的配列のサイズがマクロまたはビルド構成オプションからのものである場合にも役立ちます。

興味深いことに、GCC(およびclang)は、長さゼロの配列を許可する拡張機能を提供します。Javaでは、長さゼロの配列も使用できます。


7
stackoverflow.com/q/8625572 ... 「長さ0の配列は、各オブジェクトが一意のアドレスを持っているという要件と調和させるのが難しく、紛らわしいでしょう。」
ロバートハーヴェイ14

3
@RobertHarvey:指定されたstruct { int p[1],q[1]; } foo; int *pp = p+1;ppは正当なポインターですが*pp、一意のアドレスを持っていません。長さゼロの配列ではなぜ同じロジックが成り立たないのでしょうか?与えられたと言うint q[0]; 構造内にqその妥当性のものと同様であろうアドレスを指すことになるp+1上記の例。
supercat

@DocBrown C11標準6.7.6.2.5から、VLAのサイズを決定するために使用される式について語っています。「…評価されるたびに、ゼロより大きい値を持たなければなりません。」私はC99について知りません(そして、彼らがそれを変更するのは奇妙に思えます)が、あなたはゼロの長さを持つことができないように聞こえます。
ケビンコックス14

@KevinCox:C11標準(または問題の部分)の無料のオンラインバージョンはありますか?
ドックブラウン14

最終バージョンは無料では入手できません(残念)が、ドラフトをダウンロードできます。最後に利用可能なドラフトはopen-std.org/jtc1/sc22/wg14/www/docs/n1570.pdfです。
ケビンコックス14

回答:


11

私が賭けたい問題は、C配列は割り当てられたメモリチャンクの先頭への単なるポインタであるということです。サイズが0であることは、...へのポインタがあることを意味しますか?何も持てないので、arbitrary意的なものを選択する必要があります。を使用することはできませんnull。なぜなら、長さ0の配列はNULLポインターのように見えるからです。そしてその時点で、すべての異なる実装が異なる任意の動作を選択し、混乱を招きます。



7
@delnan:まあ、それについて熱心になりたいのなら、配列とポインターの算術が定義されているので、ポインターを使用して配列にアクセスしたり、配列をシミュレートしたりできます。言い換えれば、Cで等価なのはポインター演算と配列のインデックス付けです。しかし、結果はとにかく同じです...配列の長さがゼロの場合、まだ何も指していません。
ロバートハーヴェイ14

3
@RobertHarveyすべて本当ですが、あなたの最後の言葉(そして振り返ってみると、答え全体)は、そのような配列(この答えが「メモリのチャンク」と呼ぶものだと思いますか?)sizeof0、およびそれがどのように問題を引き起こすか。簡潔さや明確さを失うことなく、適切な概念と用語を使用して説明できます。配列とポインターを混在させると、配列=ポインターの誤解(他のコンテキストではより重要)が広がるだけでメリットはありません。

2
nullは使用できません。長さ0の配列はnullポインターのように見えるためです」空のdynarrayと空のlongstringは、技術的にはNULLポインターです。
JensG 14

3
-1、@ delnanでいっぱいです。特に長さゼロの配列の概念をサポートするいくつかの主要なコンパイラについてOPが書いたものに関しては、これは何も説明しません。「混prettyを招く」のではなく、実装に依存しない方法で長さゼロの配列をCで提供できると確信しています。
Doc Brown

6

通常、メモリ内で配列がどのようにレイアウトされるかを見てみましょう。

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

arr最初の要素のアドレスを保存する名前の個別のオブジェクトはないことに注意してください。式に配列が現れると、C は必要に応じて最初の要素のアドレスを計算します。

それでは、このことについて考えてみましょう:0要素の配列を持っていないだろう何のストレージアレイのアドレスを計算するためにそこの何も意味しない、それを脇セットをから(別の言い方をする、識別子のためのオブジェクトのマッピングはありません)。int「メモリを消費しない変数を作成したい」と言っているようなものです。それは無意味な操作です。

編集

Java配列は、CおよびC ++配列とはまったく異なる動物です。それらはプリミティブ型ではなく、から派生した参照型Objectです。

編集2

以下のコメントで指摘されている点-「0より大きい」制約は、サイズが定数式で指定されている配列にのみ適用されます。VLAの長さは0にすることができます。0値の非定数式でVLAを宣言することは制約違反ではありませんが、未定義の動作を呼び出します。

VLAが通常の配列は異なる動物であり、それらの実装がサイズ0を許容できることは明らかです。これらのstaticオブジェクトのサイズは、プログラムの開始前に認識されている必要があるため、宣言することもファイルスコープにすることもできません。

また、C11の時点では、VLAをサポートするために実装が必要ではないことも意味がありません。


3
申し訳ありませんが、私はあなたがちょうどテラスティンのように、ポイントを逃しています。長さゼロの配列は非常に意味があり、OPが私たちに言ったような既存の実装は、それができることを示しています。
Doc Brown 14

@DocBrown:最初に、言語標準がそれらを許可しない可能性が最も高い理由に対処していました。第二に、長さ0の配列が理にかなっている例が欲しいのです。なぜなら、私は正直にそれを考えることができないからです。最も可能性の高い実装は、T a[0]として扱うT *aことT *aですが、なぜ使用しないのですか?
ジョンボード14

申し訳ありませんが、標準でこれが禁止されている理由について「理論的推論」を購入していません。住所を実際に簡単に計算する方法についての私の答えをお読みください。そして、質問の下にあるロバート・ハーベイの最初のコメントのリンクをたどり、2番目の答えを読むことをお勧めします。有用な例があります。
ドックブラウン14

@DocBrown:ああ。structハック。個人的に使用したことはありません。可変サイズのstruct型を必要とする問題に取り組んだことはありません。
ジョンボード14

2
また、C99以来AFAIKを忘れないでください。Cでは可変長配列を使用できます。そして、配列サイズがパラメーターである場合、0の値を特別なケースとして扱う必要がないため、多くのプログラムが簡単になります。
ドックブラウン14

2

通常、ゼロ(実際には変数)サイズの配列には、実行時にそのサイズを知っておく必要があります。次に、これをaにパックし、たとえば次のような柔軟な配列メンバーstructを使用します。

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

明らかに、柔軟な配列メンバーはその最後でstructなければならず、前に何かを持っている必要があります。多くの場合、それは、その柔軟な配列メンバーの実際の実行時占有長に関連するものです。

もちろん割り当てます:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

私の知る限り、これはすでにC99で可能であり、非常に便利です。

ところで、C ++には柔軟な配列メンバーは存在しません(いつ、どのように構築および破棄するかを定義するのは難しいためです)。ただし、将来のstd :: dynarrayを参照してください


ご存知のように、それらは単純なタイプに制限されるだけで、困難はありません。

2

type name[count]が何らかの関数で記述されている場合、Cコンパイラにスタックフレームsizeof(type)*countバイトに割り当てて、配列の最初の要素のアドレスを計算するように指示します。

type name[count]がすべての関数および構造体定義の外部で記述されている場合、Cコンパイラにデータセグメントsizeof(type)*countバイトに割り当て、配列の最初の要素のアドレスを計算するように指示します。

name実際には、配列内の最初の要素のアドレスを格納する定数オブジェクトであり、メモリのアドレスを格納するすべてのオブジェクトはポインターと呼ばれるためname、配列ではなくポインターとして扱う理由です。Cの配列にはポインターを介してのみアクセスできることに注意してください。

if countがゼロと評価される定数式である場合、スタックフレームまたはデータセグメントのいずれかにゼロバイトを割り当て、配列の最初の要素のアドレスを返すようにCコンパイラに指示しますが、これを行う際の問題は最初の要素長さゼロの配列は存在せず、存在しないもののアドレスを計算することはできません。

これは合理的な要素番号です。-length配列にcount+1存在しないcountので、Cコンパイラが関数の内外で変数として長さ0の配列を定義することを禁止しているのはそのためnameです。どのアドレスにname正確に格納されますか?

場合はp、ポインタがその式はp[n]同等です*(p + n)

右側の式にアスタリスク*は手段がが指し示すメモリアクセスポインタの参照解除操作である場合、p + nそのアドレスに格納されたメモリやアクセスp + np + nポインタの表現であるが、それはのアドレスを受け取りp、このアドレスに番号を追加しn、乗算ポインタの型のサイズp

アドレスと番号を追加することはできますか?

はい、アドレスは16進表記で一般に表される符号なし整数であるため、可能です。


多くのコンパイラは、標準が禁止する前にゼロサイズの配列宣言を許可していましたが、多くのコンパイラはそのような宣言を拡張として許可し続けています。1の大きさの物体があることを認識した場合、このような宣言は問題ありませんNしているN+1最初のアドレス、関連付けられたNユニークなバイトと最後識別するのNこれらのバイトのそれぞれのポイントは、単に過去1を。そのような定義Nは、0の縮退した場合でもうまく機能します。
supercat18年

1

メモリアドレスへのポインタが必要な場合は、それを宣言します。配列は、実際には予約したメモリの塊を指します。配列は関数に渡されるとポインターになりますが、ポインターが指しているメモリーがヒープ上にある場合は問題ありません。サイズがゼロの配列を宣言する理由はありません。


2
通常、これを直接行うのではなく、マクロの結果として、または動的データで可変長配列を宣言するときに行います。
ケビンコックス14

配列が指すことはありません。ポインタを含めることができ、ほとんどのコンテキストでは実際に最初の要素へのポインタを使用しますが、それは別の話です。
デデュプリケーター

1
配列名は、配列に含まれるメモリへの定数ポインタです。
ncmathsadist

1
いいえ、ほとんどのコンテキストで、配列名は最初の要素のポインターに減衰します。多くの場合、違いは重要です。

1

元のC89の時代から、C標準が「未定義の動作」を指定すると、それは「特定のターゲットプラットフォームでの実装を意図した目的に最も適したものにする」という意味でした。標準の作成者は、特定の目的に最適な動作を推測しようとはしませんでした。VLA拡張機能を備えた既存のC89実装は、サイズがゼロの場合に異なるが、論理的な動作をする場合があります(たとえば、配列をNULLを生成するアドレス式として扱い、他のアドレスをアドレス式として扱うものもあります)別の任意の変数ですが、トラップせずに安全にゼロを追加できます)。コードがそのような異なる動作に依存している可能性がある場合、標準の作成者は

標準の作成者は、実装が何を行う可能性があるかを推測しようとしたり、他の動作よりも優れていると見なすべきであることを示唆したりするのではなく、実装者がそのケースを適切に判断して判断使用することを許可しました。背後でmalloc()を使用する実装は、配列のアドレスをNULLとして処理する場合があります(サイズがゼロのmallocがnullを生成する場合)。他のもの。私は、コンパイラーの作者が、ゼロサイズのコーナーケースを意図的に役に立たない方法で動作させるために邪魔にならないことを期待しているとは思わない。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.