6

次のコードを検討してください。

char buffer[256];

struct stX
{
    int a;
    int b;
    int c;
};

void * my_memcpy ( void * destination, const void * source, size_t num );

int main()
{
    struct stX x;
    x.a = 1;
    x.b = 2;
    x.c = 3;
    my_memcpy(buffer, &x.b, 2 * sizeof(int));
    {
        int i;
        for (i = 0; i < 2 * sizeof(int); ++i) {
            printf("%d\n", buffer[i]);
        }
    }
    return 0;
}

組み込みシステム用の特定のコンパイラは、xa と xc への割り当てを削除することを決定します (これらは (少なくとも明らかに) 使用されないため)。これはc標準で許可されている最適化ですか?

もちろん、構造インスタンスを揮発性として定義すると、割り当てに含まれます。

gcc と msvc は、この最適化を実行しません (ただし、これは実際の理由ではありません)。

更新:いくつかの回答が (正しく) 想定されているように、コンパイラは memcpy の既知の定義により最適化できますが、これは私の特定の実装が行うことではありません。(スタック上の構造体用にメモリを予約し、割り当てを実行しないだけです。) memcpy を、コンパイラが利用できる定義を持たない関数に置き換えたと仮定しましょう。また、関数呼び出しの後にバッファが使用されると仮定しましょう。それに応じて上記のサンプル コードを更新しました。

4

4 に答える 4

3

はい、アプリケーションの観察可能な動作が変更されない限り、コンパイラは自由に何でもできます。*

ただし、これは、適合するプログラム、つまり明確に定義された動作を備えたプログラム があることを前提としています。あなたのコードは明確に定義された動作を示しません。x.cへのポインターを間接参照してアクセスすることは有効ではありませんx.b(これは、暗黙的に要求memcpyしていることです)。

更新:上記の段落は正しくない可能性があります。コメントの議論を見てください...


* より厳密な定義については、C99 標準のセクション 5.1.2.3 を参照してください。

揮発性オブジェクトへのアクセス、オブジェクトの変更、ファイルの変更、またはこれらの操作のいずれかを行う関数の呼び出しはすべて、実行環境の状態の変化である副作用です。

...

実際の実装では、値が使用されておらず、必要な副作用が生成されていないと推測できる場合、式の一部を評価する必要はありません。

于 2013-01-25T16:00:17.730 に答える
0

はい。コンパイラは、変数の値が「独自に」変更できないと想定するコードに最適化を適用できます。

ウィキペディアから

void bar(void) {
    foo = 0;

    while (foo != 255)
         ;
}

最適化コンパイラは、他のコードがfooに格納されている値を変更できない可能性があることに気付き、常に0に等しいと想定します。したがって、コンパイラは関数本体を次のような無限ループに置き換えます。

void bar(void) {
    foo = 0;

    while (true)
         ;
}

ただし、fooは、CPUに接続されたデバイスのハードウェアレジスタなど、コンピュータシステムの他の要素によっていつでも変更できる場所を表す場合があります。上記のコードはそのような変更を検出することはありません

コンパイラが上記のようにコードを最適化するのを防ぐために、volatileキーワードが使用されます。

static volatile int foo;

void bar (void) {
    foo = 0;

    while (foo != 255)
        ;
}
于 2013-01-25T16:10:52.523 に答える
0

GCC はまったく同じ最適化を行います (x86_64 と -O3 を使用):

        .file   "test.c"
        .section        .text.startup,"ax",@progbits
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB12:
        .cfi_startproc
        movl    $2, -20(%rsp)
        movl    $3, -16(%rsp)
        movq    -20(%rsp), %rax
        movq    %rax, buffer(%rip)
        ret
        .cfi_endproc
.LFE12:
        .size   main, .-main
        .comm   buffer,256,32
        .ident  "GCC: (GNU) 4.7.2 20120921 (Red Hat 4.7.2-2)"
        .section        .note.GNU-stack,"",@progbits

読むことができるように、x.a = 1実行されません。そして、おそらくさらに最適化される可能性があります。

于 2013-01-25T16:06:35.597 に答える
0

いくつかの予備的なポイント:

  • mainこれはvoid、標準に関する限り、プログラムの動作が定義されていないことを意味します。voidしかし、あなたの実装がreturn from を明示的に許可していると仮定しましょうmain。また、UBmemcpyを含めなかったので、スコープ内にプロトタイプはありません。string.h

  • の値bufferは決して使用されないため、実際にはプログラム全体に観察可能な動作はありません。したがって、コンパイラはコードの一部またはすべてを削除できます。何もしない 1 つのプログラムは、標準に関する限り、別のプログラムと同じくらい優れています。

の後の2*sizeof(int)バイトを出力するプログラムを作成したふりをします。buffermemcpy

#include <string.h>
#include <stdio.h>

char buffer[256];

struct stX
{
    int a;
    int b;
    int c;
};

int main()
{
    struct stX x;
    x.a = 1;
    x.b = 2;
    x.c = 3;
    memcpy(buffer, &x.b, 2 * sizeof(int));
    {
        int i;
        for (i = 0; i < 2 * sizeof(int); ++i) {
            printf("%d\n", buffer[i]);
        }
    }
    return 0; /* just in case this is C89 */
}

したがって、私が理解しているように、コンパイラx.aはこのコードでへの割り当てを削除しても問題ありませんが、への割り当てを削除するx.cと、コンパイラが標準に準拠していないことを意味します。構造体に奇妙なパディングが含まれていない限り。

との間に少なくともsizeof(int)バイトのパディングがあれば、への割り当てを削除しても問題ありません。標準ではそのようなパディングが許可されていますが、使用している実際の実装にはそれがないと安全に推測できると思います;-)bcstruct stXx.c

説明は、 、、およびxの値を持つ、抽象マシンで定義された状態を持つことです。メモリ内のバイトのシーケンスである「オブジェクト表現」もあります。のアドレスから始まる、これらのバイトのアクセスへの呼び出し。これはすべて正当であり、構造体の末尾から少なくともバイトが戻っていることが保証されています。これは、C が構造体のデータ メンバーが定義されている順序で配置されることを保証しているためです。だから、後に来なければなりません。実装が持つ唯一の自由は、それが直後に来るかどうか、またはそれらの間にパディングがあるかどうかです。abcxsizeof(stX)memcpy2*sizeof(int)x.bx.b2*sizeof(int)cb

コンパイラは、 が標準でどのように定義されているかを「知る」ことが許可されているため、 の前にあるため、へのこの呼び出しは のバイトのいずれにもアクセスしないmemcpyと推測できます。これにより、物理メモリ内のバイトに抽象マシン内の値が含まれないように最適化できます。ただし、 への呼び出しが のバイトにアクセスしないと推測することは許可されていません(構造体にそれほど多くのパディングがない限り)。そのため、これらのバイトの適切な値を にコピーすることを省略することは許可されていません。memcpyx.ax.bmemcpyx.cbuffer

の末尾を超えて読み取られた追加のバイトがx.bの内容ではなくパディングであった場合x.c、もちろん、への割り当ての有無はx.c無関係になるため、省略できます。

別の正当な最適化は、コードを次のように変換することです。

int main() {
    ((int*)buffer)[0] = 2;
    ((int*)buffer)[1] = 3;
    ... same printf loop as before ...
}

つまり、物理メモリのxどこにでも「実際に存在する」必要はありませんが、標準では、実装で表現されてbufferいる方法に従って、出力されるバイトが正しいバイトである必要があります。intそして、実装でどのようstruct stXにレイアウトされているか。

于 2013-01-26T19:43:32.330 に答える