Pasando por referencia en C

204

Si C no admite pasar una variable por referencia, ¿por qué funciona esto?

#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;
}

Salida:

$ gcc -std=c99 test.c
$ a.exe
i = 21 
aks
fuente
22
¿Dónde en este código estás pasando referencia ?
Atul
15
Cabe señalar que C no tiene pase por referencia, solo se puede emular usando punteros.
Algún tipo programador el
66
La declaración correcta es "C no admite el paso implícito de una variable por referencia": debe crear explícitamente una referencia (con &) antes de llamar a la función y desreferenciarla explícitamente (con *) en la función.
Chris Dodd
2
La salida de su código es exactamente igual cuando se llama a f(&i);esto es una implementación de pase por referencia, que no existe únicamente en C. C pase por referencia
EsmaeelE
@Someprogrammerdude Pasar un puntero es pasar por referencia. Este parece ser uno de esos hechos de los que los programadores "inteligentes" de C se enorgullecen. Como si les gustara. "Oh, tal vez PIENSE que C tiene paso por referencia, pero no, en realidad es solo el valor de una dirección de memoria que se pasa harharhar". Pasar por referencia literalmente solo significa pasar la dirección de memoria de donde se almacena una variable en lugar del valor de la variable en sí. Eso es lo que C permite, y es pasar por referencia cada vez que pasa un puntero, porque un puntero es una referencia a una ubicación de memoria de variables.
YungGun

Respuestas:

315

Porque pasa el valor del puntero al método y luego lo desreferencia para obtener el entero al que apunta.

tvanfosson
fuente
44
f (p); -> ¿esto significa pasar por valor? luego desreferenciarlo para obtener el entero al que apunta -> ¿podría dar más explicaciones?
bapi
44
@bapi, desreferenciar un puntero significa "obtener el valor al que hace referencia este puntero".
Rauni Lillemets
Lo que llamamos por el método de llamar a la función que toma la dirección de la variable en lugar de pasar el puntero. Ejemplo: func1 (int & a). ¿No es esto una llamada por referencia? En este caso, la referencia se toma realmente y en el caso del puntero, todavía estamos pasando por valor porque solo estamos pasando el puntero por valor.
Jon Wheelock
1
Cuando se utilizan punteros, el hecho clave es que la copia del puntero se pasa a la función. La función usa ese puntero, no el original. Esto todavía es paso por valor, pero funciona.
Danijel
1
@Danijel Es posible pasar un puntero que no es una copia de nada a una llamada de función. Por ejemplo, llamar a la función func: func(&A);esto pasaría un puntero a A a la función sin copiar nada en absoluto. Es paso por valor, pero ese valor es una referencia, por lo que está 'pasando por referencia' la variable A. No se requiere copia. Es válido decir que es paso por referencia.
YungGun
124

Eso no es pasar por referencia, es pasar por valor como otros han dicho.

El lenguaje C es paso por valor sin excepción. Pasar un puntero como parámetro no significa pasar por referencia.

La regla es la siguiente:

Una función no puede cambiar el valor real de los parámetros.


Intentemos ver las diferencias entre los parámetros escalares y de puntero de una función.

Variables escalares

Este breve programa muestra el paso por valor utilizando una variable escalar. paramse llama parámetro formal y variableen la invocación de la función se llama parámetro real. El incremento de nota paramen la función no cambia 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;
}

El resultado es

I've received value 111
variable=111

Ilusión de paso por referencia

Cambiamos un poco el código. paramEs un puntero ahora.

#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;
}

El resultado es

I've received value 111
variable=112

Eso te hace creer que el parámetro se pasó por referencia. No era. Se pasó por valor, el valor de parámetro es una dirección. El valor de tipo int se incrementó, y ese es el efecto secundario que nos hace pensar que era una llamada de función de paso por referencia.

Punteros - pasados ​​por valor

¿Cómo podemos mostrar / probar ese hecho? Bueno, tal vez podamos probar el primer ejemplo de variables escalares, pero en lugar de escalar, usamos direcciones (punteros). Veamos si eso puede ayudar.

#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;
}

El resultado será que las dos direcciones son iguales (no se preocupe por el valor exacto).

Resultado de ejemplo:

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

En mi opinión, esto demuestra claramente que los punteros se pasan por valor. De ptrlo contrario sería NULLdespués de la invocación de la función.

Ely
fuente
69

En C, Pass-by-reference se simula pasando la dirección de una variable (un puntero) y desreferenciando esa dirección dentro de la función para leer o escribir la variable real. Esto se denominará "referencia de estilo C".

Fuente: www-cs-students.stanford.edu

Tamas Czinege
fuente
50

Porque no hay paso por referencia en el código anterior. Usar punteros (como void func(int* p)) es pasar por dirección. Esto es paso por referencia en C ++ (no funcionará en C):

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

...
int a;
func(a);
// a is 4 now
Alexander Gessler
fuente
1
Me gusta la respuesta de paso por dirección . Tiene más sentido.
Afzaal Ahmad Zeeshan
Dirección y referencia son sinónimos en este contexto. Pero puede usar esos términos para diferenciar los dos, simplemente no es honesto con su significado original.
YungGun
27

Su ejemplo funciona porque está pasando la dirección de su variable a una función que manipula su valor con el operador de desreferencia .

Si bien C no admite tipos de datos de referencia , aún puede simular el paso por referencia pasando valores de puntero explícitamente, como en su ejemplo.

El tipo de datos de referencia de C ++ es menos potente pero se considera más seguro que el tipo de puntero heredado de C. Este sería su ejemplo, adaptado para usar referencias de C ++ :

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

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

  return 0;
}
Daniel Vassallo
fuente
3
Ese artículo de Wikipedia trata sobre C ++, no C. Las referencias existían antes de C ++ y no dependen de la sintaxis especial de C ++.
1
@Roger: Buen punto ... Eliminé la referencia explícita a C ++ de mi respuesta.
Daniel Vassallo
1
Y ese nuevo artículo dice que "una referencia a menudo se llama puntero", que no es exactamente lo que dice su respuesta.
12

Estás pasando un puntero (ubicación de la dirección) por valor .

Es como decir "aquí está el lugar con los datos que quiero que actualices".

antony.trupe
fuente
6

p es una variable de puntero. Su valor es la dirección de i. Cuando llamas a f, pasas el valor de p, que es la dirección de i.

Anssi
fuente
6

No hay referencia de paso en C, pero p "se refiere" a i, y usted pasa p por valor.

Pavel Radzivilovsky
fuente
5

En C todo es paso por valor. El uso de punteros nos da la ilusión de que estamos pasando por referencia porque el valor de la variable cambia. Sin embargo, si imprimiera la dirección de la variable de puntero, verá que no se ve afectada. Se pasa una copia del valor de la dirección a la función. A continuación hay un fragmento que lo ilustra.

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
Arun Suresh
fuente
4

Porque está pasando un puntero (dirección de memoria) a la variable p en la función f. En otras palabras, está pasando un puntero, no una referencia.

Dave
fuente
4

Respuesta corta: Sí, C implementa el paso de parámetros por referencia utilizando punteros.

Al implementar el paso de parámetros, los diseñadores de lenguajes de programación utilizan tres estrategias diferentes (o modelos semánticos): transferir datos al subprograma, recibir datos del subprograma o hacer ambas cosas. Estos modelos se conocen comúnmente como en modo, modo fuera y modo fuera, correspondientemente.

Los diseñadores del lenguaje han diseñado varios modelos para implementar estas tres estrategias de paso de parámetros elementales:

Paso por valor (semántica en modo) Paso por resultado (semántica en modo fuera) Resultado por valor en paso (semántica en modo entrante) Paso por referencia (semántica en modo entrante) Paso por nombre (modo entrante) semántica)

Pass-by-reference es la segunda técnica para pasar parámetros en modo inout. En lugar de copiar datos entre la rutina principal y el subprograma, el sistema de tiempo de ejecución envía una ruta de acceso directo a los datos para el subprograma. En esta estrategia, el subprograma tiene acceso directo a los datos compartiendo efectivamente los datos con la rutina principal. La principal ventaja de esta técnica es que es absolutamente eficiente en tiempo y espacio porque no hay necesidad de duplicar espacio y no hay operaciones de copia de datos.

Implementación de paso de parámetros en C: C implementa la semántica de paso por valor y también de paso por referencia (en modo sin salida) utilizando punteros como parámetros. El puntero se envía al subprograma y no se copia ningún dato real. Sin embargo, debido a que un puntero es una ruta de acceso a los datos de la rutina principal, el subprograma puede cambiar los datos en la rutina principal. C adoptó este método de ALGOL68.

Implementación de paso de parámetros en C ++: C ++ también implementa la semántica de paso por referencia (en modo inout) usando punteros y también usando un tipo especial de puntero, llamado tipo de referencia. Los punteros de tipo de referencia se desreferencian implícitamente dentro del subprograma, pero su semántica también se pasa por referencia.

Entonces, el concepto clave aquí es que el paso por referencia implementa una ruta de acceso a los datos en lugar de copiar los datos en el subprograma. Las rutas de acceso a datos pueden ser punteros desreferenciados explícitamente o punteros desreferenciados automáticamente (tipo de referencia).

Para obtener más información, consulte el libro Conceptos de lenguajes de programación de Robert Sebesta, décima edición, capítulo 9.

Francisco Zapata
fuente
3

No estás pasando un int por referencia, estás pasando un puntero a un int por valor. Sintaxis diferente, mismo significado.

xan
fuente
1
+1 "Sintaxis diferente, mismo significado". .. Así que lo mismo, ya que el significado importa más que la sintaxis.
No es verdad. Al llamar void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }con int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, imprime 111 en lugar de 500. Si está pasando por referencia, debería imprimir 500. C no admite parámetros de paso por referencia.
Konfle Dolex
@Konfle, si pasa sintácticamente por referencia, ptr = &newvalueno se permitirá. Independientemente de la diferencia, creo que está señalando que el "mismo significado" no es exactamente cierto porque también tiene una funcionalidad adicional en C (la capacidad de reasignar la "referencia" en sí misma).
xan
Nunca escribimos algo como ptr=&newvaluesi se pasa por referencia. En su lugar, escribimos ptr=newvalueAquí hay un ejemplo en C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }el valor del parámetro pasado a func () se convertirá 500.
Konfle Dolex
En el caso de mi comentario anterior, no tiene sentido pasar el parámetro por referencia. Sin embargo, si el parámetro es un objeto en lugar de POD, esto hará una diferencia significativa porque cualquier cambio después de param = new Class()dentro de una función no tendría ningún efecto para la persona que llama si se pasa por valor (puntero). Si paramse pasa por referencia, los cambios serían visibles para la persona que llama.
Konfle Dolex
3

En C, para pasar por referencia, usa la dirección de operador &que debe usarse contra una variable, pero en su caso, dado que ha usado la variable de puntero p, no necesita prefijarla con la dirección de operador. Hubiera sido cierto si se ha utilizado &icomo parámetro: f(&i).

También puede agregar esto, para desreferenciar py ver cómo coincide ese valor i:

printf("p=%d \n",*p);
t0mm13b
fuente
¿Por qué sintió la necesidad de repetir todo el código (incluido el bloque de comentarios ...) para decirle que debería agregar un printf?
@Neil: Ese error fue introducido por la edición de @ William, lo revertiré ahora. Y ahora es evidente que tommieb solo tiene razón en su mayoría: puede aplicar y a cualquier objeto, no solo variables.
2

Los punteros y las referencias son dos thigngs diferentes.

Un par de cosas que no he visto mencionadas.

Un puntero es la dirección de algo. Un puntero se puede almacenar y copiar como cualquier otra variable. Por lo tanto, tiene un tamaño.

Una referencia debe verse como un ALIAS de algo. No tiene un tamaño y no se puede almacenar. DEBE hacer referencia a algo, es decir. no puede ser nulo o cambiado. Bueno, a veces el compilador necesita almacenar la referencia como un puntero, pero eso es un detalle de implementación.

Con las referencias, no tiene problemas con los punteros, como el manejo de la propiedad, la verificación nula, la desreferenciación en uso.

usuario2187033
fuente
1

'Pasar por referencia' (mediante el uso de punteros) ha estado en C desde el principio. ¿Por qué crees que no es así?

Maurits Rijk
fuente
77
Porque técnicamente no pasa por referencia.
Mehrdad Afshari
77
Pasar un valor de puntero no es lo mismo que pasar por referencia. Actualizar el valor de j( no *j ) en f()no tiene ningún efecto ien main().
John Bode
66
Es semánticamente lo mismo que pasar por referencia, y eso es lo suficientemente bueno como para decir que está pasando por referencia. Es cierto que el Estándar C no utiliza el término "referencia", pero eso no me sorprende ni es un problema. Tampoco estamos hablando de estándar en SO, aunque podamos referirnos al estándar, de lo contrario no veríamos a nadie hablando de valores (el Estándar C no usa el término).
44
@ Jim: Gracias por decirnos que fuiste tú quien votó por el comentario de John.
1

Creo que C, de hecho, admite pasar por referencia.

La mayoría de los idiomas requieren azúcar sintáctico para pasar por referencia en lugar de valor. (C ++, por ejemplo, requiere & en la declaración de parámetros).

C también requiere azúcar sintáctico para esto. Está * en la declaración de tipo de parámetro y & en el argumento. Entonces * y & es la sintaxis de C para pasar por referencia.

Ahora se podría argumentar que el pase real por referencia solo debería requerir sintaxis en la declaración del parámetro, no en el lado del argumento.

Pero ahora viene C #, que hace de soporte que pasa por referencia y requiere azúcar sintáctico en ambos lados de parámetros y argumentos.

El argumento de que C no tiene derivación de referencia hace que los elementos sintácticos que lo expresen exhiban la implementación técnica subyacente no es un argumento en absoluto, ya que esto se aplica más o menos a todas las implementaciones.

El único argumento restante es que pasar por ref en C no es una característica monolítica, sino que combina dos características existentes. (Tome la referencia del argumento por &, espere que la referencia escriba por *.) C #, por ejemplo, requiere dos elementos sintácticos, pero no se pueden usar el uno sin el otro.

Obviamente, este es un argumento peligroso, porque muchas otras características en los idiomas están compuestas de otras características. (como soporte de cadena en C ++)

Johannes
fuente
1

Lo que está haciendo es pasar por valor, no pasar por referencia. Debido a que está enviando el valor de una variable 'p' a la función 'f' (en main como f (p);)

Se verá el mismo programa en C con paso por referencia, (!!! este programa da 2 errores ya que el paso por referencia no es compatible con 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;
}

Salida:-

3:12: error: esperado ';', ',' o ')' antes de '&' token
             nulo f (int & j);
                        ^
9: 3: advertencia: declaración implícita de función 'f'
               fa);
               ^
Sunay
fuente