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

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

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

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

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

Q&A

解決済

4回答

2243閲覧

C言語 '&'、'*' ポインタ関連の疑問あれこれ

paraprara

総合スコア3

C

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

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

0グッド

0クリップ

投稿2021/07/12 15:29

編集2021/07/13 02:12

はじめに

C言語のポインタを学習している際にふと気になった点があったので質問いたします。
私が間違えた解釈しているのかもしれないのでご確認お願いします。

前提

int n = 5;
int* pn = &n;
以降この二つの変数を例に取り、書いていきます。

今のところの頭の中での'&'と'*'

&...その変数のアドレスを取得する
例)
printf("%p", &n); // 変数nのアドレス表示
printf("%p", pn); // 変数nのアドレス表示

*...その変数が格納しているアドレスの値を表示する
*...その変数が格納しているアドレスの値を取得する
例)
printf("%d", *n); // 不可。変数nはポインタ型ではないので他の変数のアドレスは持たない
printf("%d", n); // 変数nの値表示
printf("%d", *pn); // 変数nの値表示

疑問点

1 - 別関数内で変数の値を変更しそのまま保持させておきたいときは値渡しではなく参照渡しを使うべきとありました。
よってとある変数を変える際にはその変数のアドレスを引数にしてあげる必要があるので以下のように記載したのですがエラーが起きました。

// int& なのでint型のアドレスを渡せる!...はずなのだがエラーが起きる // 引数をint* numに替えるとエラーは起きなくなる // int* はつまりアドレスの中身、よってint型を渡すことになるので値渡しになるのでは...?(何故かならないですけど) void ChangeValue(int& num) { *num = 30; }

2 - '&'はその変数のアドレスを取得します。となると以下の1行は一体どのアドレスを指しているのでしょうか?

// 変数pnのアドレス表示..? // 既にpnはpのアドレスを所持している。 // ポインタ型でも存在している以上そのアドレスを別アドレスとして所持している...? printf("%p", &pn);

これはつまり、ポインタ型は他の変数のアドレスを持つだけではなく自分のアドレスも別に持っているということなのでしょうか?
となると、その自分のアドレス内の値に他の変数のアドレスを入れている...という事でしょうか?
てっきりポインタ型のアドレスは他の変数のアドレスと同じようアドレスになり、ポインタとポインタ先の値が一致させられる..と考えていました。

3 - '*'はその変数が格納しているアドレスの値を表示します。こちらも同じように下の一行は何の値を指しているのでしょうか?

// 変数pnの値表示..? // そもそもポインタ型なので他の変数のアドレスを持っていれば *pn で値をとれるが、こいつ自身は値(アドレスを除く)を持っていないはず... // でも表示できてしまう。ただし全くデタラメな数値 printf("%d", pn);

質問が複数あったので長くなってしまいましたが、よろしくお願いします。

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

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

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

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

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

Zuishin

2021/07/13 00:43

> 1 - 別関数内で変数の値を変更しそのまま保持させておきたいときは値渡しではなく参照渡しを使うべきとありました。 C 言語には参照渡しはありません。 参考にしている資料の品質が低く、信頼するに足りないので、これを元に学習するのではなく、ちゃんとした資料を入手しましょう。
otn

2021/07/13 01:25

> 参考にしている資料の品質が低く、信頼するに足りないので、 int *pn; を int* pn; と書いているところからも、それがうかがえますね。 もちろん文法的には空白の有無は関係ないのですが。
paraprara

2021/07/13 02:08

そうなんですね... 実はこちらの情報全て学校の授業で教えられたもので 結構周りも理解できていない状態なんですよね... 少し不安になりましたが、質問サイトや個人でやってるページなど見て学んでいこうと思います。
fana

2021/07/13 02:18

(ソースとか示せませんが) 大昔,C言語でポインタを引数に渡すことが「参照渡し」と呼ばれていたような?
Zuishin

2021/07/13 02:18

ちゃんとした資料と言ったら質問サイトや個人でやってるページになる不思議。
otn

2021/07/13 02:25

> 質問サイトや個人でやってるページなど見て学んでいこうと思います。 初心者がそういうサイトを参考にしちゃ駄目です。
paraprara

2021/07/13 02:37

となると書籍などでしょうか..?
guest

回答4

0

ベストアンサー

int& なのでint型のアドレスを渡せる!...はずなのだがエラーが起きる

C++ なら int& num と書けますが、これはCなので、

引数をint* numに替えるとエラーは起きなくなる
int* はつまりアドレスの中身、よってint型を渡すことになるので値渡しになるのでは...?

この「*」は変数(num)がポインタ変数であることを示す型修飾子です。よって int * num は「仮引数 num は int 型データを指すポインタ変数である」という意味です。
そして仮引数 num に渡ってくる値はアドレスです(アドレスを値渡しする)。ちなみに、C++の参照渡しも、渡ってくる値はアドレスです。

一方、num = 30; の「」は間接参照する間接演算子です。同じ「*」でも文法的な意味が違い、働きも違います。

関数の引数は変数定義と同じです。
int * np = &n; と変数を定義する時の「*」も型修飾子です。「np はポインタだよ」という意味で、決して間接参照するのではありません。
int * np = &n;int * np; np = &n; と分解できます。ここで初期値(&n)が代入される場所は変数 np であり、*np = &n; ではありません。

同じ*でも違う意味なのは難しい

型名(int, char 等)で始まる文は変数定義です。その「」はポインタ修飾子です。
型名で始まらないなら通常の実行文。その「
」は間接参照です。

printf("%p", &pn);
変数pnのアドレス表示..?

はい。ポインタ変数もメモリ上に配置されますので、ポインタ変数自身にもアドレスがあります。そのアドレスを表示します。

printf("%d", pn);は何の値を指しているのでしょうか?
でも表示できてしまう。ただし全くデタラメな数値

ポインタ変数 pn の値(=変数 n のアドレス)を10進数表示します。"%p" は16進表示しますが、"%d" なので10進数表示します。けっしてデタラメな値ではありません。


今どきのCコンパイラは32bit用と64bit用があります。32bitであればポインタは32bit(4byte)の値、64bitであればポインタは64bit(8byte)の値です。質問者のコンパイラがどちらか不明ですが、下のプログラムを私の手元の32bit用GCCでコンパイルし、実行させました。32bitの方がビット数が少ない分、わかりやすいので(必要なら64bitの結果も出せるが)。

さらに、プログラム後半に ppn というポインタへのポインタを加えました(ダブルポインタとも呼ぶ)。n をポイントする pn、pn をポイントする ppn、という関係です。ポインタへのポインタがあるのは、ポインタ変数自身にもアドレスがあるから、ですよね。

実行結果は次のようになりました。

# ./a.exe 0x62cc4c : &n 0x62cc4c : pn 5 : n 5 : *pn 0x62cc48 : &pn 6474828 : (int)pn 62CC4C : (int)pn 0x62cc44 : &ppn 0x62cc48 : ppn 0x62cc4c : *ppn 30 : **ppn

変数はメモリ上に配置されることを思い出してください。
メモリとは1バイト(8bit)毎にアドレスが振られた、長大な配列です。32bit の int 型変数は連続した4バイトに、64bitの double 型は連続した8バイトのメモリに、割当てられます。
同様に、ポインタ変数も4バイトもしくは8バイトのメモリに割当てられます。そして、ポインタ変数の値は他の変数のメモリアドレスです。

C言語はメモリを直接操作するプログラミング言語です。ポインタ変数はその象徴です。(n のような)単純な変数も、配列も、構造体も、ポインタも、リスト構造も・・・メモリ上に配置された変数の姿をイメージする事。イメージできれば迷うことが少なくなります。
上の表示結果から n, pn, ppn のアドレスとデータ(値)を表にしてみました。

アドレスデータ変数名
0x62cc440x62cc48ppn
0x62cc480x62cc4cpn
0x62cc4c30n

C

1#include <stdio.h> 2// C++ なら「int&」とも書けるが、Cでは int * num と書くしかない。 3// 「int* はつまりアドレスの中身(=メモリの値)、 4// よってint型を渡すことになるので値渡しになるのでは...?」<= 違う 5// 呼び出す時点で int*num = &n; と同等のことが行われる 6void ChangeValue(int * num) 7{ 8 *num = 30; 9} 10 11int main(void) 12{ 13 int n = 5; 14 int * pn = &n; 15 16 printf("%p : &n\n", &n); // 変数nのアドレス表示する 17 printf("%p : pn\n", pn); // ポインタ変数pnの値を表示する 18 // 既にpnの値は n のアドレスである 19 // いろいろ表示してみるが 20 // printf("%d", *n); // 「*n」は不可。n はポインタ変数ではないから。 21 printf("%d : n\n", n); // 変数nの値表示 22 printf("%d : *pn\n", *pn); // 変数nの値を、ポインタ pn を使って表示する 23 24 // ポインタ変数もメモリ上に存在する。よってポインタ変数自身のアドレスがある 25 // 変数 pn のアドレスを表示する 26 printf("%p : &pn\n", &pn); 27 28 // 変数pnの値を10進数表示する。念のためキャストする(気休め?) 29 // 表示される値は決して「デタラメな数値」ではない。 30 printf("%d : (int)pn\n", (int)pn); // 10進表示 31 printf("%X : (int)pn\n", (int)pn); // 16進表示 32 33 ChangeValue(&n); // *num = 30; を実行させる 34 35 // ポインタへのポインタを使う 36 int ** ppn = &pn; 37 printf("%p : &ppn\n", &ppn); // ポインタへのポインタ自身のアドレス 38 printf("%p : ppn\n", ppn); // ポインタへのポインタの値 39 printf("%p : *ppn\n", *ppn); // ポインタへのポインタが指す先 40 printf("%d : **ppn\n", **ppn); // ポインタへのポインタが指す先の先 41 return 0; 42}

投稿2021/07/13 00:39

編集2021/07/13 02:16
rubato6809

総合スコア1380

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

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

paraprara

2021/07/13 02:01

コード付きで詳しく書いていただきありがとうございます。 同じ*でも違う意味なのは難しいですね...私のような初心者はどうしてもここで躓いてしまうように思えます。 それにダブルポインタなるのものもあるんですね... もしかしてトリプルも?と思いやってみましたがありました、確かにポインタはとても有用ですね。
guest

0

*...その変数が格納しているアドレスの値を表示する

これは間違っています。

C言語に現れるには3通りあります。
1 乗法をあらわす二項演算子の

2 値をアドレスであるとみなしてそのアドレスの先の値を返す単項演算子の*
3 変数がポインタ型であることを示すために型宣言で使われる*

  • 疑問点 1への回答

型宣言では&は使えません。

int* num という書き方はCの文法です。したがって、なぜそうなのかを考えることには意味がありません。
覚え方としては、*numつまり、numという変数の指す先がint型だ、と覚えておきます。

  • 疑問点 2への回答

全ての変数はメモリ上のどこかにあります。「どこか」の場所を示しているのがアドレスです。つまり、全ての変数はアドレスを持ちます。
全ての変数はその場所に値を持っています。ポインタ型の変数に入っている値はアドレスであると解釈されます。

  • 疑問点 3への回答

コンピュータのメモリにある値は、ビット(0または1)の集まりにすぎません。それをどう解釈するかはソフトウェア次第です。
printf("%d", pn);が実行されるとprintf関数、pnに入っているビット列をintであると解釈して表示します。

投稿2021/07/12 16:46

ppaul

総合スコア24666

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

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

paraprara

2021/07/13 02:05

そういう文法なんですね... 何故*一つにここまで沢山の意味が込められているのか... せめて'2','3'はそれぞれ別の記号にしてほしかったり...とか思わなくもないんですが 調べたところ元々C言語は身内で使うよう設計されたからこのあたりの仕様が甘い...んですかね?
guest

0

void ChangeValue(int& num)

Cではこういう記述はできません。
ポインタ(アドレス)を示すには、
void ChangeValue(int* num)
とします

確かにちょっと分かりづらいですが、
型を表すと、単項演算子のは別モノと考えるほうがよろしいかと

投稿2021/07/12 15:31

編集2021/07/12 15:40
y_waiwai

総合スコア87719

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

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

paraprara

2021/07/13 02:06

ありがとうございます。 確かに別物のようですね、他の回答者様から詳しく教えて頂きました。 ただC++ではできるようなので、いつか学び始めた際に混乱しないように意識してこうと思います。
y_waiwai

2021/07/13 02:11

C++では参照ってのができてそれに&を使いますが、Cでは参照ってのがないですね。 ポインタを渡して似たようなことはできますが、参照とはべつのもんです。 #これは初心者の方がよく混乱されるとこですね
paraprara

2021/07/13 02:48

なるほど...どうやらC言語の参照渡しに見える奴はポインタ渡しと呼ばれているようですが 参照渡しとポインタ渡しの違いってざっくり言うとこんな感じですかね? 参照渡し: 変えたい変数そのものを渡す ポインタ渡し: 変えたい変数のアドレスを持った別ポインタ型変数を渡す
guest

0

  1. 「その変数のアドレスを引数にしてあげる必要がある」

そうですね。渡す側(関数の呼び出し)は、変数のアドレスを渡します。では、受け取る側(関数の宣言・定義)は? アドレスを格納する型はなんでしょう。ポインタです。intへのポインタなら、仮引数は int* num とすればいいです。

  1. そもそも変数ってなんでしょう。メモリの一部を「ここを何かの値を置く場所にしよう」と決めただけのものです。その「ここ」というのがアドレスです。で、置くデータは数値などかも知れませんし、なにかのアドレスの場合もあります。アドレスを格納するのがポインタ。ポインタの定義、ちゃんと知っていますか? C言語の「バイブル」と呼ばれるK&R「プログラミング言語C」によれば、"A pointer is a variable that contains the address of a variable."だそうです(手元に英語版しかなかったので。和訳は日本語版をみてください)。ポインタも変数のひとつなのでデータを格納するための場所であり、つまりアドレスがあります。

ポインタは特殊なナニカではありません。ただの変数。intが整数の値を保持し、doubleが浮動小数点の値を保持するのと同じように、なにかのアドレスを保持しているだけ。

3.「'*'はその変数が格納しているアドレスの値を表示します。」
表現が適切でないだけでわかってはいるのかも知れませんが、その表現では間違いと言わざるを得ません。「表示」なんてしませんし。
*は、ポインタが持っているアドレスにあるデータにアクセスする「参照演算子」です。(もちろん、単に掛け算の記号だったりすることもありますが)
しかも、その話は
printf("%d",pn);
の解釈には関係ないですよね? これまでの話をちゃんと理解していれば、pnとプログラムに書いたらそれはpnという変数に格納されている値そのもの(つまりなにかのアドレス)です。なお、アドレス値を表示するときはprintfの書式指定は"%p"を使ってください。"%d"でも表示されちゃうことがほとんどでしょうが、厳密には「未定義」です。原則として「やってはいけない」コトです。

余談1。俗説的な、ポインタ宣言の理解。
intp;
これは、int
型の変数pを宣言します。つまり
int* /ここで区切る/ p; // pはint*型
*pは、pが指している先のint型のデータになります。
int /ここで区切る/ *P; // *Pと書かれたらそれはint型

余談2。int*p; pはなんですか? と聞かれたらなんと答えますか? 「pはintのポインタです」?
そこは是非、「pはint型へのポインタです」と答えるようにしてみてください。ポインタは何かを指しているものだ、という意識が強化されるんじゃないかと思います。

余談3。Cには文法レベルでの「参照渡し」はありません。テクニック的にアドレスを渡すことで「参照渡し」と同じことができるので、それを参照渡しと呼んでいたりしますが...と言われてわからなかったら一旦忘れてください。でもいつか思い出してね。

投稿2021/07/13 00:40

thkana

総合スコア7610

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

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

paraprara

2021/07/13 01:54

なるほど、頭がスッキリしました。 関数の引数のアレはただポインタ型の変数を宣言しているだけなんですね... またポインタも変数の一部と考えれば特段難しいようにも感じませんね。 結構やってはいけない事が出来てしまうのがC言語なのでしょうか?自由な故の障害ですね。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問