-2

理解しようとしている不可解な動作が見られます...

サンプルコード..ローカル変数のアドレスを返しているという事実を無視してください..

編集: gcc の最適化動作を理解するための例として、このコードを使用しています。このサンプル コードの未定義の動作によって、gcc の最適化ロジックが変更されるとは思いません。と思われる方は、説明してください。

    #include<stdio.h>
    char *foo() {
        char arr[] = "hello world is here..\n";
        return arr;
    }

    int main() {
        char *ptr;

        ptr = foo();
        printf("0x%x \n", ptr);
        printf("%s", ptr);
    }

これを linux/x86 マシンで実行すると、main() の最初の printf でアドレスが出力されますが、2 番目の printf では何も出力されません。gcc が何らかの形で配列の初期化を最適化したようです。

次のように foo() を変更すると、文字列が正しく出力されます。未定義の動作であることはわかっています。しかし、ここで gcc の最適化を理解することだけに興味があります。

    char *foo() {
        char arr[] = "hello\n";
        printf("0x%x\n", arr);
        return arr;
    }

元のコードでは、foo がアドレスを返す可能性があるのに、初期化が最適化されていない可能性はありますか? これはアセンブリコードです..私はx86アセンブリに精通していません..2つのケースでgccは正確に何をしていますか?

            .LC0:
                    .string "hello\n"
                    .text
            .globl foo
                    .type   foo, @function
            foo:
            .LFB2:
                    pushq   %rbp
            .LCFI0:
                    movq    %rsp, %rbp
            .LCFI1:
                    movl    .LC0(%rip), %eax
                    movl    %eax, -16(%rbp)
                    movzwl  .LC0+4(%rip), %eax
                    movw    %ax, -12(%rbp)
                    movzbl  .LC0+6(%rip), %eax
                    movb    %al, -10(%rbp)
                    leaq    -16(%rbp), %rax
                    leave
                    ret
            .LFE2:

そして追加の printf を使用した foo() のアセンブリ コード..

            .LC0:
                    .string "hello\n"
            .LC1:
                    .string "0x%x\n"
                    .text
            .globl foo
                    .type   foo, @function
            foo:
            .LFB2:
                    pushq   %rbp
            .LCFI0:
                    movq    %rsp, %rbp
            .LCFI1:
                    subq    $16, %rsp
            .LCFI2:
                    movl    .LC0(%rip), %eax
                    movl    %eax, -16(%rbp)
                    movzwl  .LC0+4(%rip), %eax
                    movw    %ax, -12(%rbp)
                    movzbl  .LC0+6(%rip), %eax
                    movb    %al, -10(%rbp)
                    leaq    -16(%rbp), %rsi
                    movl    $.LC1, %edi
                    movl    $0, %eax
                    call    printf
                    leaq    -16(%rbp), %rax
                    leave
                    ret
4

4 に答える 4

6

Please ignore that fact that I am returning a local variable address..

Well as you are also using the value returned by the function, we cannot ignore this. C says your program invokes undefined behavior and the implementation has the right to do anything it wants.

于 2012-06-06T07:53:34.273 に答える
4

何が起こるかを説明するのは簡単です: スタックにデータを置き、このアドレスを返し、再び解放するようにコンパイラーに指示しました。つまり、住所はわかりますが、内容に関する証拠はありません。

関数から戻った後、printf()2 回呼び出します。最初に指定されたアドレスを出力し、次にそれに含まれるコンテンツを出力します。

出力のポイントに到達するまで、スタックの内容は他のもので文字化けする可能性があります (そして明らかに文字化けします)。printf()内部で一定量のスタックが必要であり、それを使用 (= 書き込み) して、元のコンテンツを破損していると想像できます。

前述の動作が未定義であると述べている他の人は完全に正しいですが、コンパイラがなぜそのように動作するのか疑問に思っても害はありません。


編集: この場合、アセンブリ コード fpr を見るだけでは不十分ですfoo()。最適化とは、関数呼び出しを関数に含まれるコードに置き換えることができることも意味します。自分でコードをコンパイルした後、これが当てはまるようです。foo のコードは引き続きオブジェクト ファイルに含まれていますが、main(). これにより、前述の配列がローカルにmain()なり、最後まで割り当てられたままになります。しかし、前述のとおり、これは単なる偶然であり、信頼できるものではありません。

アセンブリ コードは次のようになります。

.LC1:
    .string "0x%x \n"
.LC2:
    .string "%s"
    .text
    .p2align 4,,15
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    pushl   %ebx
    subl    $60, %esp
    leal    26(%esp), %ebx
    movl    %ebx, 4(%esp)
    movl    $1819043176, 26(%esp)
    movl    $1952784495, 30(%esp)
    movl    $1696607843, 34(%esp)
    movl    $539911028, 38(%esp)
    movl    $778269797, 42(%esp)
    movw    $10, 46(%esp)
    movl    $.LC0, (%esp)
    call    printf
    movl    %ebx, 4(%esp)
    movl    $.LC1, (%esp)
    call    printf
    movl    %ebx, 4(%esp)
    movl    $.LC2, (%esp)
    call    printf
    addl    $60, %esp
    popl    %ebx
    movl    %ebp, %esp
    popl    %ebp
    ret

ご覧のとおり、あなたにはありませcallfoo()。データは 4 バイトのブロックでスタックに書き込まれ、すぐに に渡されprintfます。

于 2012-06-06T08:17:40.403 に答える
1

「未定義の動作」とは、コンパイラのメンテナーがこのケースを気にする必要がないことを意味します。つまり、この場合コンパイラが行うことはまったく無意味です。未定義の動作を呼び出してはならないことを除いて、「理解」することは何もありませ

誰かにあなたの質問に時間を割いてもらいたい場合は、明確に定義された例を作成してください。

于 2012-06-06T08:10:30.447 に答える
0

指定されたコードの最適化をどのようにチェックしていますか。
return arr配列のベースアドレスを返します。このアドレスが に移動するmainと、 が指すメモリarrが破壊されます。この後、何かが起こる可能性があり、segfault が発生したり、プログラムがクラッシュしたりする可能性があります。初期化されていないポインターを使用したときに発生することはすべて発生する可能性があります。このコードは決して最適化をチェックしていません。投稿に書かれているように、すでに知っている未定義の動作を理解するのに役立つ場合があります!!!!

于 2012-06-06T09:43:07.000 に答える