Passando uma matriz como um argumento para uma função em C

86

Escrevi uma função contendo array como argumento e chamo-a passando o valor de array da seguinte maneira.

void arraytest(int a[])
{
    // changed the array a
    a[0]=a[0]+a[1];
    a[1]=a[0]-a[1];
    a[0]=a[0]-a[1];
}

void main()
{
    int arr[]={1,2};
    printf("%d \t %d",arr[0],arr[1]);
    arraytest(arr);
    printf("\n After calling fun arr contains: %d\t %d",arr[0],arr[1]);
}

O que descobri é que, embora esteja chamando a arraytest()função passando valores, a cópia original de int arr[]foi alterada.

Você pode explicar por quê?

Mohan Mahajan
fonte
1
Você está passando a matriz por referência, mas está modificando seu conteúdo - por isso está vendo uma mudança nos dados
Shaun Wilde
main()deve retornar int.
sublinhado_d

Respostas:

137

Ao passar um array como parâmetro, este

void arraytest(int a[])

significa exatamente o mesmo que

void arraytest(int *a)

então você está modificando os valores em principal.

Por razões históricas, os arrays não são cidadãos de primeira classe e não podem ser passados ​​por valor.

Bo Persson
fonte
3
Qual notação é melhor em quais circunstâncias?
Ramon Martinez
20
@Ramon - Eu usaria a segunda opção, pois parece menos confusa e indica melhor que você não obteve uma cópia do array.
Bo Persson
2
Você pode explicar as "razões históricas"? Suponho que a passagem de valores precisaria de uma cópia e, portanto, um desperdício de memória .. obrigado
Jacquelyn.Marquardt
5
@lucapozzobon - Originalmente, C não tinha nenhuma passagem por valor, exceto para valores únicos. Não foi até que structfoi adicionado ao idioma que isso foi alterado. E então foi considerado tarde demais para alterar as regras para matrizes. Já havia 10 usuários. :-)
Bo Persson
1
... significa exatamente o mesmo que void arraytest(int a[1000])etc etc. Resposta expandida aqui: stackoverflow.com/a/51527502/4561887 .
Gabriel Staples
9

Se você quiser passar uma matriz de dimensão única como um argumento em uma função , você terá que declarar um parâmetro formal em uma das três maneiras a seguir e todos os três métodos de declaração produzem resultados semelhantes porque cada um diz ao compilador que um ponteiro de inteiro está indo a ser recebido .

int func(int arr[], ...){
    .
    .
    .
}

int func(int arr[SIZE], ...){
    .
    .
    .
}

int func(int* arr, ...){
    .
    .
    .
}

Então, você está modificando os valores originais.

Obrigado !!!

Monirul Islam Milon
fonte
Eu estava procurando seu segundo exemplo, você pode explicar quais são as vantagens de cada método?
Puck
8

Você não está passando a matriz como cópia. É apenas um ponteiro apontando para o endereço onde o primeiro elemento da matriz está na memória.

fyr
fonte
7

Você está passando o endereço do primeiro elemento da matriz

ob_dev
fonte
7

Arrays em C são convertidos, na maioria dos casos, em um ponteiro para o primeiro elemento do próprio array. E, mais detalhadamente, os arrays passados ​​em funções são sempre convertidos em ponteiros.

Aqui está uma citação de K & R2nd :

Quando um nome de array é passado para uma função, o que é passado é a localização do elemento inicial. Na função chamada, este argumento é uma variável local e, portanto, um parâmetro de nome de matriz é um ponteiro, ou seja, uma variável que contém um endereço.

Escrita:

void arraytest(int a[])

tem o mesmo significado que escrever:

void arraytest(int *a)

Portanto, apesar de você não estar escrevendo explicitamente, é como você está passando um ponteiro e, portanto, está modificando os valores no principal.

Para mais, eu realmente sugiro a leitura disso .

Além disso, você pode encontrar outras respostas sobre SO aqui

Granmirupa
fonte
6

Você está passando o valor da localização da memória do primeiro membro da matriz.

Portanto, quando você começa a modificar o array dentro da função, está modificando o array original.

Lembre-se que a[1]é *(a+1).

alex
fonte
1
Suponho que faltam () para * a + 1 deve ser * (a + 1)
ShinTakezou
@Shin Obrigado, faz um tempo que não toco com C.
alex
6

Em C, exceto em alguns casos especiais, uma referência de array sempre "decai" para um ponteiro para o primeiro elemento do array. Portanto, não é possível passar um array "por valor". Uma matriz em uma chamada de função será passada para a função como um ponteiro, o que é análogo a passar a matriz por referência.

EDITAR: Existem três casos especiais em que uma matriz não decai para um ponteiro para seu primeiro elemento:

  1. sizeof anão é o mesmo que sizeof (&a[0]).
  2. &anão é o mesmo que &(&a[0])(e não exatamente o mesmo que &a[0]).
  3. char b[] = "foo"não é o mesmo que char b[] = &("foo").
Thom Smith
fonte
Se eu passar um array para uma função. Digamos, por exemplo, que eu fiz um array int a[10]e atribua um valor aleatório a cada elemento. Agora, se eu passar esse array para uma função usando int y[]ou int y[10]ou int *y. E nessa função que eu uso a sizeof(y)Resposta, o ponteiro de bytes foi alocado. Portanto, neste caso, ele se tornará um indicador. Seria útil se você incluir isso também. Veja este postimg.org/image/prhleuezd
Suraj Jain
Se eu usar sizeofoperar na função no array que definimos originalmente, ele decairá como um array, mas se eu passar em outra função, então usar o sizeofoperador decairá como um ponteiro.
Suraj Jain
Eu sei que isso é antigo. Duas perguntas se alguém vir isso :) 1. @ThomSmith escreveu que &anão é exatamente o mesmo que &a[0]quando aé um array. Como assim? Em meu programa de teste, ambos mostram ser os mesmos, tanto na função em que a matriz é declarada quanto quando passados ​​para uma função diferente. 2. O escritor escreve que " char b[] = "foo"não é o mesmo que char b[] = &("foo")". Para mim, este último nem compila. Sou apenas eu?
Aviv Cohn
6

Passando um array multidimensional como argumento para uma função. Passar um array de um dim como argumento é mais ou menos trivial. Vamos dar uma olhada em um caso mais interessante de passar um array 2 dim. Em C você não pode usar um ponteiro para ponteiro construct ( int **) em vez de 2 dim array. Vamos dar um exemplo:

void assignZeros(int(*arr)[5], const int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 5; j++) {
            *(*(arr + i) + j) = 0;
            // or equivalent assignment
            arr[i][j] = 0;
        }
    }

Aqui, especifiquei uma função que leva como primeiro argumento um ponteiro para uma matriz de 5 inteiros. Posso passar como argumento qualquer matriz de 2 dim que tenha 5 colunas:

int arr1[1][5]
int arr1[2][5]
...
int arr1[20][5]
...

Você pode ter a ideia de definir uma função mais geral que pode aceitar qualquer array 2 dim e alterar a assinatura da função da seguinte forma:

void assignZeros(int ** arr, const int rows, const int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            *(*(arr + i) + j) = 0;
        }
    }
}

Este código seria compilado, mas você obterá um erro de tempo de execução ao tentar atribuir os valores da mesma maneira que na primeira função. Portanto, em C, arrays multidimensionais não são o mesmo que ponteiros para ponteiros ... para ponteiros. Um int(*arr)[5]é um ponteiro para um array de 5 elementos, um int(*arr)[6] é um ponteiro para um array de 6 elementos e eles são um ponteiro para diferentes tipos!

Bem, como definir argumentos de funções para dimensões superiores? Simples, apenas seguimos o padrão! Aqui está a mesma função ajustada para obter uma matriz de 3 dimensões:

void assignZeros2(int(*arr)[4][5], const int dim1, const int dim2, const int dim3) {
    for (int i = 0; i < dim1; i++) {
        for (int j = 0; j < dim2; j++) {
            for (int k = 0; k < dim3; k++) {
                *(*(*(arr + i) + j) + k) = 0;
                // or equivalent assignment
                arr[i][j][k] = 0;
            }
        }
    }
}

Como você esperaria, ele pode tomar como argumento qualquer 3 dim arrays que tenham na segunda dimensão 4 elementos e na terceira dimensão 5 elementos. Qualquer coisa assim estaria OK:

arr[1][4][5]
arr[2][4][5]
...
arr[10][4][5]
...

Mas temos que especificar todos os tamanhos de dimensões até o primeiro.

Andrushenko Alexander
fonte
5

Uso de array padrão em C com decaimento de tipo natural de array para ptr

@Bo Persson afirma corretamente em sua ótima resposta aqui :

Ao passar um array como parâmetro, este

void arraytest(int a[])

significa exatamente o mesmo que

void arraytest(int *a)

No entanto, deixe-me acrescentar também que as duas formas acima também:

  1. significa exatamente o mesmo que

     void arraytest(int a[0])
    
  2. o que significa exatamente o mesmo que

     void arraytest(int a[1])
    
  3. o que significa exatamente o mesmo que

     void arraytest(int a[2])
    
  4. o que significa exatamente o mesmo que

     void arraytest(int a[1000])
    
  5. etc.

Em cada um dos exemplos de array acima, o tipo de parâmetro de entrada decai para umint * e pode ser chamado sem avisos e sem erros, mesmo com as opções de compilação -Wall -Wextra -Werrorativadas (veja meu repositório aqui para obter detalhes sobre essas 3 opções de compilação), como esta:

int array1[2];
int * array2 = array1;

// works fine because `array1` automatically decays from an array type
// to `int *`
arraytest(array1);
// works fine because `array2` is already an `int *` 
arraytest(array2);

Por uma questão de fato, o valor "tamanho" ( [0], [1], [2], [1000], etc.) dentro do parâmetro de matriz aqui é, aparentemente, apenas para fins de auto-documentação / estética, e pode ser qualquer inteiro positivo ( size_tdigite eu acho) que você quiser!

Na prática, entretanto, você deve usá-lo para especificar o tamanho mínimo do array que você espera que a função receba, de modo que, ao escrever o código, seja fácil rastrear e verificar. O padrão MISRA-C-2012 ( compre / baixe o PDF 236-pg versão 2012 do padrão por £ 15,00 aqui ) vai mais longe a ponto de declarar (ênfase adicionada):

Regra 17.5 O argumento da função correspondente a um parâmetro declarado como tendo um tipo de array deve ter um número apropriado de elementos.

...

Se um parâmetro for declarado como uma matriz com um tamanho especificado, o argumento correspondente em cada chamada de função deve apontar para um objeto que tenha pelo menos tantos elementos quanto a matriz.

...

O uso de um declarador de matriz para um parâmetro de função especifica a interface da função mais claramente do que usar um ponteiro. O número mínimo de elementos esperado pela função é declarado explicitamente, embora isso não seja possível com um ponteiro.

Em outras palavras, eles recomendam usar o formato de tamanho explícito, mesmo que o padrão C tecnicamente não o imponha - pelo menos ajuda a esclarecer para você como desenvolvedor e para outros que usam o código, qual array de tamanho a função espera você para passar.


Forçando a segurança de tipo em matrizes em C

Como @Winger Sendon aponta em um comentário abaixo da minha resposta, podemos forçar C a tratar um tipo de array como diferente com base no tamanho do array !

Primeiro, você deve reconhecer que, no meu exemplo acima, usando o int array1[2];seguinte: arraytest(array1);faz array1com que automaticamente decaia para um int *. NO ENTANTO, se você pegar o endereço de array1 e ligar arraytest(&array1), terá um comportamento completamente diferente! Agora, ele NÃO decai em um int *! Em vez disso, o tipo de &array1é int (*)[2], que significa "ponteiro para uma matriz de tamanho 2 de int" ou "ponteiro para uma matriz de tamanho 2 do tipo int" . Portanto, você pode FORÇAR C para verificar a segurança de tipo em uma matriz, assim:

void arraytest(int (*a)[2])
{
    // my function here
}

Essa sintaxe é difícil de ler, mas semelhante à de um ponteiro de função . A ferramenta online, cdecl , nos diz que isso int (*a)[2]significa: "declara um as ponteiro para array 2 de int" (ponteiro para array de 2 ints). NÃO confunda isso com a versão sem parênteses:, int * a[2]que significa: "declara a como array 2 do ponteiro para int" (array de 2 ponteiros para int).

Agora, esta função EXIGE que você a chame com o operador de endereço ( &) assim, usando como parâmetro de entrada um PONTEIRO PARA UMA MATRIZ DO TAMANHO CORRETO !:

int array1[2];

// ok, since the type of `array1` is `int (*)[2]` (ptr to array of 
// 2 ints)
arraytest(&array1); // you must use the & operator here to prevent
                    // `array1` from otherwise automatically decaying
                    // into `int *`, which is the WRONG input type here!

Isso, no entanto, produzirá um aviso:

int array1[2];

// WARNING! Wrong type since the type of `array1` decays to `int *`:
//      main.c:32:15: warning: passing argument 1 of ‘arraytest’ from 
//      incompatible pointer type [-Wincompatible-pointer-types]                                                            
//      main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
arraytest(array1); // (missing & operator)

Você pode testar este código aqui .

Para forçar o compilador C a transformar este aviso em um erro, de modo que você DEVE sempre chamar arraytest(&array1);usando apenas uma matriz de entrada do tamanho e tipo corretos ( int array1[2];neste caso), adicione -Werroràs suas opções de construção. Se estiver executando o código de teste acima em onlinegdb.com, faça isso clicando no ícone de engrenagem no canto superior direito e clique em "Extra Compiler Flags" para digitar esta opção. Agora, este aviso:

main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types]                                                            
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’    

vai se transformar neste erro de compilação:

main.c: In function ‘main’:
main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types]
     arraytest(array1); // warning!
               ^~~~~~
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
 void arraytest(int (*a)[2])
      ^~~~~~~~~
cc1: all warnings being treated as errors

Observe que você também pode criar ponteiros de "tipo seguro" para matrizes de um determinado tamanho, como este:

int array[2];
// "type safe" ptr to array of size 2 of int:
int (*array_p)[2] = &array;

... mas eu não recomendo necessariamente isso, pois me lembra muito das artimanhas C ++ usadas para forçar a segurança de tipos em todos os lugares, com o custo excepcionalmente alto da complexidade da sintaxe da linguagem, verbosidade e dificuldade de arquitetura de código, e das quais eu não gosto e já falei sobre isso muitas vezes antes (ex: veja "Meus pensamentos sobre C ++" aqui )


Para testes e experimentações adicionais, consulte também o link abaixo.

Referências

Veja os links acima. Além disso:

  1. Minha experimentação de código online: https://onlinegdb.com/B1RsrBDFD
Gabriel Staples
fonte
2
void arraytest(int (*a)[1000])é melhor porque o compilador irá errar se o tamanho estiver errado.
Winger Sendon
@WingerSendon, eu sabia que havia algumas sutilezas que precisava verificar aqui, e que a sintaxe é confusa (como a sintaxe de uma função ptr é confusa), então tomei meu tempo e finalmente atualizei minha resposta com uma grande seção nova intitulada Forcing type safety on arrays in C, cobrindo seu ponto.
Gabriel Staples
0

As matrizes são sempre passadas por referência se você usar a[]ou *a:

int* printSquares(int a[], int size, int e[]) {   
    for(int i = 0; i < size; i++) {
        e[i] = i * i;
    }
    return e;
}

int* printSquares(int *a, int size, int e[]) {
    for(int i = 0; i < size; i++) {
        e[i] = i * i;
    }
    return e;
}
anil karikatti
fonte
Estou aprovando isso. Não sei por que foi rejeitado.
Gabriel Staples