質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.51%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

4回答

3259閲覧

C言語 関数での参照渡しでの値の変化について

bob_heuer0925

総合スコア30

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2016/04/01 09:11

編集2016/04/01 09:48

C言語での参照渡しについて、理解できない部分があるので
質問させてください。

以下のソースは質問する為に作成したのですが
自作関数hogehogehogegegeも構造体のポインタアドレスを
引数として受け取る参照渡しをおこなっており、関数内の処理内容は
どちらも同じなのですが、返り値の型を宣言しているか・していないかで
処理結果が変わるのがどうしても理解できません。

ポインタで関数へ値を渡してやると、返り値の宣言は影響を
受けないと考えているのですが、間違っているのでしょうか?

わかりにくい質問で申し訳ないのですが、
宜しくお願いします。

#include<stdio.h> //基本ライブラ #include<stdlib.h> //malloc関数用ライブラリ typedef struct abc{ int a; int b; struct abc *NEXT; struct abc *PREV; }ABC; ABC *hogehoge(ABC *head); void hogegege(ABC *head); int main() { ABC *hoge=NULL; ABC *hoge1=NULL; ABC *hoge2=NULL; ABC *hoge3=NULL; /*hogeの場合*/ if ((hoge = (ABC *) malloc(sizeof(ABC))) == NULL){ printf("malloc error\n"); exit(EXIT_FAILURE); } hoge->a = 1; hoge->b = 1; hoge->PREV = NULL; /*hoge1の場合*/ if ((hoge1 = (ABC *) malloc(sizeof(ABC))) == NULL){ printf("malloc error\n"); exit(EXIT_FAILURE); } hoge1->a = 2; hoge1->b = 2; hoge1->PREV = hoge; hoge->NEXT = hoge1; /*hoge2の場合*/ if ((hoge2 = (ABC *) malloc(sizeof(ABC))) == NULL){ printf("malloc error\n"); exit(EXIT_FAILURE); } hoge2->a = 3; hoge2->b = 3; hoge2->PREV = hoge1; hoge1->NEXT = hoge2; /*hoge3の場合*/ if ((hoge3 = (ABC *) malloc(sizeof(ABC))) == NULL){ printf("malloc error\n"); exit(EXIT_FAILURE); } hoge3->a = 4; hoge3->b = 4; hoge3->PREV = hoge2; hoge2->NEXT = hoge3; hoge3->NEXT = NULL; hogegege(hoge); puts("現在のhogeは・・・\n"); printf("a = %d\n\n",hoge->a); hoge = hogehoge(hoge); puts("現在のhogeは・・・\n"); printf("a = %d\n\n",hoge->a); return 0; } ABC *hogehoge(ABC *head) { while(head->NEXT != NULL){ head = head->NEXT; } return head; } void hogegege(ABC *head) { while(head->NEXT != NULL){ head = head->NEXT; } return; }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

余分な部分は削って出来るだけソースコードを簡潔にしました.「説明より具体例」のポリシーで,出来るだけ読んだだけで納得できるように書いてみました.

c

1#include <stdio.h> 2 3typedef struct node { 4 int value; 5 struct node *next; 6 struct node *prev; 7} node_t; 8 9node_t *follow_next_and_return_ptr(node_t *node) 10{ 11 return node->next; 12} 13 14void follow_next_and_overwrite_ptr(node_t *node) 15{ 16 *node = *(node->next); 17} 18 19void follow_next_and_overwrite_ptrptr(node_t **node) 20{ 21 *node = (*node)->next; 22} 23 24int main(void) 25{ 26 node_t a = {1, NULL, NULL}; 27 node_t b = {2, NULL, NULL}; 28 node_t c = {3, NULL, NULL}; 29 node_t d = {4, NULL, NULL}; 30 31 a.next = &b; 32 b.prev = &a; 33 b.next = &c; 34 c.prev = &b; 35 c.next = &d; 36 d.prev = &c; 37 38 node_t *current_ptr = &a; 39 40 printf("初期状態.\n"); 41 printf("current_ptr is %p\n", current_ptr); 42 printf("current_ptr->value is %d\n", current_ptr->value); 43 printf("\n"); 44 45 current_ptr = follow_next_and_return_ptr(current_ptr); 46 printf("ポインタを関数から返された別のポインタで上書きします.\n"); 47 printf("current_ptr = follow_next_and_return_ptr(current_ptr);\n"); 48 printf("current_ptr is %p\n", current_ptr); 49 printf("current_ptr->value is %d\n", current_ptr->value); 50 printf("\n"); 51 52 follow_next_and_overwrite_ptr(current_ptr); 53 printf("ポインタの参照先を関数内で上書きします.呼び出し側のポインタ自体は変化しません.\n"); 54 printf("follow_next_and_ovewrite_ptr(current_ptr);\n"); 55 printf("current_ptr is %p\n", current_ptr); 56 printf("current_ptr->value is %d\n", current_ptr->value); 57 printf("\n"); 58 59 follow_next_and_overwrite_ptrptr(&current_ptr); 60 printf("ポインタを関数内で上書きします.呼び出し側のポインタ自体も変化します.\n"); 61 printf("follow_next_and_ovewrite_ptrptr(&current_ptr);\n"); 62 printf("current_ptr is %p\n", current_ptr); 63 printf("current_ptr->value is %d\n", current_ptr->value); 64 printf("\n"); 65 66 return 0; 67}

実行結果の例

初期状態. current_ptr is 0x7fff52475660 current_ptr->value is 1 ポインタを関数から返された別のポインタで上書きします. current_ptr = follow_next_and_return_ptr(current_ptr); current_ptr is 0x7fff52475648 current_ptr->value is 2 ポインタの参照先を関数内で上書きします.呼び出し側のポインタ自体は変化しません. follow_next_and_ovewrite_ptr(current_ptr); current_ptr is 0x7fff52475648 current_ptr->value is 3 ポインタを関数内で上書きします.呼び出し側のポインタ自体も変化します. follow_next_and_ovewrite_ptrptr(&current_ptr); current_ptr is 0x7fff52475618 current_ptr->value is 4

投稿2016/04/01 11:02

編集2016/04/01 11:03
mpyw

総合スコア5223

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bob_heuer0925

2016/04/04 00:36

回答ありがとうございます。 返信が遅れてしまって申し訳ございませんでした。 私自身Cを独学で勉強し始めたばっかりで、リスト構造体などの説明が詳しく書かれている 書籍が見当たらず独学で色々なサイトのコードの寄せ集めで、ようやく概要を理解できたかなと勘違いしていたのですが、CertaiNさんが書いて頂いたソースで 関数内での処理・自分が確認したかった事などが数珠繋ぎで理解する事が出来ました。 本当にありがとうございます。
guest

0

> hoge = hogehoge(hoge);

hogehoge() の結果を受け取って main() 側の hoge を上書きしているから
結果的にこれ以降の hoge で参照される結果が変わってるだけではないですか?

投稿2016/04/01 09:55

HiroshiWatanabe

総合スコア2160

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bob_heuer0925

2016/04/01 10:06

早速の回答ありがとうございます。 main()にて、自作関数を呼び出すタイミングで値が変わってしまうと考えたので 先に返り値宣言のないhogegege関数を呼び出し、出力させて値を確認したのですが 変更されておらず、そのあとに返り値宣言をしているhogehoge関数で値を確認して みると、変更されていたので理解ができなくなってしまいました。 あと、回答頂いてる皆様がた ふざけた関数名でほんと申し訳ないです。焦って適当に命名してしまいました。 スミマセン。。。。
HiroshiWatanabe

2016/04/01 10:34

何が問題なのかよくわからないのですが hogegegeでは変化せずhogehogeでは変化している、という事ではなく hogehogeの変化結果をhogeで受け取っているからhogeの結果が変わっている という点を理解されていないのでしょうか? 例えば hoge = hogehoge(hoge); ではなく ABC *hp = hogehoge(hoge); とすればこの後 hoge->a を表示しても変わらないですよね? (もちろんこの後 hp->a を表示すれば変わった4が表示されるでしょう) hogehoge()の中でmainのhogeが変わったのではなく hogehoge()の結果(戻り値)をmainで受け取ってhogeを上書きしてしまったから mainでのhogeが変わってしまった、という事です。
bob_heuer0925

2016/04/04 00:26

回答ありがとうございます。 また、返信が遅れてしまって申し訳ございません。 >hogehoge()の中でmainのhogeが変わったのではなく >hogehoge()の結果(戻り値)をmainで受け取ってhogeを上書きしてしまったから >mainでのhogeが変わってしまった、という事です。 上記の解りやすい説明をして頂いたお陰で、理解する事ができました。 大変お手数おかけしました。 有難うございました。
guest

0

gcc 5.3.0で試して見ましたが、hogehogeでもhogegegeでも両方とも1になりますけど、どう変わるのでしょうか?コンパイラやそのときのオプションを合わせて教えていただければと思います。


編集後のコードでやりたいことがなんとなくわかりました。hogeに入っているポインタを最後の所に移したいと言うことですね。それであれば、次のように書くべきでしょう。

ABC *hogehoge(ABC **head) { while ((*head)->NEXT != NULL) { *head = (*head)->NEXT; } return *head; } void hogegege(ABC **head) { while ((*head)->NEXT != NULL) { *head = (*head)->NEXT; } return; }

呼び出す時はhogehoge(&hoge)のようにします。書き換えたいのはABC *型のhogeですので、書き換えるにはABC **型で渡さないとhoge(つまり、ABC型へのポインタという値)は変わりません。なので、ABC *型で渡しても、hoge自体は何も変わらないのです。上のコードなら、どちらの関数を使っても期待通りに動くと思います。

投稿2016/04/01 09:51

編集2016/04/01 10:15
raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bob_heuer0925

2016/04/01 10:02

早速の回答ありがとうございます。 出力結果はmain()にて関数処理後にprintfで値を確認すると `hogehoge`だと 4(hoge4データ) `hogegege`だと1(hogeデータ) となります。 開発環境はMicrosoft Visual C++を何もオプション変更をせずに使用しているので 特殊な環境ではないと思うのですが・・・ コンパイラもVisual C++標準のコンパイラで何もアドオンなどは追加していないです。 宜しくお願いします。
bob_heuer0925

2016/04/04 00:25

回答ありがとうございます。 また、返信が遅れてしまって申し訳ございません。 raccyさんが書いてくださったコードを組み込むと、自分が実現したい動きが確認できました。本当に有難うございます。 ポインタのポインタという考え方は、私自身苦手意識を持っていて 避けてきた道だったので、これを機にポインタの勉強をし直したいと思います。
guest

0

ベストアンサー

こんにちは。

やってみました。hogegege()を呼んだ時もhogehoge()を呼んだ時も結果は同じでした。

といいますか、どちらの関数もmain()にあるhoge変数を変更していません。
両方共パラメータとして受け取ったheadを書き換えてますが、head自体は値渡しです。
headの指す先が他の言語で言う「参照渡し」されていることになりますが、headの指す先を変更していません。

C言語は常に値渡しです。参照渡しは存在しません。(C++は参照渡しできますが、Cはできません。)
代わりにポインタを渡してポインタの指す先を変更することで、呼び出し元へ結果を返すことができます。

投稿2016/04/01 09:29

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bob_heuer0925

2016/04/01 09:52

早速の回答ありがとうございます。 >どちらの関数もmain()にあるhoge変数を変更していません 自分の認識としては、自作した関数にmallocで領域確保した構造体のアドレスを引数として渡しているから、自作関数内で処理が行われたら結果が反映されると認識しているのですが、そもそもの認識が誤っているのでしょうか? 私が今使用している開発環境はMicrosoft Visual C++なのですが C言語を勉強しているつもりが、C++とごちゃ混ぜになってしまっていたのでしょうか・・・ 宜しくお願いします。
Chironian

2016/04/01 10:11 編集

構造体のアドレスを渡してますので、その構造体の中身を変更したら反映されます。 でも、構造体の中身を変更していないですよ。 構造体のアドレスを変更してますが、これは値渡しですので呼び出し元へ反映されません。 構造体のアドレス自身を変更した結果を反映したい場合は、「構造体のアドレス」が入った変数のアドレスを渡す必要があります。 void hogegege(ABC **head) {   while((*head)->NEXT != NULL){     (*head) = (*head)->NEXT;   }   return; } のように定義して、次のようにして呼び出します。 hogegege(&hoge); 【追記】 あっと、ソース修正されたのですね。 hogehogeはhogeを値渡しで受け取り、変更した結果を戻り値で返却し、呼び出し元では戻り値をhogeへ設定していますので、当たり前ですが、呼び出し元のhogeに反映されます。 hogegege()はその操作をしていませんので、呼び出し元のhogeに反映されません。
bob_heuer0925

2016/04/01 10:30

詳しい説明まで入れて頂き、有難うございます。 >構造体のアドレスを変更してますが、これは値渡しですので呼び出し元へ反映されません。 >構造体のアドレス自身を変更した結果を反映したい場合は、「構造体のアドレス」が入った >変数のアドレスを渡す必要があります。 上記の説明して頂いた文章で納得できました。 私が引数としてポインタ変数を用いれば、参照渡しになると間違った認識で 理解していたのが原因だという事が判明しました。 詳しいご回答していただきまして、有難うございます。 あと関数名に関しては、自分で声に出しながらキーボード叩いて 【ほげ】や【ほげげげ】と自分で笑ってしまったので、ふざけた奴だと勘違いされないか 心配になりました。 フォローありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.51%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問