1. ホーム
  2. c++

[解決済み] なぜCとC++は構造体内部の配列のメンバーごとの割り当てをサポートしていますが、一般的にはサポートしていないのですか?

2023-04-01 12:11:08

疑問点

配列のメンバ単位の代入はサポートされていないとのことですが、そのため、以下のような処理はうまくいきません。

int num1[3] = {1,2,3};
int num2[3];
num2 = num1; // "error: invalid array assignment"

この言語の目的は、自由なフレームワークを提供することであり、配列のコピーなどをどのように実装するかはユーザーが決めることだと考え、私はこれを事実として受け止めました。

しかし、次のようにすると動きます。

struct myStruct { int num[3]; };
struct myStruct struct1 = {{1,2,3}};
struct myStruct struct2;
struct2 = struct1;

配列 num[3] のインスタンスからメンバーごとに代入されます。 struct1 のインスタンスから struct2 .

配列のメンバ単位の代入は構造体ではサポートされていますが、一般的にはサポートされていないのはなぜですか?

編集 : ロジャー・ペイト スレッドでのコメント std::string in struct - コピー/代入の問題? は、答えの一般的な方向を指しているようですが、私自身それを確認するのに十分な知識を持っていません。

編集2 : 多くの素晴らしい回答がありました。私は ルーサー・ブリセット を選びました。なぜなら、私はその行動の背後にある哲学的または歴史的な根拠について主に疑問を持っていたからです。 ジェームズ・マクネリス の関連する仕様書への言及もまた有用でした。

どのように解決するのですか?

以下は私の見解です。

C言語の発展 は、C言語における配列型の進化について、いくつかの洞察を提供しています。

配列のことを概説してみる。

Cの前身であるBやBCPLには明確な配列の型がなく、次のような宣言がありました。

auto V[10] (B)
or 
let V = vec 10 (BCPL)

はVを(型付けされていない)ポインタと宣言し、メモリの10 "ワード"の未使用領域を指すように初期化されます。B はすでに * をポインタの再参照のために使っており、また []  というショートハンド表記があります。 *(V+i) というのは V[i] という意味であり、今日のC/C++と同じである。しかし V は配列ではなく、あるメモリを指す必要があるポインタのままです。このことは、Dennis RitchieがBを構造体型で拡張しようとしたときに問題となりました。彼は、今日の C のように、配列が構造体の一部であることを望んでいました。

struct {
    int inumber;
    char name[14];
};

しかし、B,BCPLの配列をポインタとする概念では、これには name フィールドがポインタを含む必要があり、そのポインタは 実行時に初期化される 構造体内の 14 バイトのメモリ領域へのポインタを含んでいました。初期化とレイアウトの問題は、最終的には配列に特別な処理を施すことで解決されました。コンパイラは構造体やスタックなどにおける配列の位置を追跡し、配列を含む式を除いて、データへのポインタを実際に必要としませんでした。この処理により、ほとんどすべての B コードが実行できるようになり、これが 配列は見ればポインタに変換されます。 のルールの元になっています。これは互換性のためのハックで、オープン サイズの配列などを許可するため、非常に便利であることが判明しました。

そして、なぜ配列が割り当てられないのか、私の推測はこうです。B では配列はポインターだったので、単純に書くことができました。

auto V[10];
V=V+5;

を使用して、"array"をリベースすることができます。配列変数のベースはもはやlvalueではないため、これはもはや無意味でした。そのため、この代入は禁止され、このリベースを行う少数のプログラムを捕捉するのに役立ちました。 宣言された配列に対して . そして、この考え方は定着しました。配列は C の型システムの第一級品として設計されたものではないので、それらを使用するとポインタになる特別な獣として扱われることがほとんどでした。そして、ある観点からは(Cの配列はハッキングの失敗作であることを無視すれば)、配列の代入を禁止することはまだ意味があることなのです。開いている配列や配列関数のパラメータは、サイズ情報を持たないポインタとして扱われます。コンパイラは配列の代入を生成するための情報を持っておらず、互換性のためにポインタの代入が必要でした。宣言された配列に配列代入を導入すると、実際に問題を解決することなく、偽の代入(a=bはポインタ代入か要素ごとのコピーか)やその他の問題(配列を値で渡すにはどうすればよいか)などのバグが発生します - memcpyですべてを明示すればいいのです!

/* Example how array assignment void make things even weirder in C/C++, 
   if we don't want to break existing code.
   It's actually better to leave things as they are...
*/
typedef int vec[3];

void f(vec a, vec b) 
{
    vec x,y; 
    a=b; // pointer assignment
    x=y; // NEW! element-wise assignment
    a=x; // pointer assignment
    x=a; // NEW! element-wise assignment
}

1978年のCの改訂で、構造体への代入( http://cm.bell-labs.com/cm/cs/who/dmr/cchanges.pdf ). たとえレコード memcpy を使用してメンバ単位でコピーする必要があり、関数のパラメータとしてポインタのみを渡すことができました。割り当て(とパラメータ渡し)は、構造体の生メモリのmemcpyと簡単に定義され、既存のコードを壊すことができないので、容易に採用されました。意図しない副作用として、これは暗黙的にある種の配列の割り当てを導入しましたが、これは構造体の内部のどこかで発生するため、配列の使用方法に関する問題を実際に引き起こすことはありませんでした。