sexta-feira, 15 de fevereiro de 2013

Métodos de passagem de parâmetros


Os métodos de passagem de parâmetros pode ser entendido como a maneira pelas quais transmitimos um o valor  ou um caminho de acesso de uma variável para um subprograma. Podemos destacar três métodos de passagem de parâmetros:
  • Passagem por valor
  • Passagem por referência
  • Passagem por nome
Passagem por valor

O principal método de passagem de parâmetro utilizado na  linguagem C é a passagem por valor. Neste método de passagem, o valor do parâmetro real é copiado para o parâmetro forma correspondente no subprograma. Este, age como uma variável local do subprograma.

A principal desvantagem deste modelo de passagem de parâmetro é  o custo de armazenamento e transferência das informações dos parâmetros reais para os parâmetros formais. 

Passagem por referência

Na passagem de parâmetro por referência é transmitido um caminho de acesso para o parâmetro real para o subprograma. Nenhuma custo adicional para cópia de valores é exigido. Por outro lado, o acesso dos parâmetros formais no subprograma será mais lento. O acesso dos parâmetros reais podem gerar efeitos colaterais indesejáveis.

Passagem por valor x Passagem por referência


A linguagem C consegue "emular" a passagem por referência utilizando  ponteiros. Podemos alterar os valores das variáveis não-locais  utilizando um ponteiro para essas variáveis e no subprograma utilizamos a operação de desreferenciamento do ponteiro.

void troca(int *a, int *b){
  int temp;
  temp = *a;
  *a = *b;
  *b = temp;
}

Na linguagem C, os vetores são passados implicitamente como ponteiros. Esta escolha pode ser justificada para garantir a eficiência de subprograma que utilizam vetores.

int fun(int a, int v[5]){
    a++;
    v[0]++;
}
int main(){
    int a;
    int v[5];
    a = 2;
    v[0] = 3;
   fun(a,v);
   printf("%d %d",a,v[0]);
}


Neste exemplo, o valor esperado para este programa se fosse utilizado a passagem de parâmetro por valor seria 2 e 3. Mas o resultado obtido é 2 e 4. 

A operação v[0] é substituída por *(v+0) que utiliza aritmética de ponteiros + a operação de desreferenciamento.

A linguagem C++ implementa o modelo de passagem por referência utilizando o tipo de referência.

void troca(int &a, int &b){
  int temp;
  temp = a;
  a = b;
  b = temp;
}


Passagem por referência x efeito colateral

Uma função ou expressão é dita ter um efeito colateral se além de produzir um valor, ela também modifica o estado do programa. Na presença de efeitos colaterais, o comportamento do programa depende da história, isto é, da ordem em que as expressões são avaliadas.
int f(int & a ){
  a = 2*a;
  return a;
}
a = 5;
b = f(a) + a;

Observe que o valor de b depende da ordem que a expressão é avaliada. Se a avaliação for avaliada da esquerda para a direita, o valor da expressão 20. Se a expressão for avaliada da direita para a esquerda, o valor da expressão será 15.

Passagem de parâmetro por nome


A passagem por nome é o nome dos parâmetros reais são substituídos textualmente pelos valores dos parâmetros formais.

Este método é bem mais lento que os métodos anteriores e pode confundir bastante o leitor do código.


A passagem de parâmetro por nome está bastante relacionada com substituição de macros. A substituição dos macros compartilha alguns dos problemas da passagem de parâmetro por nome.

Considere o seguinte exemplo:

#define mult(a,b) a*b

mult(3+4,6)

O valor dessa expressão é 27. Quando o esperado é 42. Neste caso, a substituição do macro troca a expressão mult(3+4,6) por 3+4*6 que dá 27.


#define mult(a,b) ((a)*(b))

mult(3+4,6)

Agora, a substituição da macro não vai provocar o efeito indesejável.

Considere o seguinte exemplo:
#define max(a,b) a>b?a:b

int main(){
  int a = 5;
  int b = 3;
  printf("%d a = %d b = %d\n", max(a++,b), a, b);
  
}
Saída
6 a = 7 b = 3

Note que o valor de a é incrementado duas vezes, a substituição do macro gera uma expressão com dois efeitos colaterais. O duplo efeito colateral pode ser evitado dessa maneira:

#define max(X, Y)                     \
     ({ typeof (X) __x = (X), __y = (Y);   \
        (__x < __y) ? __x : __y; })

int main(){
  int a = 5;
  int b = 3;
  printf("%d a = %d b = %d\n", max(a++,b), a, b);
}

Saída
3 a = 6 b = 3

Passagem por referência x Passagem por nome

Considere o seguinte exemplo:

Passagem por referência

void troca(int & a, int & b){
  int temp;
  temp = a;
  a = b;
  b = temp;
}

int main(){
  
  int valor = 3, lista[5] = {4,6,3,1,2},i;
  troca(valor, lista[0]);
  troca(lista[0], lista[1]);
  troca(valor, lista[valor]);

}

Saída
valor = 2 lista = { 6, 3, 3, 1, 4}

Passagem por nome
#define troca(X, Y)                     \
     ({ typeof (X) temp;                \
        temp = X; X = Y; Y = temp; }    )


int main(){
  int valor = 3, lista[5] = {4,6,3,1,2},i;
  troca(valor, lista[0]);
  troca(lista[0], lista[1]);
  troca(valor, lista[valor]);
}

Saída
valor = 2 lista = { 6, 3, 4, 1, 2}

Compilando o código acima com a flag gcc -E podemos interromper o processo de compilação após a expansão dos macros obtendo o seguinte código.
int main(){
  int valor = 3, lista[5] = {4,6,3,1,2},i;
  ({ typeof (valor) temp; temp = valor; valor = lista[0]; lista[0] = temp; } );
  ({ typeof (lista[0]) temp; temp = lista[0]; lista[0] = lista[1]; lista[1] = temp; } );
  ({ typeof (valor) temp; temp = valor; valor = lista[valor]; lista[valor] = temp; } );
}
























2 comentários:

jorge luiz disse...

Cara, não e mais simples você mostrar apenas como e feito a passagem por referencia em sequencia ?
no código você usou ponteiros e tipos de dados comuns, desse jeito prejudica né fera ;)

Wladimir Araújo Tavares disse...

Oi Jorge,

A sua sugestão é mostrar apenas a passagem por referência e mostrar os casos em que a passagem de referência é realizada implicitamente?