Cでの参照渡し


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
このコードのどこに参照を渡しますか?
Atul、2015

15
Cは参照渡しではなく、ポインタを使用してのみエミュレートできることに注意してください。
一部のプログラマは、

6
正しいステートメントは「Cは参照による変数の暗黙的な受け渡しをサポートしていません」です。&関数を呼び出す前に明示的に参照を(で)作成し、関数内で明示的に逆参照(で*)する必要があります。
Chris Dodd

2
コード出力f(&i);は、これが参照渡しの実装であり、純粋にCには存在しない呼び出しとまったく同じです
。C

@Someprogrammerdudeポインタを渡すと、参照渡しになります。これは、「精通した」Cプログラマが誇りに思っている事実の1つであるようです。彼らはそれからキックを得るように。「ああ、Cには参照渡しがありますが、実際にはharharharに渡されるメモリアドレスの値だけではありません。」参照渡しは、文字通り、変数の値自体ではなく、変数が格納されているメモリアドレスを渡すことを意味します。これがCで許可されていることであり、ポインタは変数のメモリ位置への参照であるため、ポインタを渡すたびに参照渡しになります。
YungGun

回答:


315

ポインタの値をメソッドに渡しそれを逆参照して、ポイントされている整数を取得するためです。


4
f(p); ->これは値渡しを意味しますか?次に、それを逆参照して、ポイントされている整数を取得します。→詳しく説明してもらえますか?
bapi 14年

4
@bapi、ポインターの逆参照は、「このポインターが参照している値を取得する」ことを意味します。
Rauni Lillemets 2014

ポインターを渡す代わりに、変数のアドレスを取得する関数を呼び出す方法に必要なもの。例:func1(int&a)。これは参照による呼び出しではありませんか?この場合、実際に参照が行われ、ポインターの場合は、ポインターを値でのみ渡すため、値で渡します。
Jon Wheelock、2015年

1
ポインターを使用する場合の重要な事実は、ポインターのコピーが関数に渡されることです。関数は、元のポインターではなく、そのポインターを使用します。これはまだ値渡しですが、機能します。
Danijel 2016年

1
@Danijel何もコピーされていないポインタを関数呼び出しに渡すことができます。たとえば、関数の呼び出しfuncfunc(&A);これは、Aへのポインタを関数に渡し、何もコピーしません。これは値渡しですが、その値は参照であるため、変数Aを「参照渡し」しています。コピーは必要ありません。参照渡しであると言うのは有効です。
YungGun

124

それは参照渡しではなく、他の人が述べたように値渡しです。

C言語は例外なく値渡しです。ポインターをパラメーターとして渡すことは、参照渡しを意味しません。

ルールは次のとおりです。

関数は、実際のパラメーター値を変更できません。


関数のスカラーパラメータとポインタパラメータの違いを見てみましょう。

スカラー変数

この短いプログラムは、スカラー変数を使用した値渡しを示しています。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

これにより、パラメーターが参照によって渡されたと確信できます。そうではありませんでした。これは値で渡され、param値はアドレスです。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;
}

その結果、2つのアドレスは等しくなります(正確な値を気にする必要はありません)。

結果の例:

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

私の意見では、これはポインターが値渡しされていることを証明しています。それ以外の場合ptrNULL、関数呼び出しの後になります。


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
住所渡しの回答が好きです。もっと理にかなっています。
Afzaal Ahmad Zeeshan 2017年

この文脈では、アドレスと参照は同義です。しかし、これらの用語を使用して2つを区別することができます。これは、元の意味に正直ではありません。
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 ++への明示的な参照を削除しました。
Daniel Vassallo、

1
そして、その新しい記事は「参照はしばしばポインターと呼ばれる」と言っていますが、それはあなたの答えが言うこととは全く異なります。

12

ポインタ(アドレスの場所)を値で渡している。

「更新したいデータがある場所はこちら」と言っているようなものです。


6

pはポインター変数です。その値はiのアドレスです。fを呼び出すときは、iのアドレスであるp の値渡します。



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はポインタを使用した参照渡しのパラメータを実装しています。

パラメータの受け渡しを実装する際、プログラミング言語の設計者は3つの異なる戦略(またはセマンティックモデル)を使用します。データをサブプログラムに転送するか、サブプログラムからデータを受信するか、またはその両方を行います。これらのモデルは一般に、それぞれinモード、outモード、およびinoutモードと呼ばれます。

言語デザイナーは、これらの3つの基本的なパラメーター受け渡し戦略を実装するために、いくつかのモデルを考案しました。

値渡し(モードセマンティクス内)結果渡し(出力モードセマンティクス)値渡し結果(入力モードセマンティクス)参照渡し(入力モードセマンティクス)名前渡し(入力モード)セマンティクス)

参照渡しは、入出力モードのパラメーターを渡すための2番目の手法です。ランタイムシステムは、メインルーチンとサブプログラムの間でデータをコピーする代わりに、サブプログラムのデータへの直接アクセスパスを送信します。この戦略では、サブプログラムはデータに直接アクセスして、メインルーチンとデータを効果的に共有します。この手法の主な利点は、スペースを複製する必要がなく、データのコピー操作がないため、時間とスペースの面で非常に効率的です。

Cでのパラメーター受け渡しの実装:Cは、パラメーターとしてポインターを使用して、値渡しと参照渡し(inoutモード)のセマンティクスを実装します。ポインタはサブプログラムに送信され、実際のデータはまったくコピーされません。ただし、ポインタはメインルーチンのデータへのアクセスパスであるため、サブプログラムはメインルーチンのデータを変更することがあります。CはALGOL68からこの方法を採用しました。

C ++でのパラメーター受け渡しの実装:C ++は、ポインターを使用して、また参照型と呼ばれる特殊な種類のポインターを使用して、参照渡し(inoutモード)セマンティクスも実装します。参照型ポインタは、サブプログラム内で暗黙的に逆参照されますが、それらのセマンティクスも参照渡しです。

したがって、ここでの重要な概念は、データをサブプログラムにコピーする代わりに、参照渡しでデータへのアクセスパスを実装することです。データアクセスパスは、明示的に逆参照されたポインターまたは自動逆参照されたポインター(参照型)にすることができます。

詳細については、Robert Sebesta著のプログラミング言語の概念、第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; }、500ではなく111が出力されます。参照渡しの場合、500が出力されます。Cは、参照渡しのパラメーターをサポートしていません。
Konfle Dolex 2013

@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 2013

上記の私のコメントの場合、参照によってparamを渡すことは無意味です。ただし、paramがPODではなくオブジェクトである場合、param = new Class()関数内で変更を行っても、値(ポインタ)で渡された場合、呼び出し元には影響がないため、大きな違いが生じます。param参照によって渡される場合、変更は呼び出し元に表示されます。
Konfle Dolex 2013

3

Cでは、参照渡し&を行うには、変数に対して使用する必要があるアドレス演算子を使用しますが、この場合、ポインター変数を使用しているためp、アドレス演算子をプレフィックスとして付ける必要はありません。&iパラメータとして使用した場合は、trueになりますf(&i)

これを追加して、逆参照しp、その値がどのように一致するかを確認することもできますi

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

printfを追加するように指示するために、すべてのコード(コメントブロックを含む)を繰り返す必要性を感じたのはなぜですか。

@ニール:そのエラーは@ウィリアムの編集によって導入されました、私は今それを元に戻します。そして、今やtommiebはほとんど正しくないことが明らかです。変数だけでなく、任意のオブジェクトに&を適用できます。

2

ポインタと参照は2つの異なる考えです。

私が見たことのないいくつかのことについて言及しました。

ポインタは何かのアドレスです。ポインタは、他の変数と同様に格納およびコピーできます。したがって、サイズがあります。

参照は何かのエイリアスと見なされます。サイズがなく、収納できません。つまり、何かを参照する必要があります。nullまたは変更することはできません。まあ、コンパイラは参照をポインタとして格納する必要がある場合がありますが、これは実装の詳細です。

参照を使用すると、所有権の処理、nullチェック、使用時の逆参照など、ポインターに関する問題は発生しません。


1

(参照を使用して渡す)(ポインターを使用する)は、最初からCで行われています。なぜそうではないと思いますか?


7
技術的には参照渡しではないためです。
Mehrdad Afshari、2010

7
ポインタ値を渡すことは、参照渡しと同じではありません。値の更新jない *jで)をf()には影響ありませんiでしmain()
ジョンボーデ

6
これは、意味的参照渡しと同じであり、参照渡しと言っても十分です。確かに、C標準では「参照」という用語を使用していませんが、それは私を驚かせたり、問題にしたりするものではありません。また、標準について言及している場合でも、SOで標準語を話しているわけではありません。

4
@ジム:ジョンのコメントに賛成したのはあなただと言ってくれてありがとう。

1

Cは実際には参照渡しをサポートしていると思います。

ほとんどの言語では、値ではなく参照で渡す構文糖が必要です。(たとえば、C ++ではパラメーター宣言に&が必要です)。

Cもこれには構文糖を必要とします。パラメータの型宣言と引数の&にあります。したがって、*と&参照渡しのC構文です。

参照による実際の受け渡しは、引数側ではなく、パラメータ宣言の構文のみを必要とするべきであると主張することができます。

しかし、参照渡しによるサポートを提供しパラメーター側と引数側の両方に構文糖必要するC#が登場しました

Cが参照渡しを持たないという議論は、それがすべての実装に多かれ少なかれ当てはまるので、構文要素がそれを表現する構文要素が根本的な技術的実装を示すことはまったく議論ではありません。

残っている唯一の議論は、Cでrefを渡すことはモノリシックな機能ではなく、2つの既存の機能を組み合わせるというものです。(&による引数の参照を受け取り、*による参照が期待されます。)たとえば、C#には2つの構文要素が必要ですが、お互いがなければ使用できません。

これは明らかに危険な議論です。言語の他の機能の多くは他の機能で構成されているためです。(C ++の文字列サポートのように)


1

あなたがしていることは、参照渡しではなく値渡しです。変数 'p'の値を関数 'f'に送信しているため(主にf(p);として)

参照渡しのCの同じプログラムは次のようになります(!!!このプログラムは、参照渡しがCではサポートされていないため、2つのエラーが発生します)。

#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'の暗黙の宣言
               f(a);
               ^
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.