srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

C言語 ポインタ同士の引き算

c言語のポインタ同士の引き算について

int main(void){
    int a[2];
    int k = &a[1] - &a[0];
    return 0;
}

上のコードを実行したら, kを表示すると結果は1となる. 4ではない. ポインタ同士の引き算は内部でアドレスの値を引いた後にそのポインタが指している変数の型のバイト数(sizeof(変数の型))で割った結果を求めるようにコンパイラは働く.
実際に上のコードをコンパイルしたアセンブラを見ると,

0000000100000f9e <_main>:
   100000f9e:   55                      push   rbp
   100000f9f:   48 89 e5                mov    rbp,rsp
   100000fa2:   c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1  //変数kに1が代入されている
   100000fa9:   b8 00 00 00 00          mov    eax,0x0
   100000fae:   5d                      pop    rbp
   100000faf:   c3                      ret 

のようになっている. これは最適化を切ってコンパイルした結果である.
連続領域の場合最適化を切っていても, コンパイル時に相対距離が計算できるので, コンパイル時に計算してしまうようだ(下記の結果から推測).

次に, 連続領域に存在しないポインタの引き算を実行してみる.

int main(void){
    int a[2], b[2];
    int k = &b[0] - &a[0];
    return 0;
}

上のコードを最適化を切って,  コンパイルしたアセンブラは以下のようになる.

0000000100000f90 <_main>:
   100000f90:   55                      push   rbp
   100000f91:   48 89 e5                mov    rbp,rsp
   100000f94:   48 8d 55 e0             lea    rdx,[rbp-0x20] // rdx に &b[0]を代入
   100000f98:   48 8d 45 f0             lea    rax,[rbp-0x10] // rax に &a[0]を代入
   100000f9c:   48 29 c2                sub    rdx,rax // rdxからraxを引く
   100000f9f:   48 89 d0                mov    rax,rdx // rdxの値をraxに代入
   100000fa2:   48 c1 f8 02             sar    rax,0x2 //raxの値を右へシフト2する (rax /= 4)
   100000fa6:   89 45 fc                mov    DWORD PTR [rbp-0x4],eax //変数kに代入する
   100000fa9:   b8 00 00 00 00          mov    eax,0x0
   100000fae:   5d                      pop    rbp
   100000faf:   c3                      ret

今回はコンパイル時には計算できないようで, sizeof(int)で割る処理が行なわれている(sar rax,0x2).

double 型でも同様なことをやっておく.

int main(void){
    double a, b;
    int k = &a - &b; //1
    int l = &b - &a; //-1
    return 0;
}
0000000100000f7b <_main>:
   100000f7b:   55                     push   rbp
   100000f7c:   48 89 e5               mov    rbp,rsp
   100000f7f:   48 8d 55 f0            lea    rdx,[rbp-0x10] // rdx = &a;
   100000f83:   48 8d 45 e8            lea    rax,[rbp-0x18] // rax = &b;
   100000f87:   48 29 c2               sub    rdx,rax // rdx -= rax;
   100000f8a:   48 89 d0               mov    rax,rdx // rax = rdx;
   100000f8d:   48 c1 f8 03             sar    rax,0x3 // rax >>= 3; (rax /= sizeof(double))
   100000f91:   89 45 fc               mov    DWORD PTR [rbp-0x4],eax // k = eax;
   100000f94:   48 8d 55 e8            lea    rdx,[rbp-0x18] // rdx = &b;
   100000f98:   48 8d 45 f0            lea    rax,[rbp-0x10] // rax = &a;
   100000f9c:   48 29 c2               sub    rdx,rax // rdx -= rax;
   100000f9f:   48 89 d0               mov    rax,rdx  // rax = rdx;
   100000fa2:   48 c1 f8 03             sar    rax,0x3 // rax >>= 3 (rax /= sizeof(double))
   100000fa6:   89 45 f8               mov    DWORD PTR [rbp-0x8],eax // l = eax;
   100000fa9:   b8 00 00 00 00          mov    eax,0x0 // return 0;を設定
   100000fae:   5d                      pop    rbp
   100000faf:   c3                       ret  

上記はコードとそのアセンブラの結果である.
sar rax,0x3
という命令があり, 右へ3シフトする. つまり sizeof(double) で割っているということ.

実際のポインタの値同士の引き算をするには?

int main(void){
    int a[2];
    int k = (int)&a[1] - (int)&a[0];
    return 0;
}

上のコードのkには4が代入される. int型にキャストしてから演算すればいい.