Pasando por referencia en C

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

#include  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 

Porque está pasando el valor del puntero al método y luego desreferenciando para obtener el entero al que apunta.

Eso no es pasado por referencia, es pasar por valor como otros dijeron.

El lenguaje C es pass-by-value 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 progtwig muestra el paso por valor usando una variable escalar. param se llama el parámetro formal y la variable en la invocación de la función se llama parámetro real. Nota: el incremento de param en la función no cambia la variable .

 #include  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 pass-by-reference

Cambiamos el fragmento de código ligeramente. param es un puntero ahora.

 #include  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. Fue aprobado por valor, el valor param es una dirección. El valor del tipo int se incrementó, y ese es el efecto secundario que nos hace pensar que se trató de una llamada de función de paso por referencia.

Punteros – pasado 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  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).

Ejemplo de resultado:

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

En mi opinión, esto demuestra claramente que los indicadores se pasan por valor. De ptr contrario, ptr sería NULL después de la invocación de la función.

En C, se simula pasar por referencia al pasar la dirección de una variable (un puntero) y desreferenciar esa dirección dentro de la función para leer o escribir la variable real. Esto se denominará “estilo de C paso por referencia”.

Fuente: www-cs-students.stanford.edu

Porque no hay una referencia de paso por paso en el código anterior. El uso de punteros (como void func(int* p) ) es pass-by-address. Esto es pass-by-reference en C ++ (no funcionará en C):

 void func(int& ref) {ref = 4;} ... int a; func(a); // a is 4 now 

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 , puede simular pasar por referencia al pasar explícitamente valores de puntero, 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; } 

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

Es como decir “aquí está el lugar con los datos que quiero que actualice”.

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.

Sin referencia en C, pero p “se refiere” a i, y se pasa p por valor.

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

Al implementar el paso de parámetros, los diseñadores de lenguajes de progtwigción utilizan tres estrategias diferentes (o modelos semánticos): transferir datos al subprogtwig, recibir datos del subprogtwig o hacer ambos. Estos modelos se conocen comúnmente como modo, modo de salida y modo de entrada, respectivamente.

Varios modelos han sido ideados por los diseñadores de lenguaje para implementar estas tres estrategias elementales de paso de parámetros:

Pass-by-Value (en semántica de modo) Pass-by-Result (semántica de modo de salida) Pass-by-Value-Result (en la semántica de modo) Pass-by-Reference (semántica de modo inout) Pass-by-Name (modo de entrada) semántica)

Pass-by-reference es la segunda técnica para el paso de parámetros inout-mode. En lugar de copiar datos entre la rutina principal y el subprogtwig, el sistema de tiempo de ejecución envía una ruta de acceso directo a los datos para el subprogtwig. En esta estrategia, el subprogtwig tiene acceso directo a los datos que comparten 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.

La implementación de paso de parámetros en C: C implementa semánticas de paso por valor y también de paso por referencia (en modo de entrada) usando punteros como parámetros. El puntero se envía al subprogtwig y no se copian datos reales. Sin embargo, como un puntero es una ruta de acceso a los datos de la rutina principal, el subprogtwig puede cambiar los datos en la rutina principal. C adoptó este método de ALGOL68.

Parámetro que pasa la implementación en C ++: C ++ también implementa la semántica paso a paso (inout mode) usando punteros y también usa un tipo especial de puntero, llamado tipo de referencia. Los punteros del tipo de referencia se desreferencian implícitamente dentro del subprogtwig, 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 subprogtwig. Las rutas de acceso a los datos pueden ser punteros explícitamente desreferenciados o punteros autoreferenciados (tipo de referencia).

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

En C todo es pasaje 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 transfiere una copia del valor de la dirección a la función. Debajo hay un fragmento que ilustra eso.

 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 

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.

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

En C, para pasar por referencia utiliza el operador de dirección & que debe usarse contra una variable, pero en su caso, ya que ha utilizado la variable de puntero p , no es necesario que la prefija con el operador de dirección . Hubiera sido cierto si usaras &i como el parámetro: f(&i) .

También puede agregar esto para desreferenciar p y ver cómo ese valor coincide con i :

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

‘Pase por referencia’ (usando punteros) ha estado en C desde el principio. ¿Por qué crees que no es así?

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 statement de parámetro).

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

Ahora se podría argumentar que el paso real por referencia solo debería requerir syntax en la statement del parámetro, no en el lado del argumento.

Pero ahora viene C #, que admite por referencia y requiere azúcar sintáctico en ambos lados, parámetro y argumento.

El argumento de que C no tiene una deriva refiriendo causa que los elementos sintácticos que lo expresan muestren que 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. (Toma ref de argumento por &, espera ref para escribir por *.) C # por ejemplo requiere dos elementos sintácticos, pero no se pueden usar el uno sin el otro.

Este es obviamente un argumento peligroso, porque muchas otras características en los idiomas se componen de otras características. (como el soporte de cadena en C ++)

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

El mismo progtwig en C con pass by reference se verá así, (!!! este progtwig da 2 errores ya que el pass by reference no se admite en C)

 #include  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 'y' token
              void f (int & j);
                         ^
 9: 3: advertencia: statement implícita de la función 'f'
                fa);
                ^

punteros y referencias son dos diferentes thigngs.

Un par de cosas que no he visto mencionaron.

Un puntero es la dirección de algo. Un puntero puede ser almacenado y copiado 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 comstackdor necesita almacenar la referencia como un puntero, pero ese es un detalle de implementación.

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