🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

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

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

4回答

24199閲覧

C++ 参照渡し時のNULLチェックの必要性

pochi_kun

総合スコア36

C

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

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

1グッド

6クリップ

投稿2019/09/15 00:59

質問の背景

お世話になります。
C++開発をしておりますが、Java上がりなのでやはりポインタ周りの理解が不十分で、以下の疑問を抱いております。

参照渡しはポインタ渡しよりも安全だ という記述を良く見かけるのですが、それがどうも良くわかりません。
特に参照渡しだと「実体がある前提で良い」かのような記述を見かけるのですが、そこが理解できません。

私個人としては、参照渡しの方がソースが見やすいので、メリットがあるのであれば、特別スコープの広い変数でヒープ領域にメモリ確保したい場合を除き、ポインタよりも参照を使用するような方向性にしたいです。
参照渡しがない時代によく開発していた人に、参照渡しを推し進めたいのですが、うまく説明できずにいます…。

本題

例えば、以下のようなプログラムがあった場合、SumPは、nullptrの考慮が必要だと思います。
SumRでは、参照がNULLとなる考慮は必要ないのでしょうか?
ないのであれば、その理由が知りたいです。

関数定義

C++

1// 【ポインタ渡し】a + b の結果をpcに格納する関数 2void SumP(int a, int b, int *pc) { 3 if( pc == nullptr ) { 4 return; 5 } 6 *pc = a + b; 7} 8 9// 【参照渡し】a + b の結果をcに格納する関数 10void SumR(int a, int b, int &c) { 11 c = a + b; 12}

呼び出し

C++

1// 【ポインタ渡し】 2void DoSumP() { 3 int *pRet = nullptr; 4 SumP(10, 20, pRet); //NG 5 pRet = new int(); 6 SumP(10, 20, pRet); //OK 7 int iRet = 0; 8 SumP(10, 20, &iRet); //OK 9} 10// 【参照渡し】 11void DoSumR() { 12 int iRet = 0; 13 SumR(10, 20, iRet); //OK 14 int* pRet = nullptr; 15 SumR(10, 20, *pRet); //NG これのチェックは必要ないの? 16 pRet = new int(); 17 SumR(10, 20, *pRet); //OK 18}

初歩的な質問でお恥ずかしい限りですが、インターネットで調べただけでは直接的な回答にたどり着けなかったため、よろしくお願いいたします。
(C++ムヅカシイ…)

LouiS0616👍を押しています

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

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

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

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

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

guest

回答4

0

いいえ、ヌルポインタの逆参照(間接参照)は未定義動作になるため、該当の部分の前でチェックしておく必要があります。二つの違いは「だれがnullptrの場合を担保するのか?」です。

※ 逆参照"dereference"(間接参照"indirection"とも呼ばれる)と参照渡しにおける左辺値参照"lvalue reference"は全く別の概念であり、混合してはいけません。

ポインタの値渡しの場合

関数側がnullptrが来ることを想定しておかなければなりません。nullptrが渡された場合どのような動作をするのか、例えば、

  1. 何もしない。(free()がそう)
  2. 必要なサイズを返す、必要な領域が確保されて返す、など別の動作をする。(Win32APIに多い)
  3. 未定義動作。(C由来の関数のほとんどがそう)
  4. 例外を発生させる。(C++では処理が重い例外は敬遠されるせいか、あまり見かけないような気がする。Javaではこちらが多い印象(勝手にヌルポになっているというのもあるが))

等が考えられ、あらかじめ決めておきます。そして、3.の未定義動作を除けば、関数内でnullptrであるかをチェックして、あらかじめ決めておいた動作をする必要があるでしょう。

対して、呼び出し側はnullptrを渡しても、nullptrの時の動作をするだけなので、その動作で十分であれば、チェックしておく必要がありません。if (p != nullptr) free(p);とかしなくても良いって事です。

参照渡しの場合

関数側はnullptrが来ることを想定する必要がありません。必ず何かしらの実体が引数と渡されます。ですので、ポインタの値渡しであったような、nullptrであったらの動作を定義する必要も無いですし、チェックする必要もありません。

さて、呼び出し側はどうかと言うことです。呼び出し側もチェックする必要は無いように思います。しかし、もともとポインタであった物を渡したい場合、逆参照して通常のオブジェクトとして渡す必要があります。質問のコードの*pRetの部分ですね。ポインタを逆参照(間接参照)するとき、そのポインタがnullptrではないことを保証するのは、逆参照(間接参照)を書いている側です。つまり、呼び出し側で*pRetが安全に使えるかを確認しておく必要があります。結局の所、コード全体ではどこかでチェックは必要になると言うことです。


参照渡しの場合の方が関数を用意する側は楽です。nullptrを考慮する必要がありませんし、考慮漏れにより思わぬバグを起こすこともありません。ですが、呼び出し側がポインタ操作で書いている場合は、逆参照などの動作に注意を払う必要が出てきます。言ってみれば、誰にとって安全かが違うと言ったところです。でも、関数はブラックボックスになりがちなので、手元にある呼び出し側で安全を確認できる、関数側は何を渡しても安全、と言う方がいいという意味で、安全と言っているのだと思われます。

投稿2019/09/15 02:20

編集2019/09/15 15:06
raccy

総合スコア21737

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

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

0

cpp

1 int* pRet = nullptr; 2 SumR(10, 20, *pRet); //NG これのチェックは必要ないの?

この時点でnullptr dereferenceをしているのでUndefined Behaviorです。

参照のポインタに対する優位なのは、まさにここで、つまりNULLチェックの責務を関数呼び出し側に追い出せるということです。

投稿2019/09/15 01:47

yumetodo

総合スコア5852

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

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

pochi_kun

2019/09/15 02:23

「Undefined Behavior」知らなかったので、調べてみました。 要約すると、「参照は呼び出し先の関数内で実体作れるわけないんだから、呼び出し先で実体作って渡すに決まってんだろ!!!」ってことですかね…? コンパイラで警告表示するなど、結局使用する人が全員この前提知識がないと、安全とはいいきれなさそうですね…。
yumetodo

2019/09/15 14:27

>コンパイラで警告表示するなど、結局使用する人が全員この前提知識がないと、安全とはいいきれなさそうですね…。 ふつうUndefined behavior Sanitizerで発見できます https://wandbox.org/permlink/5GLlxrq26OhNPJvH
yumetodo

2019/09/15 14:32

> 「Undefined Behavior」知らなかったので、調べてみました。 標準規格と処理系 - cpprefjp C++日本語リファレンス https://cpprefjp.github.io/implementation-compliance.html Old New Thing: 未定義動作はタイムトラベルを引き起こす(他にもいろいろあるけど、タイムトラベルが一番ぶっ飛んでる) | 本の虫 https://cpplover.blogspot.jp/2014/06/old-new-thing.html とても賢いコンパイラーの逆襲 | 本の虫 https://cpplover.blogspot.jp/2015/12/blog-post_21.html C/C++はnull安全になる前に安全に差の絶対値を計算できるようになるべきではないか https://qiita.com/yumetodo/items/4ea151e15b5e540cfef5 コンパイラのリミッタが外れつつある今、null安全は必須なのかもしれない https://qiita.com/AoiMoe/items/2554f78dc9c197d22109 なんかも参考にどうぞ! >要約すると、「参照は呼び出し先の関数内で実体作れるわけないんだから、呼び出し先で実体作って渡すに決まってんだろ!!!」ってことですかね…? ん?参照は関係ないです、NULLポインタをderederenceすることが問題だったわけです。
guest

0

こんにちは。

SumRでは、参照がNULLとなる考慮は必要ないのでしょうか?
ないのであれば、その理由が知りたいです。

参照をnullptrで初期化することは未定義です。
ポインタにnullptrを設定した際の振る舞いは標準規格で定義されていますが、参照をnullptrで初期化した時の振る舞いは定義されていないのです。その時、コンパイラがどのようなコードを出力するのかプログラマは制御できません。予想外の振る舞いをするかもしれないのです。
C++の未定義の挙動で呼ばれないはずの関数が呼ばれる場合

だから、もし、参照がnullptrで初期化されていないか確認しようとしても、その確認するコードが期待通りに振る舞わない可能性があります。つまり「参照がNULLとなる考慮」は関数側ではしても意味がないのです。

ということは、参照にnullptrを渡さないことはプログラマの責任となります。
間違って渡すことを避けるために、可能な時はポインタを渡さないことが望ましいです。ポインタを渡したいならnullptrでないことをチェックして渡せば良いです。

SumRはpcが指す先をアクセスすると宣言しているのですから、pcが指す先をアクセスする前にnullptrチェックすることは「常識」ですね。

C++

1void foo(int a, int b, int *pc) 2{ 3 ASSERT(pc != nullptr); 4 SumR(10, 20, *pc); 5}

また、下記のコードの場合は、pRetがnullptrでないことはチェックされています。(newはnullptrを返しません。代わりに例外を投げます。)

C++

1 pRet = new int(); 2 SumR(10, 20, *pRet); //OK

更に、下記のようなコードを教育や今回のような質問の際は別として、実際の場面で書くようなプロは居ないでしょう。(もし居たら教育不足です。その人にはもっと勉強して貰わないと怖すぎて使えません。)

C++

1 int* pRet = nullptr; 2 SumR(10, 20, *pRet);

投稿2019/09/15 04:47

編集2019/09/15 04:48
Chironian

総合スコア23272

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

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

0

下記サイトの解説はいかがですか?

実践C++入門講座

※2.参照とポインタの違い のところ

投稿2019/09/15 01:13

編集2019/09/15 01:15
meg_

総合スコア10736

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

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

pochi_kun

2019/09/15 02:25

記事の紹介、ありがとうございます。 まさにこのあたりの違いがきちんと理解できていないから、こんなこともわからないのかと猛省しました。 基礎から勉強しなければいけないと思いました…。 (C言語に精通した人も、C++用の勉強をしてもらう必要を感じていますが、業務のなかだといろいろと難しいですね…。)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問