Passando por referência em C


204

Se C não suporta a passagem de uma variável por referência, por que isso funciona?

#include <stdio.h>

void f(int *j) {
  (*j)++;
}

int main() {
  int i = 20;
  int *p = &i;
  f(p);
  printf("i = %d\n", i);

  return 0;
}

Resultado:

$ gcc -std=c99 test.c
$ a.exe
i = 21 

22
Onde neste código você está passando referência ?
Atul 27/02

15
Deve-se notar que C não tem passagem por referência, só pode ser emulado usando ponteiros.
Algum programador,

6
A declaração correta é "C não suporta a passagem implícita de uma variável por referência" - você precisa criar explicitamente uma referência (com &) antes de chamar a função e desreferê-la explicitamente (com *) na função.
Chris Dodd

2
Sua saída de código é exatamente igual a quando chamada, f(&i);isto é uma implementação de passagem por referência, que não existe puramente em C. passagem por referência
EsmaeelE

@Someprogrammerdude Passar um ponteiro é passar por referência. Esse parece ser um daqueles fatos em que os programadores C "experientes" se orgulham. Como se tivessem um pontapé com isso. "Ah, você pode pensar que C tem passagem por referência, mas não, na verdade é apenas o valor de um endereço de memória que está sendo passado para harharhar". Passar por referência literalmente significa apenas passar o endereço de memória de onde uma variável está armazenada e não o valor da variável em si. Isso é o que C permite, e é passado por referência toda vez que você passa um ponteiro, porque um ponteiro é uma referência a um local de memória de variáveis.
YungGun

Respostas:


315

Como você está passando o valor do ponteiro para o método e desreferenciando-o para obter o número inteiro apontado.


4
f (p); -> isso significa passar por valor? desreferenciando-o para obter o número inteiro apontado. -> você poderia dar mais explicações?
bapi

4
@ bapi, desreferenciar um ponteiro significa "obter o valor ao qual esse ponteiro está se referindo".
Rauni Lillemets 11/11

O que chamamos de método de chamar a função que recebe o endereço da variável em vez de passar o ponteiro. Exemplo: func1 (int & a). Não é uma chamada de referência? Nesse caso, a referência é realmente usada e, no caso de ponteiro, ainda estamos passando por valor, porque estamos passando apenas por ponteiro.
quer

1
Ao usar ponteiros, o fato principal é que a cópia do ponteiro é passada para a função. A função então usa esse ponteiro, não o original. Isso ainda passa por valor, mas funciona.
Danijel

1
@ Danijel É possível passar um ponteiro que não é uma cópia de nada para uma chamada de função. Por exemplo, chamando a função func: func(&A);Isso passaria um ponteiro para A para a função sem copiar nada. É passagem por valor, mas esse valor é uma referência; portanto, você está 'passando por referência' a variável A. Nenhuma cópia é necessária. É válido dizer que é passagem por referência.
YungGun 18/09/19

124

Isso não é passar por referência, é passar por valor, como outros declararam.

A linguagem C é transmitida por valor, sem exceção. Passar um ponteiro como parâmetro não significa passagem por referência.

A regra é a seguinte:

Uma função não pode alterar o valor real dos parâmetros.


Vamos tentar ver as diferenças entre os parâmetros escalar e ponteiro de uma função.

Variáveis ​​escalares

Este pequeno programa mostra a passagem por valor usando uma variável escalar. paramé chamado de parâmetro formal e, variablena chamada de função, é chamado de parâmetro real. A nota incrementada paramna função não muda variable.

#include <stdio.h>

void function(int param) {
    printf("I've received value %d\n", param);
    param++;
}

int main(void) {
    int variable = 111;

    function(variable);
    printf("variable %d\m", variable);
    return 0;
}

O resultado é

I've received value 111
variable=111

Ilusão de passagem por referência

Mudamos um pouco o código. paramé um ponteiro agora.

#include <stdio.h>

void function2(int *param) {
    printf("I've received value %d\n", *param);
    (*param)++;
}

int main(void) {
    int variable = 111;

    function2(&variable);
    printf("variable %d\n", variable);
    return 0;
}

O resultado é

I've received value 111
variable=112

Isso faz você acreditar que o parâmetro foi passado por referência. Não era. Foi passado por valor, o valor do parâmetro sendo um endereço. O valor do tipo int foi incrementado, e esse é o efeito colateral que nos faz pensar que era uma chamada de função de passagem por referência.

Ponteiros - passados ​​por valor

Como podemos mostrar / provar esse fato? Bem, talvez possamos tentar o primeiro exemplo de variáveis ​​escalares, mas, em vez de escalares, usamos endereços (ponteiros). Vamos ver se isso pode ajudar.

#include <stdio.h>

void function2(int *param) {
    printf("param's address %d\n", param);
    param = NULL;
}

int main(void) {
    int variable = 111;
    int *ptr = &variable;

    function2(ptr);
    printf("ptr's address %d\n", ptr);
    return 0;
}

O resultado será que os dois endereços são iguais (não se preocupe com o valor exato).

Resultado de exemplo:

param's address -1846583468
ptr's address -1846583468

Na minha opinião, isso prova claramente que os ponteiros são passados ​​por valor. Caso contrário, ptrseria NULLapós a chamada da função.


69

Em C, passagem por referência é simulada passando o endereço de uma variável (um ponteiro) e desreferenciando esse endereço na função para ler ou gravar a variável real. Isso será chamado de "passagem C por referência ao estilo C."

Fonte: www-cs-students.stanford.edu


50

Porque não há passagem por referência no código acima. O uso de ponteiros (como void func(int* p)) é passado por endereço. Isso é passado por referência em C ++ (não funcionará em C):

void func(int& ref) {ref = 4;}

...
int a;
func(a);
// a is 4 now

1
Eu gosto da resposta de passagem por endereço . Faz mais sentido.
Afzaal Ahmad Zeeshan

Endereço e referência são sinônimos neste contexto. Mas você pode usar esses termos para diferenciar os dois, simplesmente não é honesto com o significado original.
YungGun 11/09/19

27

Seu exemplo funciona porque você está passando o endereço da sua variável para uma função que manipula seu valor com o operador de desreferência .

Embora C não suporte tipos de dados de referência , você ainda pode simular a passagem por referência passando explicitamente os valores do ponteiro, como no seu exemplo.

O tipo de dados de referência C ++ é menos poderoso, mas considerado mais seguro que o tipo de ponteiro herdado de C. Este seria o seu exemplo, adaptado para usar referências C ++ :

void f(int &j) {
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

3
Esse artigo da Wikipedia é sobre C ++, não C. As referências existiam antes do C ++ e não dependem da sintaxe especial do C ++.

1
@ Roger: Bom ponto ... Eu removi referência explícita ao C ++ da minha resposta.
Daniel Vassallo

1
E esse novo artigo diz que "uma referência costuma ser chamada de ponteiro", o que não é exatamente o que sua resposta diz.

12

Você está passando um ponteiro (local do endereço) por valor .

É como dizer "aqui é o lugar com os dados que eu quero que você atualize".


6

p é uma variável de ponteiro. Seu valor é o endereço de i. Quando você chama f, passa o valor de p, que é o endereço de i.



5

Em C, tudo passa por valor. O uso de ponteiros nos dá a ilusão de que estamos passando por referência porque o valor da variável muda. No entanto, se você imprimir o endereço da variável ponteiro, verá que ela não é afetada. Uma cópia do valor do endereço é passada para a função. Abaixo está um trecho que ilustra isso.

void add_number(int *a) {
    *a = *a + 2;
}

int main(int argc, char *argv[]) {
   int a = 2;

   printf("before pass by reference, a == %i\n", a);
   add_number(&a);
   printf("after  pass by reference, a == %i\n", a);

   printf("before pass by reference, a == %p\n", &a);
   add_number(&a);
   printf("after  pass by reference, a == %p\n", &a);

}

before pass by reference, a == 2
after  pass by reference, a == 4
before pass by reference, a == 0x7fff5cf417ec
after  pass by reference, a == 0x7fff5cf417ec

4

Porque você está passando um ponteiro (endereço de memória) para a variável p na função f. Em outras palavras, você está passando um ponteiro, não uma referência.


4

Resposta curta: Sim, C implementa passagem de parâmetro por referência usando ponteiros.

Ao implementar a passagem de parâmetros, os designers de linguagens de programação usam três estratégias diferentes (ou modelos semânticos): transferir dados para o subprograma, receber dados do subprograma ou fazer as duas coisas. Esses modelos são comumente conhecidos como modo in, out e inout, respectivamente.

Vários designers foram criados por designers de linguagem para implementar essas três estratégias de passagem de parâmetros elementares:

Passagem por valor (semântica no modo) Passagem por resultado (semântica no modo de saída) Passagem por valor (semântica no modo de entrada) Passagem por referência (semântica no modo de entrada) Passagem por nome (modo de entrada semântica)

Passagem por referência é a segunda técnica para a passagem de parâmetros no modo de entrada. Em vez de copiar dados entre a rotina principal e o subprograma, o sistema de tempo de execução envia um caminho de acesso direto aos dados para o subprograma. Nesta estratégia, o subprograma tem acesso direto aos dados, compartilhando efetivamente os dados com a rotina principal. A principal vantagem dessa técnica é que ela é absolutamente eficiente no tempo e no espaço, porque não há necessidade de duplicar o espaço e não há operações de cópia de dados.

A implementação de passagem de parâmetro em C: C implementa a semântica de passagem por valor e também de passagem por referência (modo inout) usando ponteiros como parâmetros. O ponteiro é enviado para o subprograma e nenhum dado real é copiado. No entanto, como um ponteiro é um caminho de acesso aos dados da rotina principal, o subprograma pode alterar os dados na rotina principal. C adotou esse método da ALGOL68.

Implementação de passagem de parâmetro no C ++: O C ++ também implementa a semântica de passagem por referência (modo inout) usando ponteiros e também usando um tipo especial de ponteiro, chamado tipo de referência. Os ponteiros do tipo de referência são desreferenciados implicitamente dentro do subprograma, mas sua semântica também é passada por referência.

Portanto, o principal conceito aqui é que a passagem por referência implementa um caminho de acesso aos dados em vez de copiá-los no subprograma. Os caminhos de acesso a dados podem ser ponteiros desreferenciados explicitamente ou ponteiros desreferenciados automaticamente (tipo de referência).

Para mais informações, consulte o livro Concepts of Programming Languages, de Robert Sebesta, 10a Ed., Capítulo 9.


3

Você não está passando um int por referência, está passando um ponteiro para um int por valor. Sintaxe diferente, mesmo significado.


1
+1 "Sintaxe diferente, mesmo significado." .. Então, a mesma coisa, pois o significado importa mais que a sintaxe.

Não é verdade. Chamando void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }com int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, imprime 111 em vez de 500. Se você está passando por referência, deve imprimir 500. C não suporta passagem de parâmetros por referência.
Konfle Dolex

@ Konfle, se você estiver passando por referência de forma sintática, ptr = &newvaluenão será permitido. Independentemente da diferença, acho que você está apontando que "o mesmo significado" não é exatamente verdadeiro porque você também possui funcionalidade extra em C (a capacidade de reatribuir a "referência" em si).
xan

Nós nunca escrevemos algo como ptr=&newvaluese fosse passado por referência. Em vez disso, escrevemos ptr=newvalueAqui está um exemplo em C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }O valor do parâmetro passado para func () se tornará 500.
Konfle Dolex

No caso do meu comentário acima, não faz sentido passar param por referência. No entanto, se o parâmetro for um objeto em vez de POD, isso fará uma diferença significativa, pois qualquer alteração após param = new Class()dentro de uma função não terá efeito para o chamador se for passada por valor (ponteiro). Se paramfor passado por referência, as alterações serão visíveis para o chamador.
Konfle Dolex

3

Em C, para passar por referência, você usa o endereço do operador &que deve ser usado em uma variável, mas no seu caso, desde que você usou a variável ponteiro p, não é necessário prefixá-la com o operador do endereço do endereço. Teria sido verdade se você utilizado &icomo parâmetro: f(&i).

Você também pode adicionar isso à desreferencia pe ver como esse valor corresponde i:

printf("p=%d \n",*p);

Por que você sentiu a necessidade de repetir todo o código (incluindo esse bloco de comentários ...) para dizer a ele que ele deveria adicionar um printf?

@ Neil: Esse erro foi introduzido pela edição de @ William, vou reverter agora. E agora é aparente que o tommieb tem quase toda a razão: você pode aplicar & a qualquer objeto, não apenas às variáveis.

2

ponteiros e referências são dois thigngs diferentes.

Algumas coisas que eu não vi mencionadas.

Um ponteiro é o endereço de alguma coisa. Um ponteiro pode ser armazenado e copiado como qualquer outra variável. Assim, tem um tamanho.

Uma referência deve ser vista como um ALIAS de alguma coisa. Ele não tem tamanho e não pode ser armazenado. DEVE referenciar algo, ie. não pode ser nulo ou alterado. Bem, às vezes o compilador precisa armazenar a referência como um ponteiro, mas isso é um detalhe de implementação.

Com referências, você não tem problemas com ponteiros, como manipulação de propriedade, verificação nula, cancelamento de referência no uso.


1

'Passar por referência' (usando ponteiros) está em C desde o início. Por que você acha que não é?


7
Porque tecnicamente não está passando por referência.
Mehrdad Afshari

7
Passar um valor de ponteiro não é o mesmo que passar por referência. A atualização do valor de j( not *j ) in f()não tem efeito em iin main().
John Bode

6
É semanticamente a mesma coisa que passar por referência, e isso é bom o suficiente para dizer que é passar por referência. É verdade que o padrão C não usa o termo "referência", mas isso não me surpreende nem é um problema. Também não estamos falando padrão no SO, mesmo que possamos nos referir ao padrão; caso contrário, não veríamos ninguém falando sobre rvalores (o padrão C não usa o termo).

4
@ Jim: Obrigado por nos dizer que foi você que votou no comentário de John.

1

Eu acho que C de fato suporta passagem por referência.

A maioria das línguas exige que o açúcar sintático passe por referência em vez de valor. (C ++, por exemplo, requer & na declaração de parâmetro).

C também requer açúcar sintático para isso. É * na declaração do tipo de parâmetro e & no argumento. Então * e & é a sintaxe C para passar por referência.

Agora, pode-se argumentar que a passagem real por referência deve exigir apenas sintaxe na declaração do parâmetro, não no lado do argumento.

Mas agora vem C #, que faz apoio pela passagem de referência e requer açúcar sintático em ambos os parâmetros e argumentos lados.

O argumento de que C não tem passagem por referência faz com que os elementos sintáticos para expressá-lo exibam a implementação técnica subjacente não é um argumento, pois isso se aplica mais ou menos a todas as implementações.

O único argumento restante é que passar por ref em C não é um recurso monolítico, mas combina dois recursos existentes. (Pegue ref do argumento por &, espere ref digitar por *.) C #, por exemplo, requer dois elementos sintáticos, mas eles não podem ser usados ​​um sem o outro.

Este é obviamente um argumento perigoso, porque muitos outros recursos em idiomas são compostos por outros recursos. (como suporte a cadeias de caracteres em C ++)


1

O que você está fazendo é passar por valor, não por referência. Como você está enviando o valor de uma variável 'p' para a função 'f' (em principal como f (p);)

O mesmo programa em C com passagem por referência será semelhante a (!!! este programa apresenta 2 erros, pois a passagem por referência não é suportada em C)

#include <stdio.h>

void f(int &j) {    //j is reference variable to i same as int &j = i
  j++;
}

int main() {
  int i = 20;
  f(i);
  printf("i = %d\n", i);

  return 0;
}

Resultado:-

3:12: erro: esperado ';', ',' ou ')' antes do token '&'
             n vazio f (int & j);
                        ^
9: 3: aviso: declaração implícita da função 'f'
               f (a);
               ^
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.