2

次のコードは、32ビットシステムと64ビットシステムで異なる結果を出力します。

#include <stdio.h>
void swapArray(int **a, int **b) 
{ 
    int *temp = *a; 
    *a = *b; 
    *b = temp; 
} 

int main() 
{ 
    int a[2] = {1, 3}; 
    int b[2] = {2, 4}; 
    swapArray(&a, &b); 
    printf("%d\n", a[0]); 
    printf("%d\n", a[1]); 
    return 0; 
}

32ビットシステムでコンパイルした後の出力は次のとおりです。

2
3

64ビットでは、出力は次のとおりです。

2
4

私が理解しているように、この関数はとswapArrayの最初の要素へのポインタを交換するだけです。したがって、を呼び出した後、を指す必要があり、を指す必要があります。 このため、を生成し、を含む、の位置の後のメモリ内の次のバイトを参照する必要があります。abswapArraya2b1
a[0]2a[1]24

誰か説明してもらえますか?

編集:
コメントと回答のおかげで、私は今、&a&bはタイプint (*)[]であり、ではないことに気づきましたint **。これにより、明らかにコードが正しくなくなります(実際、コンパイラの警告が表示されます)。ただし、コンパイラ(gcc)がエラーではなく警告を表示するのは興味深いことです。
異なるシステムで異なる結果を引き起こす原因についてはまだ疑問が残りますが、コードが正しくないため、関連性が低くなります。

編集2:
異なるシステムでの異なる結果については、AndreyTのコメントを読むことをお勧めします。

4

3 に答える 3

5
swapArray(&a, &b); 

&aタイプで&bはなくint **タイプint (*)[2]です。ところで、あなたのコンパイラはあなたのプログラムを受け入れるのに十分親切ですが、コンパイラはそれを翻訳することを拒否する権利を持っています。

于 2012-07-28T19:29:01.773 に答える
5

質問に答える前に、ポインタ操作中に内部で何が起こるかを見てみましょう。私はこれを示すために非常に単純なコードを使用しています:

#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

DWORD32ビット幅のデータ型が使用されていることに注意することが重要です。したがって、補足として、のような整数リテラル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ですか?

  1. $raxに格納されている値にロードされ[rbp - 0x18]ます。値$rdiが保存された場所。これは、最初の配列の最初の要素のアドレスを保持します。
  2. に格納されているアドレスを使用して別の間接参照を実行し、$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バイトであるためです。ですから、ここでのことはそこでは異なります。意味的に正しくないコードの主な問題は、整数型の幅とネイティブポインタ型の違いに起因します。両方が同じ場合でも、コードを回避できます。

ただし、ネイティブタイプのサイズを想定したコードを記述しないでください。そのため、標準があります。そのため、コンパイラは警告を出します。

言語の観点からは、以前の回答ですでに指摘されているタイプの不一致があるため、ここでは取り上げません。

于 2012-07-28T21:31:04.283 に答える
3

ポインタートリックを使用して配列を交換することはできません(ポインターではありません!)。これらの配列へのポインターを作成してポインターを使用するか、mallocなどを使用して配列を動的に割り当てる必要があります。

64ビットシステムで得られる結果は、たとえば次のようになります。

2
3

test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.8, not stripped

そして、私のMacでclangを使用すると、エラーが発生します。

test.cpp: In function ‘int main()’:
test.cpp:13: error: cannot convert ‘int (*)[2]’ to ‘int**’ for argument ‘1’ to ‘void swapArray(int**, int**)’

これは未定義の動作であり、おそらくジャンク出力であるものを解釈しようとしていると思います。

于 2012-07-28T19:27:39.497 に答える