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

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

ただいまの
回答率

90.47%

  • C

    3826questions

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

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

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 969

bob_heuer0925

score 22

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;
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+1

こんにちは。

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/04/01 18:52

    早速の回答ありがとうございます。

    >どちらの関数もmain()にあるhoge変数を変更していません
    自分の認識としては、自作した関数にmallocで領域確保した構造体のアドレスを引数として渡しているから、自作関数内で処理が行われたら結果が反映されると認識しているのですが、そもそもの認識が誤っているのでしょうか?

    私が今使用している開発環境はMicrosoft Visual C++なのですが
    C言語を勉強しているつもりが、C++とごちゃ混ぜになってしまっていたのでしょうか・・・

    宜しくお願いします。

    キャンセル

  • 2016/04/01 19:06 編集

    構造体のアドレスを渡してますので、その構造体の中身を変更したら反映されます。
    でも、構造体の中身を変更していないですよ。

    構造体のアドレスを変更してますが、これは値渡しですので呼び出し元へ反映されません。
    構造体のアドレス自身を変更した結果を反映したい場合は、「構造体のアドレス」が入った変数のアドレスを渡す必要があります。

    void hogegege(ABC **head)
    {
      while((*head)->NEXT != NULL){
        (*head) = (*head)->NEXT;
      }
      return;
    }
    のように定義して、次のようにして呼び出します。
    hogegege(&hoge);

    【追記】
    あっと、ソース修正されたのですね。
    hogehogeはhogeを値渡しで受け取り、変更した結果を戻り値で返却し、呼び出し元では戻り値をhogeへ設定していますので、当たり前ですが、呼び出し元のhogeに反映されます。
    hogegege()はその操作をしていませんので、呼び出し元のhogeに反映されません。

    キャンセル

  • 2016/04/01 19:20

    ところで、関数名「変な」名前を使われていると認識されているようですが、このような時に使われる一般的な名前でもあるので問題ないですよ。
    https://ja.wikipedia.org/wiki/メタ構文変数

    キャンセル

  • 2016/04/01 19:30

    詳しい説明まで入れて頂き、有難うございます。

    >構造体のアドレスを変更してますが、これは値渡しですので呼び出し元へ反映されません。
    >構造体のアドレス自身を変更した結果を反映したい場合は、「構造体のアドレス」が入った
    >変数のアドレスを渡す必要があります。

    上記の説明して頂いた文章で納得できました。
    私が引数としてポインタ変数を用いれば、参照渡しになると間違った認識で
    理解していたのが原因だという事が判明しました。

    詳しいご回答していただきまして、有難うございます。

    あと関数名に関しては、自分で声に出しながらキーボード叩いて
    【ほげ】や【ほげげげ】と自分で笑ってしまったので、ふざけた奴だと勘違いされないか
    心配になりました。
    フォローありがとうございます!

    キャンセル

+1

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 19:02

    早速の回答ありがとうございます。

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

    宜しくお願いします。

    キャンセル

  • 2016/04/04 09:25

    回答ありがとうございます。
    また、返信が遅れてしまって申し訳ございません。

    raccyさんが書いてくださったコードを組み込むと、自分が実現したい動きが確認できました。本当に有難うございます。

    ポインタのポインタという考え方は、私自身苦手意識を持っていて
    避けてきた道だったので、これを機にポインタの勉強をし直したいと思います。

    キャンセル

+1

>    hoge = hogehoge(hoge);

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/04/01 19:06

    早速の回答ありがとうございます。

    main()にて、自作関数を呼び出すタイミングで値が変わってしまうと考えたので
    先に返り値宣言のないhogegege関数を呼び出し、出力させて値を確認したのですが
    変更されておらず、そのあとに返り値宣言をしているhogehoge関数で値を確認して
    みると、変更されていたので理解ができなくなってしまいました。

    あと、回答頂いてる皆様がた
    ふざけた関数名でほんと申し訳ないです。焦って適当に命名してしまいました。
    スミマセン。。。。

    キャンセル

  • 2016/04/01 19: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が変わってしまった、という事です。

    キャンセル

  • 2016/04/04 09:26

    回答ありがとうございます。
    また、返信が遅れてしまって申し訳ございません。

    >hogehoge()の中でmainのhogeが変わったのではなく
    >hogehoge()の結果(戻り値)をmainで受け取ってhogeを上書きしてしまったから
    >mainでのhogeが変わってしまった、という事です。
    上記の解りやすい説明をして頂いたお陰で、理解する事ができました。

    大変お手数おかけしました。

    有難うございました。

    キャンセル

+1

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

#include <stdio.h>

typedef struct node {
    int value;
    struct node *next;
    struct node *prev;
} node_t;

node_t *follow_next_and_return_ptr(node_t *node)
{
    return node->next;
}

void follow_next_and_overwrite_ptr(node_t *node)
{
    *node = *(node->next);
}

void follow_next_and_overwrite_ptrptr(node_t **node)
{
    *node = (*node)->next;
}

int main(void)
{
    node_t a = {1, NULL, NULL};
    node_t b = {2, NULL, NULL};
    node_t c = {3, NULL, NULL};
    node_t d = {4, NULL, NULL};

    a.next = &b;
    b.prev = &a;
    b.next = &c;
    c.prev = &b;
    c.next = &d;
    d.prev = &c;

    node_t *current_ptr = &a;

    printf("初期状態.\n");
    printf("current_ptr is %p\n", current_ptr);
    printf("current_ptr->value is %d\n", current_ptr->value);
    printf("\n");

    current_ptr = follow_next_and_return_ptr(current_ptr);
    printf("ポインタを関数から返された別のポインタで上書きします.\n");
    printf("current_ptr = follow_next_and_return_ptr(current_ptr);\n");
    printf("current_ptr is %p\n", current_ptr);
    printf("current_ptr->value is %d\n", current_ptr->value);
    printf("\n");

    follow_next_and_overwrite_ptr(current_ptr);
    printf("ポインタの参照先を関数内で上書きします.呼び出し側のポインタ自体は変化しません.\n");
    printf("follow_next_and_ovewrite_ptr(current_ptr);\n");
    printf("current_ptr is %p\n", current_ptr);
    printf("current_ptr->value is %d\n", current_ptr->value);
    printf("\n");

    follow_next_and_overwrite_ptrptr(&current_ptr);
    printf("ポインタを関数内で上書きします.呼び出し側のポインタ自体も変化します.\n");
    printf("follow_next_and_ovewrite_ptrptr(&current_ptr);\n");
    printf("current_ptr is %p\n", current_ptr);
    printf("current_ptr->value is %d\n", current_ptr->value);
    printf("\n");

    return 0;
}

実行結果の例

初期状態.
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/04 09:36

    回答ありがとうございます。
    返信が遅れてしまって申し訳ございませんでした。

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

    本当にありがとうございます。

    キャンセル

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

  • ただいまの回答率 90.47%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    スタックの応用

    スタックを利用して入力された文字列の回文を作るプログラムを作成したら、出力されません。 例えば、「abcd」と入力したら、「abcddcba」と主著力される。 発生して

  • 解決済

    linux 処理時間の表示

    C言語でLinuxを使っています。メモリを確保したりするプログラムなのですが、以下のプログラムを修正して 、5秒間で何回の入れ替えを行えるかを計測できるようにしてもらいたいです。初

  • 解決済

    スタック構造のpopのやり方が分かりません

    cでのスタック構造のポップのやり方が プログラミングを始めたばかりでよくわからないです。 よろしくお願いします。 #include<stdio.h> #include<s

  • 解決済

    C言語のエラー修正について

    コード #include <stdio.h> #define New (element) RealNew( & element ) #define InputInt( number

  • 解決済

    リストを逆順に表示するための関数が、上手く機能しません。

    前提・実現したいこと Cでノードを使った線形リストを作っており、元からあるリストを逆順に表示するための関数を作っていました。 発生している問題・エラーメッセージ 逆順に表示する

  • 受付中

    リスト構造と待ち行列

    リスト構造と待ち行列をしたいのですが、よくわかりません。 おすすめのサイトや説明おねがいします。 #include <stdio.h> #include <stdlib.h>

  • 受付中

    C言語 リスト 新しいノードをリストの最後に追加

    C言語 リストについて ↓のプログラムでは入力した数字を逆順に表示 /* list.c */ #include <stdio.h> #include <stdlib.h> st

  • 解決済

    「ポインタのポインタ」のつなぎ替えをしている部分がわからない

    リストの構造体のデータをソートしているコードがあるんですが、その中の exchange()関数のところを図を使って書こうと思ったのですが、うまくいきません。 その前に書かれているコ

同じタグがついた質問を見る

  • C

    3826questions

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