2
void myFunc(char dummy) {
    char *addrFirstArg = &dummy;
}

int main() {
    char dummy = 42;
    myFunc(dummy);
    return 0;
}

I run the above under gdb and add a breakpoint at myFunc. I step once to compute the addrFirstArg value and examine it.

I also do

info frame
to spit out information about the frame myFunc. As far as my understanding of the C stack implementation goes, I expect that addrFirstArg should be 8 bytes above the base pointer for the frame myFunc.

This is the output that I see:

(gdb) p &dummy
$1 = 0xffffd094 "*\202\f\b\032\004"

(gdb) info frame
Stack level 0, frame at 0xffffd0b0:
 eip = 0x8048330 in findStackBottom (reporter.c:64); saved eip 0x8048478
 called by frame at 0xffffd170
 source language c.
 Arglist at 0xffffd0a8, args: dummy=42 '*'
 Locals at 0xffffd0a8, Previous frame's sp is 0xffffd0b0
 Saved registers:
 ebp at 0xffffd0a8, eip at 0xffffd0ac

(gdb) x/1c 0xffffd0b0
0xffffd0b0:     42 'a'

Thus, inside the frame myFunc, ebp points to the location 0xffffd0a8, where as the address of dummy is 0xffffd094, which is 0x14 bytes below ebp, instead of being 0x8 bytes above it.

This 'discrepancy' disappears if I declare my dummy to be an int and myFunc to take in an int argument.

I'm really intrigued by this behavior. It was reproducible - I ran it a bunch of times.

4

1 に答える 1

2

;を使用すると、違いがよくわかりますgcc -S。私たちが持っているcharの場合

char case                       int case (diffs)

pushl   %ebp
movl    %esp, %ebp
subl    $20, %esp               subl    $16, %esp
movl    8(%ebp), %eax           x
movb    %al, -20(%ebp)          x
leal    -20(%ebp), %eax         leal    8(%ebp), %eax
movl    %eax, -4(%ebp)
leave
ret

関数が入力されると、スタックは次のようになります (上から上へ):

esp     return address
esp+4   2A 00 00 00

これは、単一の文字がこの方法でスタックに「プッシュ」されるためです。

movsbl  -1(%ebp), %eax
movl    %eax, (%esp)

x86 はリトルエンディアンです。

「プリアンブル」の後はこんな感じ

esp            (room for local char dummy - byte 42) ...
...
ebp-4          room for char *
esp+20 = ebp   ebp
ebp+4          return addr
ebp+8          2A 00 00 00       

次に、「char」(32 ビット整数として格納) が ebp+8 (メインによって「プッシュ」された元の値ですが、「32 ビット」として) から eax に取得され、下位の下位バイトがローカルに配置されます。保管所。

int の場合は、アラインメントが不要で、スタック上にあったもののアドレスを「直接」取得できるため、より単純です。

esp             ...
...
ebp-4          room for int *
esp+16 = ebp   ebp
ebp+4          return addr
ebp+8          2A 00 00 00       

したがって、最初のケース (char のケース) では、単一の char を保持するために esp がさらに 4 バイト減分されます。追加のローカル ストレージがあります。

なぜこれ?

ご覧のとおり、単一の文字が 32 ビットの「整数」(eax) としてスタックにプッシュされ、同じ方法で eax に戻されます。この操作には、エンディアンの問題はありません。

しかし、char に対して ebp+8 のアドレスが返され、マシンがリトル エンディアンではない場合はどうなるでしょうか。その場合、ebp+8 は を指し、逆参照する00 00 00 2A*dummy42 ではなく 0 になります。

したがって、「偽の int」(エンディアンが何であれ、CPU が首尾一貫して処理する操作) がレジスタに取り込まれると、そのアドレスがその char (下位バイト) を指すことが保証されるように、LSByte をローカルストレージに配置する必要があります。延期されたとき。これが余分なコードの理由であり、ebp+8 が使用されていないという事実です:エンディアンとアドレスのアライメントの要件 (たとえば、00 00 00 2Aビッグ エンディアンの場合の 2A は奇数アドレスになります。

于 2012-05-26T08:43:58.027 に答える