Diferença entre passar array e ponteiro de array em função em C

110

Qual é a diferença entre as duas funções em C?

void f1(double a[]) {
   //...
}

void f2(double *a) {
   //...
}

Se eu chamasse as funções em uma matriz substancialmente longa, essas duas funções se comportariam de maneira diferente, ocupariam mais espaço na pilha?

Kaushik Shankar
fonte

Respostas:

114

Primeiro, alguns padrões :

6.7.5.3 Declaradores de função (incluindo protótipos)
...
7 Uma declaração de um parâmetro como '' array do tipo '' deve ser ajustada para '' ponteiro qualificado para tipo '', onde os qualificadores de tipo (se houver) são aqueles especificados dentro de [e ]da derivação de tipo de matriz. Se a palavra-chave statictambém aparecer dentro de [e ]da derivação do tipo de matriz, então para cada chamada para a função, o valor do argumento real correspondente deve fornecer acesso ao primeiro elemento de uma matriz com pelo menos tantos elementos quanto especificado pelo tamanho expressão.

Portanto, em resumo, qualquer parâmetro de função declarado como T a[]ou T a[N]é tratado como se tivesse sido declarado T *a.

Então, por que os parâmetros de array são tratados como se fossem declarados como ponteiros? Aqui está o porquê:

6.3.2.1 Valores L, matrizes e designadores de função
...
3 Exceto quando é o operando do sizeofoperador ou o &operador unário , ou é um literal de string usado para inicializar uma matriz, uma expressão que tem o tipo '' matriz do tipo ' 'é convertido em uma expressão com tipo' 'ponteiro para tipo ' 'que aponta para o elemento inicial do objeto de matriz e não é um lvalue. Se o objeto de matriz tiver classe de armazenamento de registro, o comportamento é indefinido.

Dado o seguinte código:

int main(void)
{
  int arr[10];
  foo(arr);
  ...
}

Na chamada para foo, a expressão de array arrnão é um operando de sizeofou &, então seu tipo é convertido implicitamente de "array de 10 elementos de int" para "ponteiro para int" de acordo com 6.2.3.1/3. Portanto, fooreceberá um valor de ponteiro, em vez de um valor de array.

Por causa de 6.7.5.3/7, você pode escrever foocomo

void foo(int a[]) // or int a[10]
{
  ...
}

mas será interpretado como

void foo(int *a)
{
  ...
}

Assim, as duas formas são idênticas.

A última frase em 6.7.5.3/7 foi introduzida com C99 e basicamente significa que se você tiver uma declaração de parâmetro como

void foo(int a[static 10])
{
  ...
}

o parâmetro real correspondente a adeve ser uma matriz com pelo menos 10 elementos.

John Bode
fonte
1
Há uma diferença ao usar (pelo menos alguns mais antigos) compiladores MSVC C ++, devido ao compilador alterar incorretamente o nome da função de maneira diferente nos dois casos (embora reconheça que eles são iguais), resultando em problemas de link. Consulte o relatório de bug "Não corrige" aqui connect.microsoft.com/VisualStudio/feedback/details/326874/…
greggo
29

A diferença é puramente sintática. Em C, quando a notação de array é usada para um parâmetro de função, ela é automaticamente transformada em uma declaração de ponteiro.

Thomas Pornin
fonte
1
@Kaushik: Embora sejam iguais neste caso, lembre-se de que não são iguais no caso geral
BlueRaja - Danny Pflughoeft
@BlueRaja: sim, é uma das armadilhas de C. A declaração de parâmetros de função é muito semelhante à declaração de variáveis ​​locais, mas existem algumas diferenças sutis (como esta transformação automática array-to-pointer) que são propenso a morder o programador incauto.
Thomas Pornin,
0

Não, não há diferença entre eles. Para testar, escrevi este código C no compilador Dev C ++ (mingw):

#include <stdio.h>

void function(int* array) {
     int a =5;
}

void main() {  
     int array[]={2,4};
     function(array);
     getch();
}

Quando desmonto a função principal em .exe de ambas as versões de chamada do arquivo binário em IDA, obtenho exatamente o mesmo código de montagem como abaixo:

push    ebp
mov     ebp, esp
sub     esp, 18h
and     esp, 0FFFFFFF0h
mov     eax, 0
add     eax, 0Fh
add     eax, 0Fh
shr     eax, 4
shl     eax, 4
mov     [ebp+var_C], eax
mov     eax, [ebp+var_C]
call    sub_401730
call    sub_4013D0
mov     [ebp+var_8], 2
mov     [ebp+var_4], 4
lea     eax, [ebp+var_8]
mov     [esp+18h+var_18], eax
call    sub_401290
call    _getch
leave
retn

Portanto, não há diferença entre as duas versões desta chamada, pelo menos o compilador as ameaça igualmente.

Caltuntas
fonte
18
Desculpe, mas isso só prova que alguma versão do gcc gera o mesmo assembly em x86 para ambos. Resposta correta, explicação errada.
lambdapower de