質問に答える前に、ポインタ操作中に内部で何が起こるかを見てみましょう。私はこれを示すために非常に単純なコードを使用しています:
#include <stdio.h>
int main() {
int *p;
int **p2;
int x = 3;
p = &x;
p2 = &p;
return 0;
}
今分解を見てください:
(gdb) disassemble
Dump of assembler code for function main:
0x0000000000400474 <+0>: push rbp
0x0000000000400475 <+1>: mov rbp,rsp
0x0000000000400478 <+4>: mov DWORD PTR [rbp-0x14],0x3
0x000000000040047f <+11>: lea rax,[rbp-0x14]
0x0000000000400483 <+15>: mov QWORD PTR [rbp-0x10],rax
0x0000000000400487 <+19>: lea rax,[rbp-0x10]
0x000000000040048b <+23>: mov QWORD PTR [rbp-0x8],rax
=> 0x000000000040048f <+27>: mov eax,0x0
0x0000000000400494 <+32>: leave
0x0000000000400495 <+33>: ret
分解はかなり自明です。ただし、ここにいくつかのメモを追加する必要があります。
私の関数のスタックフレームはここから始まります:
0x0000000000400474 <+0>: push rbp
0x0000000000400475 <+1>: mov rbp,rsp
だから彼らが今持っているものをしましょう
(gdb) info registers $rbp
rbp 0x7fffffffe110 0x7fffffffe110
ここでは、のアドレスに値を入れてい3
ます[rbp - 0x14]
。メモリマップを見てみましょう
(gdb) x/1xw $rbp - 0x14
0x7fffffffe0fc: 0x00000003
DWORD
32ビット幅のデータ型が使用されていることに注意することが重要です。したがって、補足として、のような整数リテラル3
は4バイト単位として扱われます。
次の命令はlea
、前の命令で保存した値の実効アドレスをロードするために使用します。
0x000000000040047f <+11>: lea rax,[rbp-0x14]
これは$rax
、値がになったことを意味します0x7fffffffe0fc
。
(gdb) p/x $rax
$4 = 0x7fffffffe0fc
次に、このアドレスを使用してメモリに保存します
0x0000000000400483 <+15>: mov QWORD PTR [rbp-0x10],rax
ここで使用されていることに注意してQWORD
ください。64ビットシステムには8バイトのネイティブポインタサイズがあるためです。0x14 - 0x10 = 4
以前のmov
命令ではバイトが使用されていました。
次にあります:
0x0000000000400487 <+19>: lea rax,[rbp-0x10]
0x000000000040048b <+23>: mov QWORD PTR [rbp-0x8],rax
これも2番目の間接参照です。アドレスに関連するすべての値は常にQWORD
です。これは、これに注意するための重要なことです。
それでは、コードを見てみましょう。
swaparrayを呼び出す前に、次のようにします。
=> 0x00000000004004fe <+8>: mov DWORD PTR [rbp-0x10],0x1
0x0000000000400505 <+15>: mov DWORD PTR [rbp-0xc],0x3
0x000000000040050c <+22>: mov DWORD PTR [rbp-0x20],0x2
0x0000000000400513 <+29>: mov DWORD PTR [rbp-0x1c],0x4
0x000000000040051a <+36>: lea rdx,[rbp-0x20]
0x000000000040051e <+40>: lea rax,[rbp-0x10]
0x0000000000400522 <+44>: mov rsi,rdx
0x0000000000400525 <+47>: mov rdi,rax
これは非常に些細なことです。配列が初期化&
され、配列の開始の実効アドレスがとに読み込まれる$rdi
と、演算子の効果が表示され$rsi
ます。
それでは、内部で何が行われているのかを見てみましょうswaparray()
。
配列の先頭はとに保存され$rdi
ます$rsi
。だから彼らの内容を見てみましょう
(gdb) p/x $rdi
$2 = 0x7fffffffe100
(gdb) p/x $rsi
$3 = 0x7fffffffe0f0
0x00000000004004c8 <+4>: mov QWORD PTR [rbp-0x18],rdi
0x00000000004004cc <+8>: mov QWORD PTR [rbp-0x20],rsi
これで、最初のステートメントint *temp = *a
は次の指示に従って実行されます。
0x00000000004004d0 <+12>: mov rax,QWORD PTR [rbp-0x18]
0x00000000004004d4 <+16>: mov rax,QWORD PTR [rax]
0x00000000004004d7 <+19>: mov QWORD PTR [rbp-0x8],rax
今、決定的な瞬間が来ます、あなたに何が起こっているの*a
ですか?
$rax
に格納されている値にロードされ[rbp - 0x18]
ます。値$rdi
が保存された場所。これは、最初の配列の最初の要素のアドレスを保持します。
- に格納されているアドレスを使用して別の間接参照を実行し、
$rax
をフェッチしてQWARD
にロードし$rax
ます。それで、それは何を返しますか?QWARD
からを返し0x7fffffffe100
ます。これは、実際には、そこに保存されている2つの4バイトの量から8バイトの量を形成します。詳述すると、
そこの記憶は以下のようなものです。
(gdb) x/2xw $rdi
0x7fffffffe100: 0x00000001 0x00000003
今、あなたがフェッチした場合QWORD
(gdb) x/1xg $rdi
0x7fffffffe100: 0x0000000300000001
だからすでにあなたは実際にねじ込まれています。間違った境界でフェッチしているためです。
残りのコードも同様に説明できます。
では、なぜ32ビットプラットフォームで違うのでしょうか。32ビットプラットフォームでは、ネイティブポインタの幅が4バイトであるためです。ですから、ここでのことはそこでは異なります。意味的に正しくないコードの主な問題は、整数型の幅とネイティブポインタ型の違いに起因します。両方が同じ場合でも、コードを回避できます。
ただし、ネイティブタイプのサイズを想定したコードを記述しないでください。そのため、標準があります。そのため、コンパイラは警告を出します。
言語の観点からは、以前の回答ですでに指摘されているタイプの不一致があるため、ここでは取り上げません。