2

У меня есть функция, как это:Pass множественный одномерный массив в функцию в C

void myfunc(int** arr, int n) { 
    int i, j; 
    for(i=0; i<n; ++i) { 
    for(j=0; j<n; ++j) { 
     printf("%d,", *(arr + i*n + j)); // Print numbers with commas 
    } 
    printf("\n");      // Just breakline 
    } 
} 

в другой функции У меня есть двумерный массив так:

int main() { 
    int seqs[8][8] = { 
    {0, 32, 36, 52, 48, 16, 20, 4}, 
    {0, 16, 20, 52, 48, 32, 36, 4}, 
    {0, 32, 36, 44, 40, 8, 12, 4}, 
    {0, 8, 12, 44, 40, 32, 36, 4}, 
    {0, 32, 36, 38, 34, 2, 6, 4}, 
    {0, 2, 6, 38, 34, 32, 36, 4}, 
    {0, 32, 36, 37, 33, 1, 5, 4}, 
    {0, 1, 5, 37, 33, 32, 36, 4} 
    }; 

    // Call to myfunc 
    myfunc(seqs, 8); // This is the idea 
} 

Но компилятор перекидного меня эта ошибка:

Что такое правильный способ передать этот массив (seqs) к функции (myfunc)?

+0

http://www-ee.eng.hawaii.edu/~dyun/ee160/Book/chap7/section2. 1.2.html – Jeyaram

+0

Он должен выглядеть как 'void myfunc (int arr [] [8], int n) {'. –

+0

Из внешнего вида кода в функции вам действительно нужен 'int *', а не 'int **' для вашего аргумента 'arr'. Затем просто перейдите в адрес первого элемента ('& seqs [0] [0]'). – Dmitri

ответ

2

Массивы и указатели не совпадают. Точно так же двумерный массив и указатель на указатель - это не одно и то же. Тип seqs рассогласований аргумента функции, вы можете изменить подпись myfunc к:

void myfunc(int arr[][8], int n) 

или, вы можете изменить seqs к реальному указателю на указатель.

+0

Как вы можете успокоить предупреждение и все еще принимать массивы любого размера? Это реальная проблема, я думаю, – slezica

+1

@ uʍopǝpısdn. Вы правы, OP имеет параметр 'n' по какой-то причине. Реальный указатель на указатель - это один из вариантов. –

+0

@YuHao, я не могу изменить подпись 'myfunc', я могу изменить' seqs' из массива в указатель, но есть ли способ передать 'seqs' как' int [] [] 'или я должен его изменить? – Israel

1

Вы объявляете int** как массив со статическим размером, поэтому его подпись int *[8]. Вы можете избавиться от ошибки компилятора, выделив массив, когда вы вызываете myfunc.

myfunc((int **)seqs, 8); 

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

+1

Если вы выделяете динамический 2D-массив [правильно] (http://stackoverflow.com/questions/12462615/how-do-i-correctly-set-up-access-and-free-a-multidimensional-array-in- c), у вас будет такая же проблема, как здесь. Если вы выделите его как таблицу поиска на основе указателей, сегментированную по всей куче, да, тогда вам не понадобится бросок. – Lundin

+0

Полезно знать, я не знал 'sizeof' обрабатывал многомерные массивы, подобные этому. Похоже, может даже вернуться к C90. – OregonTrail

+0

Нет, к сожалению, это не так (см. Комментарии к принятому ответу), потому что вы не можете объявить указатель массива a с переменной длиной в C90, он должен быть константой времени компиляции. – Lundin

0

Способ, которым вы получаете доступ к значению из массива в своей функции, даст вам правильный элемент с простым int *, а не int **. С *(arr + i*n + j). i*n доставит вас в правый ряд, и j доставит вам столбец. Таким образом изменить функцию заголовка строку:

void myfunc(int* arr, int n) { 

... (с одной «*»), и передать адрес первого элемента, а не голое имя массива, например:

myfunc(&seqs[0][0], 8); 

..or первая строка:

myfunc(seqs[0], 8); 
0

При определении 2-мерный массив как

int seqs[8][8]; 

тип seqs - это массив из 8 elements, где каждый element имеет тип array of 8 integers. Когда вы передаете seqs в myfunc, seqs неявно преобразуется в указатель на свой первый элемент (что и происходит, когда вы передаете массив функции), то есть набираете * element. element имеет тип int [8]. Следовательно, seqs неявно преобразован в тип int *[8] - указатель на массив из 8 целых чисел.

В функции myfunc, тип параметра arr является int ** явно другой тип, чем то, что вы передаете его, когда вы вызываете его в main. Они разные типы и имеют различную арифметику указателя. В результате компилятор жалуется.

Вы должны изменить тип arr на int *[8].

void myfunc(int arr[][8], int n); 

arr не является массивом здесь, а указатель на массив из 8 целых чисел, и вы могли бы также объявить myfunc, как

void myfunc(int *arr[8], int n); 

Они точно так же.

Редактировать

Что касается Вашего комментария here, вы не можете сделать следующее и ожидать, что все правильно работает.

// in main 
myfunc((int **)seqs, 8); 

// the above call is essentially doing this 
int **arr = (int **)seqs; 

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

Посмотрим почему. (После вызова функции.)

seqs (type: array of 8 int[8] type; value: address of the location seqs[0][0]) 
*seqs (type: int[8] type, value: address of the location seqs[0][0]) 

arr (type: pointer to a pointer to an int; value: address of the location seqs[0][0]) 
*arr (type: pointer to an int; value: seqs[0][0]) 
**arr (type: int; value: value at the address seqs[0][0]) 

Приведенные выше утверждения могут быть проверены с assert макро. Таким образом, мы видим, что когда мы делаем **arr, то, что мы на самом деле делаем, обрабатывает значение seqs[0][0] как адрес памяти и пытается получить доступ к значению в этом месте. Это, очевидно, неправильно! Это может привести к неопределенному поведению или, скорее всего, к потере сегментации.

Невозможно сделать значение seqs (значение, которое оно оценивает при инициализации arr), действует как int ** даже при типизации. Это также показывает, что мы должны быть осторожны с значениями приведения типов и не должны этого делать, если мы не знаем и не уверены в том, что делаем.

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

+0

Что касается редактирования: отбрасывание на 'int **' и разыменование дважды с помощью '* (* (arr + i * n) + j)' не будет работать, так как адрес в 'arr' по-прежнему будет началом данные массива - не адрес указателя на него. – Dmitri

+0

... и этот адрес не содержит указателей на строки, а сами строки. – Dmitri

+0

@ Dmitri Право. Я удалил редактирование. – ajay

5

В C99 или C11, вы могли бы сделать это следующим образом:

void myfunc(int n, int arr[n][n]) 
{ 
    for (int i = 0; i < n; ++i) 
    { 
     for (int j = 0; j < n; ++j) 
      printf("%d,", arr[i][j]); 
     printf("\n"); 
    } 
} 

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

int main(void) 
{ 
    int seqs[8][8] = 
    { 
     { 0, 32, 36, 52, 48, 16, 20, 4 }, 
     { 0, 16, 20, 52, 48, 32, 36, 4 }, 
     { 0, 32, 36, 44, 40, 8, 12, 4 }, 
     { 0, 8, 12, 44, 40, 32, 36, 4 }, 
     { 0, 32, 36, 38, 34, 2, 6, 4 }, 
     { 0, 2, 6, 38, 34, 32, 36, 4 }, 
     { 0, 32, 36, 37, 33, 1, 5, 4 }, 
     { 0, 1, 5, 37, 33, 32, 36, 4 }, 
    }; 
    myfunc(8, seqs); 

    int matrix3x3[3][3] = { { 1, 2, 3 }, { 2, 4, 6 }, { 3, 6, 9 } }; 
    myfunc(3, matrix3x3); 
} 

меня спросили:

Your example does look much better indeed, but is it well-defined? Is n really guaranteed to be evaluated before int arr[n][n] ? Wouldn't the order of evaluation of function parameters be unspecified behavior?

старый стандарт (ISO/IEC 9899: 1999) говорит, что в §6.7.5.2 * Массив declarators *:

¶5 If the size is an expression that is not an integer constant expression: if it occurs in a declaration at function prototype scope, it is treated as if it were replaced by * ; otherwise, each time it is evaluated it shall have a value greater than zero. The size of each instance of a variable length array type does not change during its lifetime. Where a size expression is part of the operand of a sizeof operator and changing the value of the size expression would not affect the result of the operator, it is unspecified whether or not the size expression is evaluated.

И это дает пример (это ненормативный текст, потому что это пример, но сильный что указывает на ожидаемые результаты):

EXAMPLE 4 All declarations of variably modified (VM) types have to be at either block scope or function prototype scope. Array objects declared with the static or extern storage-class specifier cannot have a variable length array (VLA) type. However, an object declared with the static storage class specifier can have a VM type (that is, a pointer to a VLA type). Finally, all identifiers declared with a VM type have to be ordinary identifiers and cannot, therefore, be members of structures or unions.

extern int n; 
int A[n];      // invalid: file scope VLA 
extern int (*p2)[n];   // invalid: file scope VM 
int B[100];      // valid: file scope but not VM 
void fvla(int m, int C[m][m]); // valid: VLA with prototype scope 
void fvla(int m, int C[m][m]) // valid: adjusted to auto pointer to VLA 
{ 
    typedef int VLA[m][m];  // valid: block scope typedef VLA 
    struct tag { 
     int (*y)[n];   // invalid: y not ordinary identifier 
     int z[n];    // invalid: z not ordinary identifier 
    }; 
    int D[m];     // valid: auto VLA 
    static int E[m];   // invalid: static block scope VLA 
    extern int F[m];   // invalid: F has linkage and is VLA 
    int (*s)[m];    // valid: auto pointer to VLA 
    extern int (*r)[m];   // invalid: r has linkage and points to VLA 
    static int (*q)[m] = &B; // valid: q is a static block pointer to VLA 
} 

Есть и другие примеры, показывающие измененные параметры функции.

Кроме того, в §6.9.10 определений функций, он говорит:

¶10 On entry to the function, the size expressions of each variably modified parameter are evaluated and the value of each argument expression is converted to the type of the corresponding parameter as if by assignment. (Array expressions and function designators as arguments were converted to pointers before the call.)

+2

Умм, это был ваш ответ № 2^13, довольно поэтичный :) – fedorqui

+0

Последняя запятая в инициализации 'seq' не вызывает никаких предупреждений. Я не знаю, нужно ли это, но это напоминает мне список Python, кортежи и типы dict, где конечная запятая является приемлемой. – ajay

+1

@ajay: C разрешил трейлинг-запятую в инициализаторах, начиная с первого выпуска K & R (1978 или около того). C89 не разрешал конечную запятую при определении перечисления, но C99 добавил правило, чтобы явно разрешить это. Это значительно упрощает создание инициализаторов C, и добавление завершающего инициализатора означает, что изменения с меньшей вероятностью не сработают (потому что вы не добавили дополнительную запятую, когда добавили дополнительную строку данных). –