Подтвердить что ты не робот

Какое обоснование для предотвращения назначения массивов?

Я попытался это сделать и прочитал:

Но все они указывают на очевидность: вы не можете назначать массивы, потому что стандарт так говорит. Это здорово и все, но я хочу знать, почему стандарт не включает поддержку назначения массивов. В стандартном комитете подробно обсуждаются вещи, и я был бы удивлен, если бы они никогда не обсуждали возможность назначения массивов. Предполагая, что они обсудили это, у них должно быть некоторое обоснование того, что нельзя назначать массивы.

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

struct wrapper
{
    int array[2];
};

struct wrapper a = {{1, 2}};
struct wrapper b = {{3, 4}};
a = b; // legal

Но использование массива напрямую запрещено, хотя оно эффективно выполняет одно и то же:

int a[2] = {1, 2};
int b[2] = {3, 4};
a = b; // Not legal

Каково стандартное обоснование комитета для запрещения назначения массивов?

4b9b3361

Ответ 1

Поймите, что намерение заключалось не в том, чтобы сделать выражения массива нецелесообразными; это не было целью 1. Скорее, это поведение выпадает из дизайнерского решения, которое Ritchie сделал упрощенной обработкой массива в компиляторе, но в обмене вырабатывает массивы выражений "объекты второго класса"; они теряют свою "массивность" в большинстве контекстов.

Прочитайте эту статью (особенно раздел под названием "Эмбриональный С" ) для некоторого фона; У меня также есть более подробный ответ здесь.


1. С возможным исключением Perl или PHP 2 наиболее вопиющие языковые WTF обычно являются авариями дизайна или результатом компромиссов; большинство языков не намеренно проектируются, чтобы быть глупыми.

2. Я только немного троллирую; Perl и PHP - беспорядочные.

Ответ 2

В C присваивание копирует содержимое объекта фиксированного размера другому объекту фиксированного размера. Это хорошо определено и довольно просто реализовать для скалярных типов (целые числа, с плавающей точкой, указатели, сложные типы с C99). Назначение структур почти так же просто; для более крупных может потребоваться вызов memcpy() или эквивалент, но он по-прежнему прост, так как размер и выравнивание известны во время компиляции.

Массивы - это другое дело. Большинство объектов массива имеют размеры, которые не определены до времени выполнения. Хорошим примером является argv. Среда выполнения создает массив char для каждого аргумента командной строки, а массив char* содержит указатели на аргументы. Они доступны для main через argv, a char** и через динамически распределенные массивы char[], на которые указывают элементы argv.

C массивы являются объектами в своем собственном праве, но обычно они не доступны как объекты. Вместо этого их элементы получают доступ через указатели, а код перемещается от одного элемента к другому, используя арифметику указателя.

Языки могут быть разработаны для обработки массивов как объектов первого класса с назначением - но это сложно. Как разработчик языка, вы должны решить, является ли массив из 10 целых чисел и массив из 20 целых чисел одним и тем же типом. Если они есть, вы должны решить, что происходит, когда вы пытаетесь присвоить их другому. Копирует ли он меньший размер? Вызывает ли это исключение во время выполнения? Вам нужно добавить операцию среза, чтобы вы могли работать с подмножествами массивов?

Если int[10] и int[20] - разные типы без неявного преобразования, то операции массива являются негибкими (см., например, Паскаль).

Все эти вещи могут быть определены (см. Ada), но только путем определения конструкций более высокого уровня, чем типичные для C. Вместо этого разработчики C (в основном Деннис Ритчи) решили предоставить массивы с низкоуровневыми операциями. Это, по общему признанию, неудобно порой, но это структура, которая может быть использована для реализации всех операций массива более высокого уровня любого другого языка.

Ответ 3

Причина в основном историческая. Был C еще до ISO C89, который назывался "K & R" C, после Kernighan и Ritchie. Язык был разработан так, чтобы быть достаточно маленьким, чтобы компилятор входил в строго ограниченную (по сегодняшним меркам) память объемом 64 КБ.

Этот язык не позволяет назначать массивы. Если вы хотите скопировать массивы одинакового размера, memcpy был для ваших нужд. Написание memcpy(a, b, sizeof a) вместо a = b, конечно, не является большим осложнением. Он имеет дополнительное преимущество в том, что он может быть обобщен для массивов различного размера и массивов массивов.

Интересно, что обходное решение struct, о котором вы упоминаете, также не работает в K & R C. Вам приходилось либо назначать членов по одному, либо снова использовать memcpy. первое издание K & R Язык программирования C упоминает назначение struct как функцию для будущей реализации на языке. Что в конечном итоге произошло с C89.

Ответ 4

Ответ прост: он никогда не разрешался до того, как комитет вошел (даже struct -ассоциирование считалось слишком тяжелым), и учитывая, что разложение массива будет иметь всевозможные интересные последствия.

Посмотрим, что изменится:

int a[3], b[3], *c = b, *d = b;
a = b; // Currently error, would assign elements
a = c; // Currently error, might assign array of 3?
c = a; // Currently pointer assignment with array decay
c = d; // Currently pointer assignemnt

Таким образом, позволяя присвоение массива сделать (до) два в настоящее время запрещенных назначений действительными.

Это не проблема, однако, что почти идентичные выражения будут иметь совершенно разные результаты.

Это становится особенно пикантным, если вы считаете, что аргумент массива в аргументах функции в настоящее время представляет собой просто отличную нотацию для указателей. Если было задано назначение массива, это станет еще более запутанным.
Не то, чтобы люди не были полностью смущены такими вещами, как сегодня...

int g(int* x);  // Function receiving pointer to int and returning int
int f(int x[3]);// Currently the same. What afterwards? Change to value-copy?

Ответ 5

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

Цитирование отрывок из этого ответа:

Вот почему вы не можете сделать что-то вроде

int a[N], b[N];
a = b;

поскольку как a, так и b оценивают значения указателя в этом контексте; это эквивалентно написанию 3 = 4. В памяти нет ничего, что фактически хранит адрес первого элемента в массиве; компилятор просто вычисляет его во время фазы перевода.

Ответ 6

Возможно, было бы полезно включить вопрос и спросить, почему вы когда-либо хотели бы назначать массивы (или структуры) вместо использования указателей? Это намного проще и понятнее (по крайней мере, если вы усвоили Zen of C), и это имеет преимущество, не скрывая того факта, что большая работа скрыта под "простым" назначением массивов с несколькими мегабайтами.