Почему C и C ++ поддерживают членское присваивание массивов внутри структур, но не в целом? - PullRequest
80 голосов
/ 09 августа 2010

Я понимаю, что членское присвоение массивов не поддерживается, так что следующее не будет работать:

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.

Почему присваивание массивов для элементов поддерживается для структур, но не в целом?

edit : Комментарий Роджера в ветке std :: string in struct - Проблемы копирования / назначения? , кажется, указывает наобщее направление ответа, но я не знаю достаточно, чтобы подтвердить это сам.

edit 2 : Множество отличных ответов.Я выбрал Лютера Блиссетта , потому что меня больше всего интересовало философское или историческое обоснование поведения, но ссылка Джеймса МакНеллиса на сопутствующую документацию спецификаций также была полезна.

Ответы [ 4 ]

41 голосов
/ 09 августа 2010

Вот мое мнение:

Развитие языка 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 не является массивом, это все же указатель, который должен указывать на некоторую память. Это вызвало проблемы, когда Деннис Ритчи попытался расширить B структурами типа. Он хотел, чтобы массивы были частью структур, как в C сегодня:

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

Но с концепцией массивов B, BCPL как указателей для этого требовалось бы, чтобы поле name содержало указатель, который должен был быть инициализирован во время выполнения для области памяти из 14 байтов в пределах структура. Проблема инициализации / компоновки была в конечном итоге решена путем предоставления массивам специальной обработки: компилятор отслеживал бы расположение массивов в структурах, в стеке и т. Д., Фактически не требуя указателя на данные для материализации, за исключением выражений, которые включают массивы. Эта обработка позволила почти всему B-коду все еще выполняться и является источником «массивов, преобразованных в указатель, если вы посмотрите на них» . Это хак совместимости, который оказался очень удобным, потому что он допускал массивы открытого размера и т. Д.

И вот мое предположение, почему массив не может быть назначен: поскольку массивы были указателями в B, вы можете просто написать:

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

чтобы перебазировать "массив". Теперь это было бессмысленно, потому что база переменной массива больше не была lvalue. Таким образом, это назначение было запрещено, что помогло отловить несколько программ, которые делали это перебазирование на объявленные массивы . И затем это понятие застряло: поскольку массивы никогда не предназначались для первого класса, цитируемого системой типов C, они в основном рассматривались как специальные звери, которые становятся указателем, если вы их используете. И с определенной точки зрения (которая игнорирует тот факт, что C-массивы являются неудачным взломом), запрещение назначения массива все еще имеет некоторый смысл: открытый массив или параметр функции массива обрабатываются как указатель без информации о размере. Компилятор не имеет информации для создания для них назначения массива, и назначение указателя было необходимо для совместимости. Введение назначения массива для объявленных массивов привело бы к ошибкам, хотя ложные назначения (это = назначение указателя = ba или поэлементное копирование?) И другие проблемы (как передать массив по значению?) Без фактического решения проблемы - просто сделать все явно с 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
}

Это не изменилось, когда в редакцию C в 1978 году было добавлено присвоение структуры (http://cm.bell -labs.com / cm / cs / who / dmr / cchanges.pdf ). Хотя записи были различных типов в C, в ранних версиях K & R их было невозможно назначить. Вы должны были копировать их по элементам с помощью memcpy, и вы могли передавать только указатели на них в качестве параметров функции. Назначение (и передача параметров) теперь просто определялось как memcpy необработанной памяти структуры, и так как это не могло сломать существующий код, оно было легко добавлено. В качестве непреднамеренного побочного эффекта это неявно вводило какое-то назначение массива, но это происходило где-то внутри структуры, так что это не могло действительно создать проблемы с использованием массивов.

28 голосов
/ 09 августа 2010

Что касается операторов присваивания, стандарт C ++ гласит следующее (C ++ 03 §5.17 / 1):

Существует несколько операторов присваивания ... для всех требуется изменяемое значение lvalueкак их левый операнд

Массив не является изменяемым lvalue.

Однако присвоение объекту типа класса определено специально (§5.17 / 4):

Назначение объектам класса определяется оператором присвоения копии.

Итак, мы посмотрим, что делает неявно объявленный оператор присвоения копии для класса (§12.8 / 13):

Неявно определенный оператор назначения копирования для класса X выполняет членское присваивание своих подобъектов.... Каждый подобъект присваивается способом, соответствующим его типу:
...
- если подобъект является массивом, каждому элементу присваивается способ, соответствующий типу элемента
...

Итак, для объекта типа класса массивы копируются правильно.Обратите внимание, что если вы предоставляете пользовательский оператор назначения копирования, вы не сможете воспользоваться этим, и вам придется копировать массив поэлементно.


Обоснование аналогично в C(C99 §6.5.16 / 2):

Оператор присваивания должен иметь изменяемое значение l в качестве своего левого операнда.

И §6.3.2.1 / 1:

Модифицируемое lvalue - это lvalue, у которого нет типа массива ... [другие ограничения следуют]

В C назначение намного проще, чем в C ++ (§6.5.16.1 / 2):

В простом присваивании (=) значение правого операнда преобразуется в тип выражения присваивания и заменяет значение, хранящееся в объекте, обозначенном левым операндом.

Для присвоения объектов типа структуры левый и правый операнды должны иметь одинаковый тип, поэтому значение правого операнда просто копируется в левый операнд.

2 голосов
/ 09 августа 2010

В этой ссылке: http://www2.research.att.com/~bs/bs_faq2.html есть раздел о назначении массива:

Две фундаментальные проблемы с массивами заключаются в том, что

  • массив не знает егособственный размер
  • имя массива преобразуется в указатель на его первый элемент при малейшей провокации

И я думаю, что это принципиальное различие между массивами и структурами.Переменная массива - это элемент данных низкого уровня с ограниченными знаниями о себе.По сути, это кусок памяти и способ индексации в нем.

Итак, компилятор не может определить разницу между int a [10] и int b [20].

Структуры, однако, не имеют такой же двусмысленности.

0 голосов
/ 09 августа 2010

Я знаю, все, кто ответил, являются экспертами в C / C ++.Но я подумал, что это основная причина.

num2 = num1;

Здесь вы пытаетесь изменить базовый адрес массива, что недопустимо.

и, конечно, struct2 = struct1;

Здесь назначен объект struct1на другой объект.

...