Передача всего массива в функцию - PullRequest
9 голосов
/ 22 октября 2010

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

Но это не тот случай, когда мы передаем целый массив.В этом случае он (вызываемая функция) получает доступ к фактическим аргументам, и любые изменения, внесенные в формальные аргументы, влияют на фактические аргументы.Почему это происходит?

Ответы [ 8 ]

17 голосов
/ 22 октября 2010

Массив передается как указатель на элементы.

Если я напишу:

void doStuff(int *ptr)
{
  ptr[1] = 5;
}

int main()
{
  int arr[] = {0, 1, 2};
  doStuff(arr);
  printf("%d %d %d\n", arr[0], arr[1], arr[2]);
}

вывод будет "0 5 2". Это потому, что C передает то, что на самом деле является параметром, по значению. Параметр является указателем на int. Таким образом, указатель на int передается по значению. Таким образом, doStuff получает копию указателя на память в кадре стека main. Когда он разыменовывает этот указатель с помощью ptr[1], он следует за указателем на основную память и изменяет там массив.

C может проходить только по значению, но это происходит "мелко". Если вы попросите его передать int *, он передаст int *. Он только копирует значение указателя, а не значения всего, на что он указывает.

Если вы хотите, чтобы doStuff получал свою собственную копию массива, либо оберните массив в структуру, как предлагали другие, либо используйте memcpy для глубокого копирования массива вручную следующим образом:

void doStuff(int *ptr, int nElems)
{
  int myCopyOfTheArray[nElems];
  memcpy(myCopyOfTheArray, ptr, sizeof(int) * nElems);
  /* do stuff with the local copy */
}

В отличие от использования структуры, подход memcpy работает, если nElems известен только во время выполнения.

8 голосов
/ 22 октября 2010

Массивы не могут передаваться по значению в C. Они понижаются до указателей.Таким образом, вызываемая функция видит указатель на массив (переданный по ссылке) и оперирует им.Если вы хотите сделать копию, вы должны либо сделать это явно, либо поместить свой массив в структуру (которая может быть передана по значению в функции.)

5 голосов
/ 22 октября 2010

В целом, сделать копию всего массива дорого.В те времена, когда C был впервые создан, он мог легко исчерпать ресурсы машины (в частности, стек).

Если вы действительно хотите передать массив, оберните его в структуру:

struct wrap { int array[100]; };

int somefunc(struct wrap large) { ... }

void anotherfunc(void)
{
    struct wrap a;
    ...add data to array...
    printf("%d\n", somefunc(a));
}
3 голосов
/ 22 октября 2010

На самом деле я еще не видел ни одного ответа, пока не рассмотрел весь вопрос. Из того, что я вижу, похоже, что вопрос задает что-то вроде этого:

С учетом следующего кода:

int a[5];

foo(a);
bar(a[0]);

Почему foo может манипулировать исходными значениями, в то время как bar получает только копию передаваемого значения?

Это связано с использованием записи массива: оператор [] разыменовывает элемент, на который он ссылается в массиве, и передает значение в этом месте.

So

int a[5];
bar(a[0]);

отличается от:

int* a;
bar(a);

Если вы хотите передать ссылки на определенный элемент в массиве, вам нужно использовать оператор &:

bar(&a[5]);
3 голосов
/ 22 октября 2010

«Передача по ссылке» - красная сельдь на языке C. Все аргументы функции передаются только по значению.В случае массивов вы передаете адрес первого элемента в виде указателя.Затем вы можете использовать этот указатель для обращения к нужной ячейке памяти и чтения или записи значений.

2 голосов
/ 22 октября 2010

Из онлайн-стандарта C :

6.3.2.1 L-значения, массивы и обозначения функций
...
3 За исключением случаев, когдаявляется операндом оператора sizeof или унарного оператора & , или строковым литералом, используемым для инициализации массива, выражение с типом "массив типа" преобразуется ввыражение с типом «указатель на тип», которое указывает на начальный элемент объекта массива и не является lvalue.Если объект массива имеет класс хранения регистров, поведение не определено.

Предположим следующий фрагмент кода:

int a[10];
...
foo(a);

При вызове foo тип выражения a равен "10-элементному массивуint».Однако, поскольку выражение не является операндом операторов sizeof или &, и поскольку оно не является строковым литералом, используемым для инициализации другого массива в объявлении, его тип неявно преобразуется ("распадается")от "10-элементного массива int" до "указателя на int", и его значением будет адрес первого элемента в массиве (то есть &a[0]).

Следовательно, foo получает указатель на int, а не массив.Разыменование или подписка этого указателя позволяет вам изменять значения массива.

Это особенность языка Си;массивы не являются первоклассными объектами.Фактически, в большинстве случаев (включая подписку) массив выражений будет преобразован в типы указателей.

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

1 голос
/ 22 октября 2010

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

1 голос
/ 22 октября 2010

Массив в C может рассматриваться как указатель на первый элемент массива. Указатель передается по значению (вы не можете изменить переданное значение), но вы можете разыменовать его и изменить память, на которую он указывает.

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