Проходження посиланням на С


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
Вихід вашого коду точно дорівнює, коли виклик f(&i);- це реалізація пропуску посилань, якого не існує суто в C. Перехід C за посиланням
EsmaeelE

@Someprogrammerdude Передача вказівника проходить шляхом посилання. Це, здається, є одним із тих фактів, якими "кмітливі" програмісти C пишаються. Наче вони отримують удар від цього. "О, ви можете думати, що C має пропускний посилання, але ні, це насправді лише значення адреси пам'яті, яка передається harharhar". Перехід за посиланням буквально означає лише передачу адреси пам'яті, де зберігається змінна, а не значення самої змінної. Саме це дозволяє C, і це проходження посилань кожного разу, коли ви передаєте вказівник, тому що вказівник є посиланням на місце пам'яті змінних.
YungGun

Відповіді:


315

Тому що ви передаєте значення вказівника методу, а потім відкидаєте його, щоб отримати ціле число, на яке вказано.


4
f (p); -> це означає проходження за значенням? потім дереференціюючи його, щоб отримати ціле число, на яке вказано. -> Чи можете ви, будь ласка, дати більше пояснень.
бапі

4
@bapi, перенаправлення покажчика означає "отримати значення, на яке посилається цей вказівник".
Рауні Ліллемець

Що ми називаємо методом виклику функції, яка приймає адресу змінної замість передачі покажчика. Приклад: func1 (int & a). Це не дзвінок за посиланням? У цьому випадку посилання дійсно береться, і у випадку вказівника ми все ще передаємо значення, оскільки ми передаємо лише вказівник за значенням.
Джон Уілок

1
При використанні покажчиків головним фактом є те, що копія вказівника передається у функцію. Потім функція використовує той вказівник, а не оригінальний. Це все ще є прохідною цінністю, але це працює.
Данієль

1
@Danijel На виклик функції можна передати покажчик, який не є копією нічого. Наприклад, виклик функції func: func(&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 було збільшено, і саме цей побічний ефект змушує нас думати, що це був виклик функції проходження посилання.

Покажчики - передані значення

Як ми можемо показати / довести цей факт? Ну, можливо, ми можемо спробувати перший приклад змінних Scalar, але замість скалярних ми використовуємо адреси (покажчики). Подивимось, чи може це допомогти.

#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

Ваш приклад працює тому, що ви передаєте адресу вашої змінної функції, яка маніпулює її значенням оператором dereference .

Хоча 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
@Roger: Добре ... я мою відповідь видалив посилання на C ++.
Даніель Вассалло

1
І ця нова стаття говорить, що "посилання часто називається вказівником", що не зовсім те, що говорить ваша відповідь.

12

Ви передаєте вказівник (адресу адреси) за значенням .

Це як би сказати: "Ось місце з даними, які я хочу, щоб ви оновили".


6

p - змінна вказівник. Його значення - адреса 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 реалізує параметри, що передають посилання за допомогою покажчиків.

Реалізуючи передачу параметрів, дизайнери мов програмування використовують три різні стратегії (або семантичні моделі): передавати дані в підпрограму, отримувати дані з підпрограми або робити обидві. Ці моделі зазвичай відомі як в режимі, так і в режимі виходу відповідно.

Мовні дизайнери розробили кілька моделей для реалізації цих трьох елементарних стратегій проходження параметрів:

Значення "По-значення" (в семантиці режиму) "По-результат-результат" (семантика в режимі ")" По-значення-результат-результат "(семантика в режимі входу)" Перехід за посиланням "(вхідний режим семантика)" Перехід за назвою " семантика)

Пропускна посилання - друга методика передачі параметрів в режимі вихідного режиму. Замість того, щоб копіювати дані між основною програмою та підпрограмою, система виконання відправляє прямий шлях доступу до даних для підпрограми. У цій стратегії підпрограма має прямий доступ до даних, що ефективно обмінюються даними з основною програмою. Основна перевага цієї методики полягає в тому, що вона абсолютно ефективна у часі та просторі, оскільки не потрібно дублювати простір і немає операцій з копіювання даних.

Реалізація параметрів передачі в C: C реалізує пропускну цінність, а також семантику проходження посилання (режим входу), використовуючи покажчики як параметри. Покажчик відправляється на підпрограму, і фактичні дані взагалі не копіюються. Однак, оскільки покажчик - це шлях доступу до даних основної програми, підпрограма може змінювати дані в основній процедурі. C прийняв цей метод від ALGOL68.

Реалізація параметрів передачі в C ++: C ++ також реалізує семантику проходження посилання (вхідний режим), використовуючи покажчики, а також використовуючи спеціальний тип покажчика, який називається еталонним типом. Покажчики опорного типу неявно відкидаються всередині підпрограми, але їх семантика також проходить шляхом посилання.

Отже, ключова концепція тут полягає в тому, що перехідна посилання реалізує шлях доступу до даних замість копіювання даних у підпрограму. Шляхи доступу до даних можуть бути явно відкинутими вказівниками або автоматично відміненими вказівниками (тип посилання).

Для отримання додаткової інформації зверніться до книги Концепції мов програмування Роберта Себести, 10-е видання, глава 9.


3

Ви не передаєте int за посиланням, ви передаєте вказівник на-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буде заборонено. Незалежно від різниці, я думаю, ви вказуєте, що "те саме значення" не зовсім відповідає дійсності, оскільки ви також маєте додаткову функціональність у С (можливість переназначення "посилання").
xan

Ми ніколи не пишемо щось подібне, ptr=&newvalueякщо це передається посиланням. Натомість ми пишемо ptr=newvalueОсь приклад в C ++: void func(int& ptr){ ptr=111; int newValue=500; ptr = newValue; }Значення параметра, переданого у func (), стане 500.
Konfle Dolex

Що стосується мого коментаря вище, безглуздо передавати парам посилання. Однак якщо парам є об'єктом замість POD, це призведе до суттєвої різниці, оскільки будь-яка зміна після param = new Class()функції всередині функції не матиме ефекту для абонента, якщо вона передається за значенням (покажчик). Якщо paramбуде передано посилання, зміни будуть видимі для абонента.
Konfle Dolex

3

У C, щоб перейти за посиланням, ви використовуєте адресу оператора, &який повинен бути використаний проти змінної, але у вашому випадку, оскільки ви використовували змінну вказівника p, вам не потрібно префіксувати її з адресою оператора. Було б справедливо , якщо ви використовували в &iякості параметра: f(&i).

Ви також можете додати це для відновлення pта побачити, як це значення відповідає i:

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

Чому ви відчули необхідність повторити весь код (включаючи цей блок коментарів ..), щоб сказати йому, що він повинен додати printf?

@Neil: Ця помилка була внесена редакцією @ Вільяма, я зараз її переверну. І тепер очевидно, що tommieb має рацію лише в основному: ви можете застосувати & до будь-якого об'єкта, а не лише до змінних.

2

покажчики та посилання - це два різних тигр.

Пару речей, про які я не бачив.

Вказівник - адреса чогось. Вказівник може зберігатися і копіюватися, як і будь-яка інша змінна. Таким чином, він має розмір.

Посилання має розглядатися як АЛІАС чогось. Він не має розміру і не може зберігатися. ПОВИНЕН ДОПОМОГИТИ щось, тобто. він не може бути нульовим або зміненим. Ну, іноді компілятору потрібно зберігати посилання як вказівник, але це деталь реалізації.

З посиланнями у вас немає проблем з покажчиками, як, наприклад, обробка власності, перевірка нуля, де-посилання на використання.


1

"Перейти за посиланням" (за допомогою покажчиків) був у С від початку. Чому ти вважаєш, що це не так?


7
Тому що це технічно не проходить посилання.
Мехрдад Афшарі

7
Передача значення вказівника не є такою ж, як передача посилання. Оновлення значення j( не *j ) в f()не впливає на iв main().
Джон Боде

6
Це семантично те саме, що проходження посилання, і це досить добре, щоб сказати, що це проходження посиланням. Правда, у Стандарті C не використовується термін "посилання", але це ні мене не дивує, ні проблеми. Ми також не говоримо про стандарти на SO, навіть якщо ми можемо посилатися на стандарт, інакше ми не бачимо, щоб ніхто не говорив про rvalues ​​(стандарт C не використовує цей термін).

4
@Jim: Дякуємо, що сказали нам, що ви підтримали коментар Джона.

1

Я думаю, що С насправді підтримує перехід за посиланням.

Більшість мов вимагають, щоб синтаксичний цукор проходив посилання замість значення. (C ++, наприклад, вимагає & в оголошенні параметра).

C також вимагає для цього синтаксичний цукор. Це * у оголошенні типу параметра та & в аргументі. Отже * і & - це синтаксис С для передачі посилання.

Тепер можна стверджувати, що реальний прохід посиланням повинен вимагати синтаксису лише в оголошенні параметра, а не на стороні аргументу.

Але тепер приходить C # , яка робить підтримку посилальної мимохідь і вимагає синтаксичний цукор на обох параметрів і аргументів сторін.

Аргумент про те, що C не має побічного переходу, викликає, що синтаксичні елементи для його вираження виявляють базову технічну реалізацію, зовсім не є аргументом, оскільки це стосується більшої чи меншої кількості для всіх реалізацій.

Єдиний аргумент, що залишився - це те, що проходження посилання на C не є монолітною ознакою, а поєднує дві існуючі особливості. (Візьміть посилання на аргумент на &, очікуйте, що тип 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: помилка: очікувана ';', ',' або ')' перед маркером '&'
             пустота f (int & j);
                        ^
9: 3: попередження: неявне оголошення функції 'f'
               f (a);
               ^
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.