5

重複の可能性:
「構造体ハック」は技術的に未定義の動作ですか?

通常、配列の終わりを超えてアクセスすることは、Cでは未定義の動作です。次に例を示します。

int foo[1];
foo[5] = 1; //Undefined behavior

配列の終わりの後のメモリ領域がmallocまたはスタックに割り当てられていることがわかっている場合、それはまだ未定義の動作ですか?次に例を示します。

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
  int len;
  int data[1];
} MyStruct;

int main(void)
{
  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  foo->data[5] = 1;
}

このパッテンが可変長構造体を作るためにいくつかの場所で使用されているのを見たことがありますが、実際には機能しているようです。技術的に未定義の動作ですか?

4

3 に答える 3

6

あなたが説明しているのは、愛情を込めて「構造体ハック」と呼ばれます。それが完全に大丈夫かどうかは明らかではありませんが、広く使用されていました。

最近(C99)から、「柔軟な配列メンバー」に置き換えられ始めました。このメンバーint data[];は、構造体の最後のフィールドである場合にフィールドを配置できます。

于 2012-09-10T15:04:17.063 に答える
3

6.5.6加法演算子の下:

セマンティクス

8-[...]ポインタオペランドが配列オブジェクトの要素を指し、配列が十分に大きい場合、結果は元の要素からオフセットされた要素を指し、結果と元の添え字の差が配列要素は整数式と同じです。[...]結果が配列オブジェクトの最後の要素の1つ過ぎを指している場合、*評価される単項演算子のオペランドとして使用されないものとします。

それまでにメモリが割り当てられているmalloc場合:

7.22.3メモリ管理機能

1-[...]割り当てが成功した場合に返されるポインタは、基本的な配置要件を持つ任意のタイプのオブジェクトへのポインタに割り当てられ、そのようなオブジェクトまたはそのようなオブジェクトの配列にアクセスするために使用できるように、適切に配置されます。割り当てられたスペース内(スペースが明示的に割り当て解除されるまで)。割り当てられたオブジェクトの存続期間は、割り当てから割り当て解除まで延長されます。

ただし、これは適切なキャストなしでそのようなメモリの使用を考慮しないため、MyStruct上記で定義されているように、オブジェクトの宣言されたメンバーのみを使用できます。これが、柔軟な配列メンバー(6.7.2.1:18)が追加された理由です。

また、付録J.2の未定義動作は、配列アクセスを呼び出すことに注意してください。

1-次の状況では、動作は定義されていません。[...]
—配列オブジェクトおよび整数型への、またはそのすぐ先へのポインターの加算または減算により、同じ配列オブジェクト。
—配列オブジェクトおよび整数型への、またはそのすぐ先へのポインターの加算または減算は、配列オブジェクトのすぐ先を指す結果を生成し、*評価される単項演算子のオペランドとして使用されます。
—指定された添え字でオブジェクトに明らかにアクセスできる場合でも(a[1][7]宣言で指定された左辺値式のように)、配列の添え字が範囲外ですint a[4][5])

したがって、お気づきのように、これは未定義の動作になります。

  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  foo->data[5] = 1;

ただし、次のことを行うことができます

  MyStruct *foo = malloc(sizeof(MyStruct) + sizeof(int) * 10);
  ((int *) foo)[(offsetof(MyStruct, data) / sizeof(int)) + 5] = 1;

C ++は、この点で緩いです。3.9.2複合タイプ[basic.compound]には次のものがあります。

3-[...]型のオブジェクトがTアドレスにある場合、値がアドレスAである型のポインタは、値がどのように取得されたかに関係なく、そのオブジェクトを指していると言われます。cv T*A

restrictこれは、たとえば修飾子を使用した、ポインターに対するCのより積極的な最適化の機会に照らして検討するのが理にかなっています。

于 2012-09-10T15:22:14.767 に答える
2

C99理論的根拠文書は、セクション6.7.2.1でこれについて説明しています。

C99の新機能:可変サイズ配列を含む構造体を作成するための「構造体ハック」と呼ばれる一般的なイディオムがあります。

..。

この構成の妥当性は常に疑わしいものでした。p->items1つの欠陥レポートへの応答で、委員会は、スペースが存在するかどうかに関係なく、アレイには1つのアイテムしか含まれていないため、未定義の動作であると判断しました。別の構成が提案されました。配列サイズを可能な最大の場合よりも大きくします(たとえば、を使用int items[INT_MAX];)が、このアプローチは他の理由でも定義されていません。

委員会は、C89に「構造体ハック」を実装する方法はありませんでしたが、それでも有用な機能であると感じました。そのため、「柔軟な配列メンバー」の新機能が導入されました。空の角かっこと、および呼び出しでの「-1」の削除を除けばmalloc、これは構造体ハックと同じ方法で使用されますが、現在は明示的に有効なコードです。

構造体ハックは未定義の動作であり、C仕様自体(他の回答にも引用があると確信しています)だけでなく、委員会もその意見を記録しているためです。

したがって、答えは「はい」です。これは、標準ドキュメントによると未定義の動作ですが、事実上のC標準に従って明確に定義されています。ほとんどのコンパイラライターはハックに精通していると思います。GCCからtree-vrp.c

   /* Accesses after the end of arrays of size 0 (gcc
      extension) and 1 are likely intentional ("struct
      hack").  */

コンパイラのテストスイートで構造体のハックを見つける可能性も十分にあると思います。

于 2012-09-10T15:48:38.620 に答える