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

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

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 : Множество отличных ответов. Я выбрал Лютера Блиссетта , потому что меня больше всего интересовало философское или историческое обоснование поведения, но ссылка Джеймса МакНеллиса на сопутствующую документацию спецификаций также была полезна.

82
задан Antti Haapala 23 March 2019 в 13:06
поделиться

4 ответа

Вот мое мнение по этому поводу:

The Development of the C Language предлагает некоторое понимание эволюции типа массива в 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 типами struct. Он хотел, чтобы массивы были частью структур, как в современном Си:

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

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

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

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

чтобы перебазировать "массив". Теперь это было бессмысленно, потому что база переменной массива больше не была l-значением. Так что это назначение было запрещено, что помогло отловить те немногие программы, которые делали этот rebasing на объявленных массивах. А потом это понятие прижилось: Поскольку массивы никогда не были предназначены для того, чтобы стать первоклассным объектом системы типов языка Си, к ним стали относиться как к особым зверям, которые становятся указателями, если вы их используете. И с определенной точки зрения (которая не учитывает, что массивы в Си - это неудачный хак), запрет на присваивание массивов все еще имеет некоторый смысл: Открытый массив или параметр функции массива рассматривается как указатель без информации о размере. У компилятора нет информации, чтобы сгенерировать для них присвоение массива, а присвоение указателя было необходимо по соображениям совместимости. Внедрение присваивания массива для объявленных массивов привело бы к ошибкам, включая ложные присваивания (является ли 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
}

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

43
ответ дан 24 November 2019 в 09:21
поделиться

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

Существует несколько операторов присваивания... все они требуют изменяемое значение l в качестве левого операнда

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

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

Присвоение объектам класса определяется оператором присвоения copy.

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

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

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


В Си рассуждения аналогичны (C99 §6.5.16/2):

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

И §6.3.2.1/1:

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

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

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

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

28
ответ дан 24 November 2019 в 09:21
поделиться

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

num2 = num1;

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

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

Здесь объект struct1 присваивается другому объекту.

0
ответ дан 24 November 2019 в 09:21
поделиться

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

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

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

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

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

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

2
ответ дан 24 November 2019 в 09:21
поделиться