Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > как передать массив в функцию по значению?


Автор: Dmitriy2k 30.3.2008, 11:29
Подскажите, можно ли передать копию массива в функцию?
т.е. обычно мы передаем в функцию как бы указатель на первый элемент массива
и выглядит это так:

FuncArray(int arr[][20]){
...
}

main()
{
    int a[2][20];
    FuncArray(a);
}

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

Автор: bsa 30.3.2008, 11:38
Код
typedef int Array[2][20];
FuncArray(Array arr)
{
 ....
}
main()
{
    Array a;
    FuncArray(a);
}

Автор: Dennnis 30.3.2008, 11:38
Код

struct Arr
{
    int arr[2][20];
};

FuncArray(Arr arr){
...
}
main()
{
    Arr a;
    FuncArray(a);
}


или

Код

FuncArray(const std::vector<std::vector<int> >& arr){
...
}

main()
{
    std::vector<std::vector<int> > arr;
    FuncArray(arr);
}

Автор: bsa 30.3.2008, 11:41
Dennnis, во втором случае передачи идет по ссылке, по сути, тоже самое, что по указателю.

Автор: Dennnis 30.3.2008, 11:49

Цитата(bsa @  30.3.2008,  11:41 Найти цитируемый пост)
Dennnis, во втором случае передачи идет по ссылке, по сути, тоже самое, что по указателю. 

Ты имеешь ввиду надо так:
Код

FuncArray(std::vector<std::vector<int> > arr/*Теперь копирует*/){
...
}
main()
{
    std::vector<std::vector<int> > arr;
    FuncArray(arr);
}

Автор: bsa 30.3.2008, 11:53
Dennnis, да. Теперь уже передача по значению.

Автор: Dmitriy2k 30.3.2008, 14:50
Цитата

std::vector<std::vector<int> > arr;


Мне не очень понятно что означают угловые скобки. Я сколько писал на Си ни разу не встречал :(
[quote]

Автор: bsa 30.3.2008, 22:59
Цитата(Dmitriy2k @ 30.3.2008,  14:50)
Цитата

std::vector<std::vector<int> > arr;


Мне не очень понятно что означают угловые скобки. Я сколько писал на Си ни разу не встречал :(

Это С++. std::vector - это шаблон динамического массива.
В этой строчке, в частности, создание массива массивов int.

Автор: Rififi 30.3.2008, 23:43
Цитата
т.е. обычно мы передаем в функцию как бы указатель на первый элемент массива

массив не является указателем на первый элемент а неявно приводится к нему компилятором.
вот пример функцмии, в который передаётся сам массив, без потери информации о типе.

Код

template <size_t N>
void f(int (&a)[N])
{
    std::cout << "в массиве " << sizeof(a) / sizeof(a[0]) << " элементов." << std::endl;
}

int arr[10];
f(arr);

Автор: bsa 31.3.2008, 11:40
Rififi, читаем вопрос Dmitriy2k внимательно - речь идет о языке Си

Автор: Rififi 31.3.2008, 12:17
bsa, 
а чего тогда ему советуют вектор?

Для Си кроме отсутствия ссылочного типа ничего не меняется
void f(int (*arr)[10])
{
    printf("%d элементов.\n", sizeof(*arr) / sizeof((*arr)[0]));
}

int arr[10];
FuncArray(&arr);

Автор: bsa 31.3.2008, 12:28
Rififi, ты ему предлагаешь передать по указателю, а ему нужно по значению

Автор: Rififi 31.3.2008, 13:40
чтобы передать "массив по-значению", нужно чтобы он был lvalue и/или имел конструктор копирования.
а массив, как известно, не является lvalue, зато является POD-типом, следовательно, запрашиваемое в исходном вопросе невозможно.

Автор: UnrealMan 31.3.2008, 15:23
Цитата(Rififi @  31.3.2008,  13:40 Найти цитируемый пост)
чтобы передать "массив по-значению", нужно чтобы он был lvalue и/или имел конструктор копирования.
а массив, как известно, не является lvalue

Это ты где такое вычитал?

Автор: Graf_h 31.3.2008, 16:39
Цитата(Dmitriy2k @  30.3.2008,  11:29 Найти цитируемый пост)
Подскажите, можно ли передать копию массива в функцию

Это честно говоря глупо smile ! лудше по сonst ссылке smile 

Автор: vinter 31.3.2008, 18:26
Цитата(Graf_h @  31.3.2008,  17:39 Найти цитируемый пост)
Это честно говоря глупо  ! лудше по сonst ссылке  

угу, это очень умно, особенно если учесть что
Цитата(Dmitriy2k @  30.3.2008,  12:29 Найти цитируемый пост)
 чтобы можно было в функцииизменять значения элементов, не повреждая исходный массив.


Цитата(Rififi @  31.3.2008,  00:43 Найти цитируемый пост)
массив не является указателем на первый элемент а неявно приводится к нему компилятором.

имя массива есть адрес первого элемента, т.е можно считать указателем.
Цитата(Rififi @  31.3.2008,  14:40 Найти цитируемый пост)
чтобы передать "массив по-значению", нужно чтобы он был lvalue и/или имел конструктор копирования.

чего?  smile 

Автор: Rififi 31.3.2008, 18:37
vinter, процитированная фраза - это исключительно моё предположение, потому что я не знаю, что означает "передать массив по-значению".

Автор: bsa 31.3.2008, 21:15
Цитата(Rififi @ 31.3.2008,  18:37)
vinter, процитированная фраза - это исключительно моё предположение, потому что я не знаю, что означает "передать массив по-значению".

Похоже, массив действительно нельзя передать по значению. Тогда придется либо через структуру извращаться, либо вручную создавать копию

Автор: JackFlamingo 31.3.2008, 21:33
На мой взгляд, наиболее элегантное решение в отношении переносимости и понимаемости кода - это передать массив по ссылке с модификатором const, а затем ВРУЧНУЮ создать копию.

Автор: vinter 31.3.2008, 22:34
Цитата(JackFlamingo @  31.3.2008,  22:33 Найти цитируемый пост)
На мой взгляд, наиболее элегантное решение в отношении переносимости и понимаемости кода - это передать массив по ссылке с модификатором const

зачем? какой смысл тут от передачи по ссылке?

Автор: MAKCim 1.4.2008, 09:22
Цитата(bsa @  30.3.2008,  11:38 Найти цитируемый пост)

typedef int Array[2][20];
FuncArray(Array arr)
{
 ....
}
main()
{
    Array a;
    FuncArray(a);
}

а теперь посмотри ассемблерный листинг  smile 

как бы мы не старались передать массив по значению, не получится
единственный вариант - инкапсуляция
Код

typedef struct {
    int array[2][2];
} array_t;

Автор: Dmitriy2k 1.4.2008, 09:31
Понятно. Спасибо за ответы. Ну чтож единственным вариантом является передача элемента структуры, содержащей массив, в функцию

Автор: UnrealMan 1.4.2008, 14:36
Цитата(vinter @ 31.3.2008,  18:26)
Цитата(Graf_h @  31.3.2008,  17:39 Найти цитируемый пост)
Это честно говоря глупо  ! лудше по сonst ссылке  

угу, это очень умно, особенно если учесть что
Цитата(Dmitriy2k @  30.3.2008,  12:29 Найти цитируемый пост)
 чтобы можно было в функцииизменять значения элементов, не повреждая исходный массив.

А создать копию массива внутри функции религия не позволяет?

Цитата(vinter @  31.3.2008,  18:26 Найти цитируемый пост)
имя массива есть адрес первого элемента, т.е можно считать указателем.

Имя массива - это не указатель.

Цитата(vinter @  31.3.2008,  22:34 Найти цитируемый пост)
Цитата(JackFlamingo @  31.3.2008,  21:33 Найти цитируемый пост)
На мой взгляд, наиболее элегантное решение в отношении переносимости и понимаемости кода - это передать массив по ссылке с модификатором const, а затем ВРУЧНУЮ создать копию. 

зачем? какой смысл тут от передачи по ссылке?

Затем, что такая передача позволяет контролировать размерность (если она должна быть жёстко задана). Вместо ссылки на массив можно использовать указатель на массив (не путать с указателем на первый элемент).

Цитата(MAKCim @  1.4.2008,  09:22 Найти цитируемый пост)
а теперь посмотри ассемблерный листинг  

А может, лучше всё-таки в стандарт посмотреть?

Цитата(8.3.5 Functions)
After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively.

Невозможно сделать параметр функции массивом. Всякий параметр, объявление которого выглядит, как объявление массива, на самом деле будет объявленным как указатель. Для массивов не существует copy-initialization, а именно copy-initialization выполняется при передаче параметра:

Цитата(8.5 Initializers)
The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form
T x = a;

При отсутствии у массивов copy-initialization функция, параметром которой был бы массив, попросту никогда бы не могла быть вызвана.

Автор: Rififi 1.4.2008, 15:40
MAKCim, 
 
Цитата
а теперь посмотри ассемблерный листинг

листинг доказывает только то, что массив и его первый элемент начинаются с одного адреса, "и чё дальше"?

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

во-первых, обьясни, что это значит, "массив по значению"?
во-вторых, выше была приведена функция, которая получает массив на вход (да-да, именно сам массив, а ни какой не указатель)

Автор: vinter 1.4.2008, 19:15
Цитата(UnrealMan @  1.4.2008,  15:36 Найти цитируемый пост)
А создать копию массива внутри функции религия не позволяет?

причем тут это? причем тут константные ссылки? читай название топика smile  никто не спрашивал "как лучше передать, чтобы потом скопировать"

Добавлено через 4 минуты и 42 секунды
Цитата(UnrealMan @  1.4.2008,  15:36 Найти цитируемый пост)
Имя массива - это не указатель.

не будем играть терминами, это практически одно тоже, в случае одномерного. И не надо меня сейчас тыкать стандартом, и с пеной на губах доказывать что я не прав. На практике это работает точно так же, а значит это эквивалент ;)

Автор: UnrealMan 1.4.2008, 20:39
Цитата(vinter @  1.4.2008,  19:15 Найти цитируемый пост)
никто не спрашивал "как лучше передать, чтобы потом скопировать"

Ответ на поставленный в топике вопрос - никак. Всё, что тут можно предложить, - это обходное решение.

Цитата(vinter @  1.4.2008,  19:15 Найти цитируемый пост)
не будем играть терминами, это практически одно тоже, в случае одномерного.

ну да, здесь

Код

void f(char (&)[100])
{
    std::cout << "array" << std::endl;
}

void f(void *)
{
    std::cout << "pointer" << std::endl;
}

int main()
{
    char arr[100];
    std::cout << sizeof(arr) << std::endl;
    f(arr);
}

и здесь

Код

void f(char (&)[100])
{
    std::cout << "array" << std::endl;
}

void f(void *)
{
    std::cout << "pointer" << std::endl;
}

int main()
{
    char arr[100];
    char *p = arr;
    std::cout << sizeof(p) << std::endl;
    f(p);
}

вывод будет практически один и тот же smile

Автор: vinter 1.4.2008, 21:32
UnrealMan, я не говорил что массивы ничем от указателей не отличаются, я сказал что имя массива можно считать указателем(константным). Специфика использования  одинакова, и разница между ними не существенна..

Автор: MAKCim 1.4.2008, 21:32
Цитата(UnrealMan @  1.4.2008,  14:36 Найти цитируемый пост)
А может, лучше всё-таки в стандарт посмотреть?

мне достаточно ассемблерного листинга

Цитата(Rififi @  1.4.2008,  15:40 Найти цитируемый пост)
листинг доказывает только то, что массив и его первый элемент начинаются с одного адреса, "и чё дальше"?

Код

typedef int array[2][2];

void f2(array a) {
}

void f1(array a) {
    f2(a);
}

int main() {
    array a;
    f1(a);
    return 0;
}

Код

f1:
    push   rbp
    mov    rbp, rsp
    sub    rsp, 8
    mov    qword [rbp - 8], rdi
    mov    rdi, qword [rbp - 8]
    call    f2
    leave
    ret

main:
    push  rbp
    mov   rbp, rsp
    sub   rsp, 16
    lea    rdi, qword [rbp - 16]
    call    f1
    mov    eax, 0
    leave
    ret

теперь понятно?
если нет, объясню

Автор: Rififi 1.4.2008, 22:57
vinter, 
Цитата
имя массива можно считать указателем(константным)


int arr[10];

в этой записи имя 'arr' имеет тип 'массив из 10 элементов типа int'. всё. где тут уазатель, покажите мне его? а нету. никакой указатель, константный или нет ни имеет к данной записи ни малейшего отношения. если внутри, в машинном представлении какой-то конкретный компилятор реализует массивы через указатели, как очевидно пытается доказать MAKCim, то я рад за них обоих. только причём здесь C/C++? меня вообще не волнует, что там сидит в машинном коде, да и раздел этот про C/C++.
мифические "указатели" появляются, как только имя arr будет использовано в выражениях. а именно, компилятор приводит имя arr к типу int*. так почему же arr - не указатель? да потому, что такое приведение типов не срабатывает всегда:
1. оператор sizeof(arr) вернет размер, равный 10*sizeof(int), а не sizeof(int*) как это было бы в случае указателя
2. унарный оператор & для выражения &arr имеет тип int(*)[10] а не int**

а результат унарного оператора нужен например затем, чтобы итерироваться по подмассивам:
int arr[10][10];
int (*p)[10] = &a[0];

(*p)[0] = 10; // работаем с первым подмассивом
++p; переходим на следующий подмассив.
(*p)[0] = 20; // продолжаем инициализацию



Автор: baldina 1.4.2008, 23:56
Цитата

Подскажите, можно ли передать копию массива в функцию?

Можно. Делаешь копию массива и передаешь ;)
Можно наоборот - передаешь исходный, а внутри делаешь копию. В этом случае видимо удобно воспользоваться функций _alloca():
Код

void f (const int* array, size_t count)
{
  size_t size = count * sizeof(int);
  int *temp = (int*)_alloca (size);
  memcpy (temp, array, size);
  // работаем с temp
}

Автор: vinter 2.4.2008, 06:15
Rififi, блин, указатель это переменная содержащая адрес, имя массива является адресом. Связи не улавливаешь?
Цитата(Rififi @  1.4.2008,  23:57 Найти цитируемый пост)
в этой записи имя 'arr' имеет тип 'массив из 10 элементов типа int'. всё. где тут уазатель, покажите мне его?

реализация разная, а вот использование одинаково.

Автор: Dmitriy2k 2.4.2008, 12:18
Цитата(UnrealMan @  1.4.2008,  14:36 Найти цитируемый пост)
Имя массива - это не указатель.


а как тогда объяснишь это?

Код

#include "stdafx.h"
#include <stdio.h>
#include <malloc.h>


int main()
{
   int **ptr=(int**)malloc(100);
   ptr[4][4]=5;
   
   return 0;
}

Автор: vinter 2.4.2008, 12:23
Dmitriy2k, тут ты объявляешь двойной указатель, а не массив

Автор: Dmitriy2k 2.4.2008, 12:56
правильней конечно будет так:
Код


#include <stdio.h>
#include <malloc.h>


int main()
{
   int arr[]={4,4,4,4,4,4,4};
   int *ptr=arr;  

   ptr[4]=5;

   printf("ptr[4]=%d\n",ptr[4]);
   
   return 0;
}




Цитата(vinter @  2.4.2008,  12:23 Найти цитируемый пост)
Dmitriy2k, тут ты объявляешь двойной указатель, а не массив 

а какая разница то компилятор все равно имя массива приводит к указателю(адресу первого элемента массива)

смоти ассемблерный код:
Код

8:       int arr[]={4,4,4,4,4,4,4};
0040B4D8   mov         dword ptr [ebp-1Ch],4      //первый элемент массива
0040B4DF   mov         dword ptr [ebp-18h],4
0040B4E6   mov         dword ptr [ebp-14h],4
0040B4ED   mov         dword ptr [ebp-10h],4
0040B4F4   mov         dword ptr [ebp-0Ch],4
0040B4FB   mov         dword ptr [ebp-8],4
0040B502   mov         dword ptr [ebp-4],4
9:       int *ptr=arr;
0040B509   lea         eax,[ebp-1Ch]                         //помещаем адрес массива
0040B50C   mov         dword ptr [ebp-20h],eax
10:
11:      ptr[4]=5;
0040B50F   mov         ecx,dword ptr [ebp-20h]
0040B512   mov         dword ptr [ecx+10h],5

Автор: Rififi 2.4.2008, 12:59
vinter, 
Цитата
реализация разная, а вот использование одинаково.

внимательно перечитай мое предыдущее сообшение, особенно про операторы sizeof и &

Добавлено через 2 минуты и 27 секунд
Цитата(Dmitriy2k @  2.4.2008,  12:18 Найти цитируемый пост)
а как тогда объяснишь это?

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

void f(int**);

int arr[10][10];
f((int**) arr);

или даже так:

int* p = NULL;
p[0] = 1;

Автор: vinter 2.4.2008, 13:28
Rififi, имя массива есть адрес, указатель есть переменная хранящая адрес. Для меня очевидно что эти понятия эквивалентны, даже если в одном есть некоторый функционал отсутсвующий у другого.
к тому же ты привел пример различия, которое используется достаточно редко, а вот то что используется часто работает одинаково.

Автор: MAKCim 2.4.2008, 16:39
Цитата(Rififi @  1.4.2008,  22:57 Найти цитируемый пост)
если внутри, в машинном представлении какой-то конкретный компилятор реализует массивы через указатели, как очевидно пытается доказать MAKCim

я пытаюсь доказать, что в С/С++ массив нельзя передать в функцию по значению
ассемблерный код тому подтверждение
точка

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)