Проходя по ссылке в С


204

Если C не поддерживает передачу переменной по ссылке, почему это работает?

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

Вывод:

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

22
Где в этом коде вы передаете ссылку ?
Атул

15
Следует отметить, что C не имеет проходной ссылки, его можно эмулировать только с помощью указателей.
Какой-то программист чувак

6
Правильное утверждение: «C не поддерживает неявную передачу переменной по ссылке» - вам необходимо явно создать ссылку (с &) перед вызовом функции и явно разыменовать ее (с *) в функции.
Крис Додд,

2
Выходные данные вашего кода в точности равны, когда call f(&i);это реализация передачи по ссылке, которой нет просто в C. C передача по ссылке
EsmaeelE

@Someprogrammerdude Передача указателя является передачей по ссылке. Похоже, это один из тех фактов, которыми гордятся «опытные» программисты на Си. Как будто они получают удовольствие от этого. «О, вы, возможно, ДУМАЕТЕ, что C имеет обходную ссылку, но на самом деле это просто значение адреса памяти, передаваемого хархархаром». Передача по ссылке буквально означает передачу адреса памяти, где хранится переменная, а не ее значения. Это то, что позволяет C, и это передача по ссылке каждый раз, когда вы передаете указатель, потому что указатель является ссылкой на ячейку памяти переменных.
ЮнгГун

Ответы:


316

Потому что вы передаете значение указателя методу, а затем разыменовываете его, чтобы получить целое число, на которое указывает указатель.


4
е (р); -> это означает передачу по значению? затем разыменовываем его, чтобы получить целое число, на которое указывает. -> Не могли бы вы дать больше объяснений.
Bapi

4
@bapi, разыменование указателя означает «получить значение, на которое ссылается этот указатель».
Рауни Лиллемец

То, что мы вызываем для метода вызова функции, которая получает адрес переменной вместо передачи указателя. Пример: func1 (int & a). Разве это не звонок по ссылке? В этом случае ссылка действительно берется, а в случае указателя мы все еще передаем по значению, потому что мы передаем указатель только по значению.
Джон Уилок

1
При использовании указателей ключевым фактом является то, что копия указателя передается в функцию. Затем функция использует этот указатель, а не исходный. Это все еще передача по значению, но это работает.
Даниэль

1
@Danijel Можно передать указатель, который не является копией чего-либо, для вызова функции. Например, вызов функции func: func(&A);это передало бы указатель на A функции, не копируя что-либо вообще. Это передача по значению, но это значение является ссылкой, поэтому вы 'передаете по ссылке' переменную A. Копирование не требуется. Это справедливо сказать, что это передача по ссылке.
YungGun

124

Это не передача по ссылке, это передача по значению, как заявили другие.

Язык Си передается по значению без исключения. Передача указателя в качестве параметра не означает передачу по ссылке.

Правило следующее:

Функция не может изменить фактическое значение параметров.


Давайте попробуем увидеть разницу между скалярными и указательными параметрами функции.

Скалярные переменные

Эта короткая программа показывает передачу по значению с использованием скалярной переменной. paramназывается формальным параметром и variableпри вызове функции называется фактическим параметром. Приращение примечания paramв функции не изменяется 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;
}

Результат

I've received value 111
variable=111

Иллюзия передачи по ссылке

Мы немного изменили кусок кода. paramуказатель сейчас

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

Результат

I've received value 111
variable=112

Это заставляет вас поверить, что параметр был передан по ссылке. Не было. Он был передан по значению, а значение параметра является адресом. Значение типа int было увеличено, и это побочный эффект, который заставляет нас думать, что это был вызов функции передачи по ссылке.

Указатели - передаваемые по значению

Как мы можем показать / доказать этот факт? Ну, может быть, мы можем попробовать первый пример скалярных переменных, но вместо скалярных мы используем адреса (указатели). Посмотрим, может ли это помочь.

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

Результатом будет то, что два адреса равны (не беспокойтесь о точном значении).

Пример результата:

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

На мой взгляд, это ясно показывает, что указатели передаются по значению. В противном случае ptrбудет NULLпосле вызова функции.


69

В C передача по ссылке моделируется путем передачи адреса переменной (указателя) и разыменования этого адреса в функции для чтения или записи фактической переменной. Это будет упоминаться как «передача по ссылке в стиле C».

Источник: www-cs-students.stanford.edu


50

Потому что в приведенном выше коде нет передачи по ссылке. Использование указателей (таких как void func(int* p)) является передачей по адресу. Это передача по ссылке в C ++ (не будет работать в C):

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

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

1
Мне нравится ответ по адресу . Имеет больше смысла.
Афзаал Ахмад Зеешан

Адрес и ссылка являются синонимами в этом контексте. Но вы можете использовать эти термины, чтобы различать их, это просто не соответствует их первоначальному значению.
YungGun

27

Ваш пример работает, потому что вы передаете адрес вашей переменной в функцию, которая манипулирует ее значением с помощью оператора разыменования .

Пока C не поддерживает ссылочные типы данных , вы все равно можете имитировать передачу по ссылке, явно передавая значения указателя, как в вашем примере.

Ссылочный тип данных C ++ менее мощный, но считается более безопасным, чем тип указателя, унаследованный от C. Это будет ваш пример, адаптированный для использования ссылок C ++ :

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

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

  return 0;
}

3
Эта статья в Википедии посвящена C ++, а не C. Ссылки существовали до C ++ и не зависят от наличия специального синтаксиса C ++.

1
@ Роджер: Хороший вопрос ... Я удалил явную ссылку на C ++ из своего ответа.
Даниэль Вассалло

1
И в этой новой статье говорится, что «ссылку часто называют указателем», что не совсем то, что говорит ваш ответ.

12

Вы передаете указатель (адрес) по значению .

Это все равно что сказать «вот место с данными, которые я хочу, чтобы вы обновили».


6

р является переменной указателя. Его значением является адрес i. Когда вы вызываете f, вы передаете значение p, которое является адресом i.



5

В C все передается по значению. Использование указателей дает нам иллюзию, что мы передаем по ссылке, потому что значение переменной изменяется. Однако, если вы распечатаете адрес переменной-указателя, вы увидите, что это не влияет. Копия от значения адреса передается в функцию. Ниже приведен фрагмент, иллюстрирующий это.

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

Потому что вы передаете указатель (адрес памяти) на переменную p в функцию f. Другими словами, вы передаете указатель, а не ссылку.


4

Краткий ответ: Да, C осуществляет передачу параметров по ссылке с использованием указателей.

При реализации передачи параметров разработчики языков программирования используют три разные стратегии (или семантические модели): передавать данные в подпрограмму, получать данные из подпрограммы или выполнять обе эти функции. Эти модели обычно известны как в режиме, режиме выхода и режиме выхода, соответственно.

Разработчики языка разработали несколько моделей для реализации этих трех элементарных стратегий передачи параметров:

Pass-by-Value (в семантике режима) Pass-by-Result (семантика режима out) Pass-by-Value-Result (семантика режима inout) Pass-by-Reference (семантика режима inout) Pass-by-Name (входной режим семантика)

Передача по ссылке является вторым методом передачи параметров в режиме ожидания. Вместо копирования данных между основной подпрограммой и подпрограммой назад и вперед система времени выполнения отправляет путь прямого доступа к данным подпрограммы. В этой стратегии подпрограмма имеет прямой доступ к данным, эффективно разделяя данные с основной программой. Основным преимуществом этого метода является то, что он абсолютно эффективен во времени и пространстве, потому что нет необходимости дублировать пространство и нет операций копирования данных.

Реализация передачи параметров в C: C реализует семантику передачи по значению, а также семантику передачи по ссылке (в режиме inout), используя указатели в качестве параметров. Указатель отправляется в подпрограмму, и никакие фактические данные не копируются вообще. Однако, поскольку указатель является путем доступа к данным основной подпрограммы, подпрограмма может изменить данные в основной подпрограмме. C принял этот метод от ALGOL68.

Реализация передачи параметров в C ++: C ++ также реализует семантику передачи по ссылке (inout mode) с использованием указателей, а также с использованием специального вида указателя, называемого ссылочным типом. Указатели на ссылочные типы неявно разыменовываются внутри подпрограммы, но их семантика также передается по ссылке.

Таким образом, ключевая концепция здесь заключается в том, что передача по ссылке реализует путь доступа к данным вместо копирования данных в подпрограмму. Пути доступа к данным могут быть явно разыменованными указателями или автоматически разыменованными указателями (ссылочный тип).

Для получения дополнительной информации, пожалуйста, обратитесь к книге «Концепции языков программирования» Роберта Себесты, 10-е издание, глава 9.


3

Вы не передаете int по ссылке, вы передаете указатель на an-int по значению. Другой синтаксис, одно и то же значение.


1
+1 «Другой синтаксис, одно и то же значение.» .. То же самое, поскольку значение имеет большее значение, чем синтаксис.

Не правда. Вызывая void func(int* ptr){ *ptr=111; int newValue=500; ptr = &newvalue }с int main(){ int value=0; func(&value); printf("%i\n",value); return 0; }, он печатает 111 вместо 500. Если вы передаете по ссылке, он должен вывести 500. C не поддерживает передачу параметра по ссылке.
Konfle Dolex

@Konfle, если вы синтаксически передаете по ссылке, ptr = &newvalueбудет запрещено. Независимо от разницы, я думаю, вы указываете, что «одно и то же значение» не совсем верно, потому что у вас также есть дополнительная функциональность в C (возможность переназначения самой «ссылки»).
xan

Мы никогда не пишем что-то вроде, ptr=&newvalueесли это передается по ссылке. Вместо этого мы пишем ptr=newvalueВот пример на C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }значение параметра, переданного в func (), станет 500.
Konfle Dolex

В случае моего комментария выше, бессмысленно передавать параметр по ссылке. Однако, если param является объектом вместо POD, это будет иметь существенное значение, потому что любое изменение после param = new Class()внутри функции не будет иметь никакого эффекта для вызывающей стороны, если оно передается по значению (указателю). Если paramпередать по ссылке, изменения будут видны для вызывающей стороны.
Konfle Dolex

3

В C для передачи по ссылке вы используете оператор address-of, &который следует использовать для переменной, но в вашем случае, поскольку вы использовали переменную-указатель p, вам не нужно ставить перед ней префикс address-of. Было бы справедливо , если вы использовали в &iкачестве параметра: f(&i).

Вы также можете добавить это к разыменованию pи посмотреть, как это значение соответствует i:

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

Почему вы почувствовали необходимость повторить весь код (включая этот блок комментариев ...), чтобы сказать ему, что он должен добавить printf?

@Neil: Эта ошибка была введена правкой @ William, я сейчас исправлю ее. И теперь очевидно, что tommieb в основном прав: вы можете применять & к любому объекту, а не только к переменным.

2

указатели и ссылки - это два разных принципа.

Пара вещей, которые я не видел, упоминается.

Указатель - это адрес чего-либо. Указатель может быть сохранен и скопирован как любая другая переменная. Таким образом, он имеет размер.

Ссылка должна рассматриваться как алиас чего-то. Он не имеет размера и не может быть сохранен. Это ДОЛЖНО ссылаться на что-то, т.е. он не может быть нулевым или измененным. Ну, иногда компилятору нужно хранить ссылку как указатель, но это деталь реализации.

Со ссылками у вас нет проблем с указателями, такими как обработка владения, проверка нуля, снятие ссылок на использование.


1

«Передача по ссылке» (с помощью указателей) была в C с самого начала. Почему вы думаете, что нет?


7
Потому что технически он не передается по ссылке.
Мехрдад Афшари

7
Передача значения указателя не совпадает с передачей по ссылке. Обновление значения j( не *j ) в f()не влияет на iв main().
Джон Боде

6
Это семантический то же самое , как передача по ссылке, и это достаточно хорошо , чтобы сказать , что проходит по ссылке. Правда, в стандарте C термин «ссылка» не используется, но это не удивляет меня и не является проблемой. Мы также не говорим на стандартном SO, хотя мы можем ссылаться на стандарт, иначе мы не увидим, чтобы кто-то говорил о значениях (в стандарте C этот термин не используется).

4
@Jim: Спасибо, что сказали нам, что вы проголосовали за комментарий Джона.

1

Я думаю, что C на самом деле поддерживает передачу по ссылке.

В большинстве языков синтаксический сахар требуется передавать по ссылке, а не по значению. (Например, C ++ требует & в объявлении параметра).

С также требует синтаксического сахара для этого. Это * в объявлении типа параметра и & в аргументе. Так * и & - это синтаксис языка Си для передачи по ссылке.

Теперь можно утверждать, что для реального прохождения по ссылке требуется только синтаксис в объявлении параметра, а не на стороне аргумента.

Но теперь приходит C # , которая делает поддержку ссылочной мимоходом и требует синтаксический сахар на обоих параметров и аргументов сторон.

Аргумент, что C не имеет передачи by-ref, заставляет синтаксические элементы выражать его, демонстрируя основную техническую реализацию, вообще не является аргументом, поскольку это более или менее применимо ко всем реализациям.

Единственным оставшимся аргументом является то, что передача по ссылке в C не является монолитной функцией, а объединяет две существующие функции. (Возьмите ref аргумента от &, ожидайте, что ref напечатайте *.) Например, C # требует двух синтаксических элементов, но их нельзя использовать друг без друга.

Это, очевидно, опасный аргумент, потому что многие другие функции в языках состоят из других функций. (например, поддержка строк в C ++)


1

То, что вы делаете, это передача по значению, а не передача по ссылке. Потому что вы отправляете значение переменной 'p' в функцию 'f' (в основном как f (p);)

Та же самая программа на C с передачей по ссылке будет выглядеть так: (!!! эта программа выдает 2 ошибки, так как передача по ссылке не поддерживается в 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;
}

Вывод:-

3:12: ошибка: ожидается ';', ',' или ')' перед '&' токеном
             void f (int & j);
                        ^
9: 3: предупреждение: неявное объявление функции 'f'
               е (а);
               ^
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.