Почему я не могу назначить массив другому массиву в C - PullRequest
1 голос
/ 09 июля 2020
#include<stdio.h>    

int main(){    
  int a[] = {1,2,3};
  int b[] = {4,5,6};
  b = a;
  return 0;  
 } 

Результат этой ошибки:

array type 'int [3]' is not assignable

Я знаю, что массивы имеют lvalue и не могут быть присвоены, но в этом случае все, что нужно сделать компилятору, - это переназначить указатель. b должен просто указывать на адрес a. Почему это невозможно?

Ответы [ 5 ]

3 голосов
/ 09 июля 2020

Я знаю, что массивы являются l-значениями и не могут быть присвоены, но в этом случае все, что нужно сделать компилятору, - это переназначить указатель. b должен просто указывать на адрес a. Почему это невозможно?

Потому что b не является указателем. Когда вы объявляете и выделяете a и b, вы получаете следующее:

+---+
| 1 | a[0]
+---+
| 2 | a[1]
+---+
| 3 | a[2]
+---+
 ...
+---+
| 4 | b[0]
+---+
| 5 | b[1]
+---+
| 6 | b[2]
+---+

Для каких-либо указателей места не выделяется. Не существует объекта-указателя a или b, отдельного от самих элементов массива.

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

   +---+
b: | +-+--+
   +---+  |
    ...   |
     +----+
     |
     V
   +---+
   |   | b[0]
   +---+
   |   | b[1]
   +---+
    ...
   +---+
   |   | b[N-1]
   +---+

Когда Деннис Ритч ie разрабатывал C, он хотел сохранить семантику массива B (в частности, a[i] == *(a + i)), но он не хотел хранить этот отдельный указатель где угодно. Поэтому вместо этого он создал следующее правило - если это не операнд операторов sizeof или унарных &, или строковый литерал, используемый для инициализации массива символов в объявлении, выражение типа «N-элементный массив T» будет преобразован («распад») в выражение типа «указатель на T», и значением выражения будет адрес первого элемента массива, и это значение не является lvalue .

Это имеет несколько практических эффектов, наиболее важным из которых является то, что выражение массива может не быть целью присваивания. Выражения массивов теряют свою "массивность" в большинстве случаев и просто не рассматриваются как другие типы.

Edit

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

End Edit

В результате вы не можете скопировать содержимое одного массива в другой. используя только оператор =. Вы должны либо использовать библиотечную функцию, например memcpy, либо копировать каждый элемент по отдельности.

3 голосов
/ 09 июля 2020

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

"b должен просто указывать на адрес a. Почему это невозможно? "

Вы, кажется, здесь что-то путаете. b не является указателем . Это массив из трех int элементов.

b = a;

Поскольку b используется здесь как lvalue в присваивании, он принимается как тип int [3], а не int *. Указатель на правило распада не имеет здесь места для b, только для a как rvalue.

Вы не можете назначить массив (здесь b) указателем на первый элемент другого массива (здесь a), используя b = a; в C.

Синтаксис этого не позволяет.

Вот в чем ошибка

" тип массива 'int [3]' не назначается"

говорит вам для b.

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

Это неверно. Это преобразование происходит только очень неявным образом и является предметом этого SO-вопроса:

Изменен ли массив для распада указателя на объект-указатель?

Если вы хотите присвоить значения из массива a массиву b, вы можете использовать memcpy():

memcpy(b, a, sizeof(a));
2 голосов
/ 09 июля 2020

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

Просто оберните свой массив в структуре.

#include <stdio.h>

int main(){
  struct Array3 {
    int t[3];
  };
  struct Array3 a = {{1,2,3}};
  struct Array3 b = {{4,5,6}};
  a = b;
  printf("%d %d %d", a.t[0], a.t[1], a.t[2]);
  return 0;
 }

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

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

1 голос
/ 09 июля 2020

Переменная b в вашем коде размещается в стеке как 3 последовательных int s. Вы можете взять адрес b и сохранить его в переменной типа int*. Вы можете присвоить ему значение, если вы выделите массив в куче и сохраните только указатель на него в стеке, в этом случае вы действительно можете изменить значение указателя, чтобы оно было таким же, как a.

0 голосов
/ 15 июля 2020

Из C Язык программирования :

Имя массива - это адрес нулевого элемента.

Там Это одно из различий между именем массива и указателем, которое необходимо иметь в виду. Указатель - это переменная. Но имя массива не является переменной.

Я понимаю, что имя массива является константой, поэтому его нельзя присвоить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...