1

私はCでポインターを調べていたところ、さまざまなケースを作成していたときに、これに出くわしました:(IDEを使用 - Code::Blocks

コンパイラ - GNU GCC)

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a=2;
    int *pa;
    pa=&a;

    printf("1 %u\n", &a );
    printf("2 %u\n", pa );
    printf("3 %d\n", a );

    printf("4 %u\n", &(pa));
    //printf("\n4 %u\n", &(*pa)); // output not as expected 

    printf("End \n");
    return 0;
}

出力は次のとおりです。

1 2686748

2 2686748

3 2

4 2686744

終わり

ここで、4 番目の printf を次のように変更します。

printf("\n4 %u\n", &(*pa)); 

出力は次のように変わります。

1 2686744

2 2686744

3 2

4 2686744

終わり

ここで 2 番目の部分では、*pa2を指定し、&(*pa)は2686748を指定する必要がありますが、ここでは前の値が変更されています!

予想される出力は次のようになります (&(*pa) の場合): 2686748、2686748、2、2686748

期待される出力が得られない理由と、どこで間違っているのか説明してください??

私は意図的に printf() で %p を使用していないので、%u や %d をそれに置き換えるように頼まないでください。

必要に応じて、IDEone へのリンクを次に示します (同様の出力がそこでも生成されます)。

プログラム -ここでは、コメントを削除/挿入するだけで理解できます。最初に両方の printf を含め、代わりにそのうちの 1 つをコメントアウトします。

stdlib.h のインクルードを無視する

できるだけシンプルにしてください:)

アセンブリ:

First for:

     printf("4 %u\n", &(pa));

   // printf("\n4 %u\n", &(*pa)); 


0x00401334  push   %ebp

0x00401335  mov    %esp,%ebp

0x00401337  and    $0xfffffff0,%esp

0x0040133A  sub    $0x20,%esp

0x0040133D  call   0x401970 <__main>

0x00401342  movl   $0x2,0x1c(%esp)

0x0040134A  lea    0x1c(%esp),%eax

0x0040134E  mov    %eax,0x18(%esp)

0x00401352  lea    0x1c(%esp),%eax

0x00401356  mov    %eax,0x4(%esp)

0x0040135A  movl   $0x403024,(%esp)

0x00401361  call   0x401be8 <printf>

0x00401366  mov    0x18(%esp),%eax

0x0040136A  mov    %eax,0x4(%esp)

0x0040136E  movl   $0x40302a,(%esp)

0x00401375  call   0x401be8 <printf>

0x0040137A  mov    0x1c(%esp),%eax

0x0040137E  mov    %eax,0x4(%esp)

0x00401382  movl   $0x403030,(%esp)

0x00401389  call   0x401be8 <printf>

**here>** 0x0040138E    lea    0x18(%esp),%eax

0x00401392  mov    %eax,0x4(%esp)

0x00401396  movl   $0x403036,(%esp)

0x0040139D  call   0x401be8 <printf>

0x004013A2  movl   $0x40303c,(%esp)

0x004013A9  call   0x401be0 <puts>

0x004013AE  mov    $0x0,%eax

0x004013B3  leave

0x004013B4  ret



Second for :

   // printf("4 %u\n", &(pa));

    printf("\n4 %u\n", &(*pa));



0x00401334  push   %ebp

0x00401335  mov    %esp,%ebp

0x00401337  and    $0xfffffff0,%esp

0x0040133A  sub    $0x20,%esp

0x0040133D  call   0x401970 <__main>

0x00401342  movl   $0x2,0x18(%esp)

0x0040134A  lea    0x18(%esp),%eax

0x0040134E  mov    %eax,0x1c(%esp)

0x00401352  lea    0x18(%esp),%eax

0x00401356  mov    %eax,0x4(%esp)

0x0040135A  movl   $0x403024,(%esp)

0x00401361  call   0x401be8 <printf>

0x00401366  mov    0x1c(%esp),%eax

0x0040136A  mov    %eax,0x4(%esp)

0x0040136E  movl   $0x40302a,(%esp)

0x00401375  call   0x401be8 <printf>

0x0040137A  mov    0x18(%esp),%eax

0x0040137E  mov    %eax,0x4(%esp)

0x00401382  movl   $0x403030,(%esp)

0x00401389  call   0x401be8 <printf>

**here>** 0x0040138E    mov    0x1c(%esp),%eax

0x00401392  mov    %eax,0x4(%esp)

0x00401396  movl   $0x403036,(%esp)

0x0040139D  call   0x401be8 <printf>

0x004013A2  movl   $0x40303d,(%esp)

0x004013A9  call   0x401be0 <puts>

0x004013AE  mov    $0x0,%eax

0x004013B3  leave

0x004013B4  ret


Third one for:

    printf("4 %u\n", &(pa));

    printf("\n4 %u\n", &(*pa));


0x00401334  push   %ebp

0x00401335  mov    %esp,%ebp

0x00401337  and    $0xfffffff0,%esp

0x0040133A  sub    $0x20,%esp

0x0040133D  call   0x401980 <__main>

0x00401342  movl   $0x2,0x1c(%esp)

0x0040134A  lea    0x1c(%esp),%eax

0x0040134E  mov    %eax,0x18(%esp)

0x00401352  lea    0x1c(%esp),%eax

0x00401356  mov    %eax,0x4(%esp)

0x0040135A  movl   $0x403024,(%esp)

0x00401361  call   0x401bf8 <printf>

0x00401366  mov    0x18(%esp),%eax

0x0040136A  mov    %eax,0x4(%esp)

0x0040136E  movl   $0x40302a,(%esp)

0x00401375  call   0x401bf8 <printf>

0x0040137A  mov    0x1c(%esp),%eax

0x0040137E  mov    %eax,0x4(%esp)

0x00401382  movl   $0x403030,(%esp)

0x00401389  call   0x401bf8 <printf>

0x0040138E  lea    0x18(%esp),%eax

0x00401392  mov    %eax,0x4(%esp)

0x00401396  movl   $0x403036,(%esp)

0x0040139D  call   0x401bf8 <printf>

**here>** 0x004013A2    mov    0x18(%esp),%eax

0x004013A6  mov    %eax,0x4(%esp)

0x004013AA  movl   $0x40303c,(%esp)

0x004013B1  call   0x401bf8 <printf>

0x004013B6  movl   $0x403043,(%esp)

0x004013BD  call   0x401bf0 <puts>

0x004013C2  mov    $0x0,%eax

0x004013C7  leave

0x004013C8  ret
4

6 に答える 6

4

それはおそらくコンパイラの最適化です

を使用しない場合&pa、 のすべての使用法はpaと同等&aです。

したがって、コンパイラはpa変数を完全に削除し、スペースを使用しなくなります。

于 2013-10-27T08:40:00.080 に答える
3

Here in the 2nd part the the *pa should have given 2 and &(*pa) should have given 2686748 but here the previous values have been altered!

プログラムを実行するたびに、実行するたびにまったく同じメモリ位置が得られると誰が言いましたか? %p住所の印刷にも使用します。

スタック領域は伝統的にヒープ領域に隣接し、反対方向に拡大していました。スタック ポインターがヒープ ポインターに一致すると、空きメモリが使い果たされます。(最新の大規模アドレス空間と仮想メモリ技術を使用すると、ほとんどどこにでも配置できますが、通常は逆方向に拡張されます。)

スタック領域には、プログラム スタック (LIFO 構造体) が含まれ、通常はメモリの上位部分に配置されます。標準の PC x86 コンピューター アーキテクチャでは、アドレス 0 に向かって大きくなります。他のいくつかのアーキテクチャでは、反対方向に成長します。「スタック ポインタ」レジスタは、スタックの先頭を追跡します。値がスタックに「プッシュ」されるたびに調整されます。

関数が呼び出されるたびに保存される情報とともに、自動変数が格納されるスタック。関数が呼び出されるたびに、戻り先のアドレスと、一部のマシン レジスタなど、呼び出し元の環境に関する特定の情報がスタックに保存されます。新しく呼び出された関数は、自動変数と一時変数用にスタックにスペースを割り当てます。

したがって、ポイントは変数であり、各実行に応じてどこにでも割り当てることができます。

画像

于 2013-10-27T08:39:15.283 に答える
0

予想される出力は次のようになります (&(*pa) の場合): 2686748、2686748、2、2686748

期待される出力が得られない理由と、どこで間違っているのか説明してください??

OPから、結果は再現可能であると想定しているため、複数回実行するとまったく同じ出力が得られます。右?したがって、OS はアドレス (少なくともスタック) のランダム化を行いません。

コンパイラの最適化が原因であるという BeniBela の理論に同意します。2 番目のプログラムでは、ローカル変数 pa が最適化されています。これは次と同等です:

int main()
{
    int a=2;

    printf("1 %u\n", &a );
    printf("2 %u\n", &a );
    printf("3 %d\n", a );    
    printf("\n4 %u\n", &(a)); // output not as expected 

    printf("End \n");
    return 0;
}

したがって、最初のプログラムと 2 番目のプログラムのスタック フレーム レイアウトは異なります。ただし、関数フレームのサイズは、デフォルトで gcc のスタックが 16 バイトにアラインされているため、変更されない場合があります。

更新:逆アセンブリから、スタック フレームは次のようになります。

最初の1つ

           +-------+
0x0028FF00 |       |  <== (%esp)
           +-------+
0x0028FF04 |       |
           +-------+
0x0028FF08 |       |
           +-------+
0x0028FF0c |       |
           +-------+
0x0028FF10 |       |
           +-------+
0x0028FF14 |       |
           +-------+
0x0028FF18 |       |  <== pa
           +-------+
0x0028FF1c |   2   |  <== a
           +-------+
0x0028FF20 |       |
           +-------+

次に2番目:

           +-------+
0x0028FF00 |       |  <== (%esp)
           +-------+
0x0028FF04 |       |
           +-------+
0x0028FF08 |       |
           +-------+
0x0028FF0c |       |
           +-------+
0x0028FF10 |       |
           +-------+
0x0028FF14 |       |
           +-------+
0x0028FF18 |   2   |  <== a
           +-------+
0x0028FF1c |       |
           +-------+
0x0028FF20 |       |
于 2013-10-27T12:28:48.210 に答える
0

最初のプログラムは のアドレスを取得しますpaが、2 番目のプログラムは取得しません。オブジェクトのアドレスを取得しない場合、C 実装は実際のメモリ空間を予約する必要がなく、最適化によってメモリへの配置が回避される場合があります。オブジェクトは、レジスターにのみ存在するか、追加の最適化によって完全に削除されることさえあります。

したがって、アドレスが取得される (および出力されるため、プログラムの外部でアドレスを強制的に監視できるようになる) かどうかによって、実装で使用する必要があるメモリが変わります。これにより、メモリ内の配置が異なり、アドレスが異なります。

(もちろん、これらのアドレスに関する保証はありません。実装は、選択したさまざまな方法でこれらのオブジェクトを自由に配置できます。)

于 2013-10-27T09:36:08.407 に答える
0

&aへのポインタアドレスを出力しますa

paaの値はアドレスであるため、へのポインターアドレスも出力paします (*paは逆参照された値です)。

a明らかな理由から、2を出力する必要があります

&(pa)住所を印刷しますpa

&(*pa)逆参照された pa のアドレス (のアドレスa)

pa             &(pa)           *(&pa)         *pa           a             &a
(a's address)  (pa's address)  (address of a) (value of a)  (value of a)  (address of a)

基本的に、それは期待される動作です。

于 2013-10-27T08:44:11.470 に答える
0

以下の単一の手順について説明します。

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int a=2;
    int *pa;
    pa=&a; // (1)

    printf("1  %u\n", &a ); // outputs the address of a
    printf("2  %u\n", pa ); // outputs the address of a as well due to (1)
    printf("3  %d\n", a ); // outputs the value of a

    printf("4  %u\n", &(pa)); // outputs the value of the pointer variable pa
    printf("4a %u\n", &(*pa)); // Outputs the address of the mempry pa points to, so it is esssentially pa. Output as expected!

    printf("End\n");
    return 0;
}
于 2013-10-27T09:00:49.160 に答える