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

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

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

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

Q&A

解決済

3回答

1630閲覧

C++でアドレスに16進数を足すと意図しない値になります。。。

退会済みユーザー

退会済みユーザー

総合スコア0

C++

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

0グッド

1クリップ

投稿2020/06/17 22:57

#前提
メモリのオフセットの理解がいまいちできていなかったので、Visual Studioのデバッグを使って色々試してみようと思ったのですが、問題が出ました。

#コード
全体のコードです
超シンプルなものですし、その後部分的に分けて意図を書くので流し読みで大丈夫です。

cpp

1#include <iostream> 2int main() 3{ 4 uintptr_t initialVar = 12345; 5 uintptr_t nextVar = 5530; 6 7 uintptr_t* pInitialVar = &initialVar; 8 uintptr_t* pNextVar = &nextVar; 9 10 std::cout << "initialVar address: " << pInitialVar << std::endl; 11 std::cout << "nextVar address: " << pNextVar << std::endl; 12 13 std::cout << "address distance: " << pInitialVar - pNextVar << std::endl; 14 15 uintptr_t* A = pInitialVar - 0xC; 16 std::cout << "A address: " << A << std::endl; 17 std::cout << "A value: " << *A << std::endl; 18 19 return 0; 20}

#各行の意図と出力の結果

cpp

1uintptr_t initialVar = 12345; 2uintptr_t nextVar = 5530; 3 4uintptr_t* pInitialVar = &initialVar; 5uintptr_t* pNextVar = &nextVar;

まずuintptr_t型の変数と、その変数のポインタを作りました。

cpp

1std::cout << "initialVar address: " << pInitialVar << std::endl; 2std::cout << "nextVar address: " << pNextVar << std::endl;

ここで、二つの変数のアドレス値を出力しました。
もちろん実行するたびにアドレスは変動するのですが、例として一つ実際の出力結果を置いときます↓

initialVar address: 001EFC5C nextVar address: 001EFC50

この二つの変数のアドレスの距離は、見た通り0xCですよね?


cpp

1std::cout << "address distance: " << pInitialVar - pNextVar << std::endl;

にも関わらず、この行の出力結果は3となります。

cpp

1uintptr_t* A = pInitialVar - 0xC; 2std::cout << "A address: " << A << std::endl; 3std::cout << "A value: " << *A << std::endl;

なので、pInitialVar - 0xCをすると、見当違いなアドレスになってしまいます。
ちなみに0x3にすると、想定通りの動きをします。

これはどういうことなんでしょうか。
Visual Studioのメモリビューで確認したところ、pInitialVarとpNextVarの頭から頭までの距離は0xCバイトでした。
なんで減算の結果が3となるんでしょうか?

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

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

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

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

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

guest

回答3

0

pInitialVarは、intのポインタですね。
とすると、サイズは、4byteでしょうか。
0x0C / 4 => 3
で、3となります。ポインタどうしの差は、サイズで割ったものとなります。
分かりにくいという話もありますが、Cから。

またそうでないと、 +1 が不適切な場所を指す事になります。

投稿2020/06/17 23:02

pepperleaf

総合スコア6385

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

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

退会済みユーザー

退会済みユーザー

2020/06/18 04:59

二つの変数のアドレスの間にあるバイト達は特に型とかがないバイトだと思ってたんですが、なぜ間の12バイトを整数型のバイト数で割るのでしょうか?
guest

0

ベストアンサー

配列とポインタの関係は理解されていますか?
配列an番目要素のアドレスは&a[n]a + nのどちらでも書けます。

  • &a[n] == a + n
  • &a[0] == a == a + n - n == &a[n] - n

要素の型のポインタとしての加減算はnですが、実アドレスの差はn要素分のsizeof(a[0]) * nとなります。

質問の場合はuintptr_t*なのでsizeof(unsigned int) * 3 = 0xC


気になったのでC++の仕様書(Draft)を確認したら、
ポインタ同士の引き算は

  • どちらもヌルポインタと評価されるなら結果は0
  • どちらも同じ配列の要素を指しているならば要素番号の引き算結果
  • それ以外は未定義

とあるので、異なるオブジェクトを指すポインタの引き算は未定義の動作ですね。

Additive operators
When two pointer expressions P and Q are subtracted, the type of the result is an implementation-defined signed integral type; this type shall be the same type that is defined as std​::​ptrdiff_­t in the <cstddef> header ([support.types.layout]).
(5.1)If P and Q both evaluate to null pointer values, the result is 0.
(5.2)Otherwise, if P and Q point to, respectively, array elements i and j of the same array object x, the expression P - Q has the value i − j.
(5.3)Otherwise, the behavior is undefined. [ Note: If the value i − j is not in the range of representable values of type std​::​ptrdiff_­t, the behavior is undefined. — end note ]

投稿2020/06/18 01:15

編集2020/06/18 15:53
SHOMI

総合スコア4079

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

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

退会済みユーザー

退会済みユーザー

2020/06/18 04:41

ごめんなさい、配列とポインタの関係を全く知りませんでした! なぜここで配列という考え方が出てくるんでしょうか? 二つの変数は、メモリ上では配列という形で保管されているんでしょうか?
SHOMI

2020/06/18 05:14 編集

a[n]は*(a+n)の糖衣構文です。 そのため、ポインタaへの1加算/減算は実際のアドレスとしてはaの指す要素のサイズ(今回ならunsigned int)分加算/減算されるのです。 *(a+n)のaを今回のpInitialVar,pNextVarに置き換えれば後の話は同じです。
退会済みユーザー

退会済みユーザー

2020/06/18 05:25 編集

変数が、実際は配列のようなもので管理されているのはわかりました! ただ、例えばinitialVarとnextVarがメモリ上遠く離れた位置で初期化された場合、配列の中身はどうなるんでしょうか?メモリ上の二つの変数の間には他の関数やクラスなどが挟まってるわけですが、その場合配列の中身にその関数などが入ってくるんですか?
SHOMI

2020/06/18 06:15 編集

>変数が、実際は配列のようなもので管理されているのはわかりました! 違います。 a[n]と*(a+n)の記述が等価であるというだけです。 ポインタ同士の引き算は引く側のアドレスを起点としたnを求めることとなります。
退会済みユーザー

退会済みユーザー

2020/06/18 10:34

えっと、僕は本当に疎いので大雑把に理解するのが精いっぱいだったんですが ・単純なポインタ同士の計算は、配列でいうインデックス動詞の計算みたいになる ・実際のバイト数(アドレス間の距離)を出す場合は sizeof(uintptr_t) * (a - b) みたいに、型のサイズを乗算すると出る これから推測するに、二つのアドレスの距離は、型のサイズで必ず除算できるようになってるんですかね多分。
anndonut

2020/06/19 00:17

part34さん、詳しいことが知りたければC++の仕様書を見ましょう。仕様書を見る前に、C++の仕様はどのタイミングで改定されているか調べましょう。wikipediaを見れば分かるはずです。C/C++の仕様書は実はJISで和訳がでているのですがそれはどうすれば閲覧できるか調べてみましょう。また、他のサイトで仕様書の和訳が読めないか確認してみましょう。SHOMIさんもひまではないのです。詳しい仕様を知りたくなければ、自分が知りたい最低限の情報は何か考えてから調べましょう。またはUS版のStack Overflowを見ればpart34さんの知りたい情報が全部載っているかもしれませんがうやむやを解決するにはC++の仕様書をあたることです。
guest

0

具体的に書いてみました。

C++

1#include <iostream> 2 3int main() { 4 uintptr_t a = 12345; 5 uintptr_t b = 5530; 6 7 uintptr_t *pa = &a; 8 uintptr_t *pb = &b; 9 10 // これは合っている 11 std::cout << "Address of a: " << pa << std::endl; 12 std::cout << "Address of b: " << pb << std::endl; 13 14 // ポインタ同士の減算は、基本的に同じ配列の要素同士の引き算である 15 // ことが前提になっているのでこれは間違い 16 std::cout << "pb - pa = " << pb - pa << std::endl; 17 18 // uintptr_tはポインタを整数型として扱うための型なのでこのように使うのが 19 // 正解 20 std::cout << "Address difference of a and b: " 21 << ((uintptr_t)pb) - ((uintptr_t)pa) 22 << std::endl; 23 24 uintptr_t c[] = { 1, 2, 3, 4, 5 }; 25 uintptr_t *pc1 = &(c[1]); 26 uintptr_t *pc4 = &(c[4]); 27 std::cout << "Address of c[1]: " << pc1 << std::endl; 28 std::cout << "Address of c[4]: " << pc4 << std::endl; 29 std::cout << "pc4 - pc1 = " << pc4 - pc1 << std::endl; 30 31 return 0; 32} 33

実行結果です。

Address of a: 0x7fffbdb1d1b0 Address of b: 0x7fffbdb1d1b8 pb - pa = 1 Address difference of a and b: 8 Address of c[1]: 0x7fffbdb1d1e8 Address of c[4]: 0x7fffbdb1d200 pc4 - pc1 = 3

[参考]
Stack Overflowの記事で、C++の仕様の中にポインタの減算が配列の要素に関係するという記述があったので紹介しておきます。
When subtracting two pointers in C

投稿2020/06/18 02:22

編集2020/06/18 05:02
anndonut

総合スコア667

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

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

退会済みユーザー

退会済みユーザー

2020/06/18 04:57

なぜ別々で初期化した変数aとbが配列という概念に縛られているのかがよくわからないです。。 また、(uintptr_t)でキャストするとなぜ結果が変わるんですかね...?表面上16進数から10進数に変わっただけに見えるので不思議です 最後から二番目の行なんですが、このように実際の配列内のpc4 - pc1をした場合は実際のアドレス間距離が出ているというのはわかりました!
anndonut

2020/06/18 05:03

参考記事のリンク追加しておきました
anndonut

2020/06/18 05:08

キャストすると結果が変わるのは、operator(<<)が型によって挙動を変えるからですね。
退会済みユーザー

退会済みユーザー

2020/06/18 05:26 編集

リンク拝見しました!つまり二つの変数は配列の要素のようなものとして格納されていて、単純に引き算すると配列内での位置の差が出てくるってことっぽいですね!! この場合aとbが同じ型なので話が分かりやすいんですが、これがintとstd::stringだった場合どのようになるんでしょうか。 これも減算時は二つのポインタを(uintptr_t)にキャストして減算すると、byteでの距離が出るということで正しいですかね?(uintptr_tでのキャストはアドレスの計算をする上では不変ですよねおそらく?)
SHOMI

2020/06/18 05:28 編集

>キャストすると結果が変わるのは、operator(<<)が型によって挙動を変えるからですね。 違いますよ。 int p[2]; int diff = &p[1] - &p[0]; int diff2 = (intptr_t)&p[1] - (intptr_t)&p[0]; でdiffには1、diff2には4が入ります。
anndonut

2020/06/18 05:51

SHOMIさん、うっかり間違えました。ポインタ同士の引き算をすると整数型が返ってくるからですね。 ポインタ型と整数型についての加減算は面倒なんですよ。直感的にはわかりやすいんですけど。C++の仕様書にはどう書いてあるのかわかりません。私はC++オタクではないので…
anndonut

2020/06/18 05:57 編集

結局のところ、みなさんが主張されている、a[3]と*(a + 3)が同じだっていうことなんですね。で、aはchar, short, int, doubleとかでサイズが変わってくるからa + 3の+3の意味も変わってくる。b = a + 3という演算ができるからb - a = 3にすれば整合性がとれるよね、って発想だと思います。私はアドレス同士の減算なんて使わないのでわからなかったんですけど。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問